java深入源码

java深入源码知识

1、哪些情况下的对象会被垃圾回收机制处理掉?

java 的垃圾回收是 java 语言的重要功能之一。当程序创建对象、数组等引用类型实体时,系统会在堆内存中位置分配一块内存区,对象就保存在这块内存区中,当这块内存不在被任何变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。

垃圾回收机制只负责回收堆内存中的对象。当一个对象在堆内存中运行时,根据它被引用变量引用的状态,可以把它所处的状态分成如下三种。

  • 可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法。
  • 可恢复状态:如果程序中某个对象不再有任何变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象的所占用的内存,再回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用 finalize() 方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象进入不可达状态。
  • 不可达状态:当对象与所有引用变量的联系被切断,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态,系统会回收该对象所占有的资源。

2、Java线程池

ThreadPoolExecutor

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

提供了4个构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);

}

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

 从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释下一下构造器中各个参数的含义:

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

    1
    2
    3
    4
    5
    6
    7
    TimeUnit.DAYS;               //天
    TimeUnit.HOURS; //小时
    TimeUnit.MINUTES; //分钟
    TimeUnit.SECONDS; //秒
    TimeUnit.MILLISECONDS; //毫秒
    TimeUnit.MICROSECONDS; //微妙
    TimeUnit.NANOSECONDS; //纳秒
  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

    1
    2
    3
    4
    ArrayBlockingQueue;
    PriorityBlockingQueue
    LinkedBlockingQueue;
    SynchronousQueue;

    ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

  • threadFactory:线程工厂,主要用来创建线程;

  • handler:表示当拒绝处理任务时的策略,有以下四种取值:

    1
    2
    3
    4
    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

    线程池状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING = -1 << COUNT_BITS;
    private static final int SHUTDOWN = 0 << COUNT_BITS;
    private static final int STOP = 1 << COUNT_BITS;
    private static final int TIDYING = 2 << COUNT_BITS;
    private static final int TERMINATED = 3 << COUNT_BITS;

3、ArrayList的扩容机制

  • ArrayList实现了List接口,继承了AbstractList,底层是数组实现的,一般我们把它认为是可以自增扩容的数组。它是非线程安全的,一般多用于单线程环境下(与Vector最大的区别就是,Vector是线程安全的,所以ArrayList 性能相对Vector 会好些),它实现了Serializable接口,因此它支持序列化,能够通过序列化传输(实际上java类库中的大部分类都是实现了这个接口的),实现了RandomAccess接口,支持快速随机访问(只是个标注接口,并没有实际的方法),这里主要表现为可以通过下标直接访问(底层是数组实现的,所以直接用数组下标来索引),实现了Cloneable接口,能被克隆。

  • ArrayList提供了3个构造方法

  • 先看一下无参的构造方法

    1
    2
    3
    4
    5
    6
    /**
    * Constructs an empty list with an initial capacity of ten.
    */
    public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    1
    2
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//默认空数组
    transient Object[] elementData; // non-private to simplify nested class access

    这里的将默认的空数组指向elementData。

  • 在看一下带参的构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
    elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
    // replace with empty array.
    this.elementData = EMPTY_ELEMENTDATA;
    }
    }

    可以看到将集合c转为数组指向elementData,这里的size是Arraylist的私有变量,是指元素个数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
    this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
    } else {
    throw new IllegalArgumentException("Illegal Capacity: "+
    initialCapacity);
    }
    }
    1
    2
    3
    4
    /**
    * Shared empty array instance used for empty instances.
    */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    如果参数大于0则创建一个长度为initialCapacity的数组指向elementData,如果为0则指向空数组(这里的空数组不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA 这个数组)。

  • add方法

    1
    2
    3
    4
    5
    public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
    }

    首先调用了ensureCapacityInternal()方法。size是当前集合拥有的元素个数(未算进准备新增的e元素),从源码看出,调用了ensureCapacityInternal来保证容量问题,传进去的参数是size+1,来保证新增元素后容量满足要求。

    1
    2
    3
    private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    再看calculateCapacity()方法

    1
    2
    3
    4
    5
    6
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }

    进一步判断elementData是否为默认的空数组,即是否调用了无参构造函数。如果是的话,则在DEFAULT_CAPACITY和minCapacity取一个大的,DEFAULT_CAPACITY默认为10

    1
    private static final int DEFAULT_CAPACITY = 10;

    所以,如果此时为空数组的话,size=0,size+1=1,与DEFAULT_CAPACITY比起来返回10.,所以没有指定大小,默认初始为一个容量为10的数组。然后在调用ensureExplicitCapacity()方法.

    1
    2
    3
    4
    5
    6
    7
    private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }

    这里的modCount用在集合的Fail-Fast机制(即快速失败机制)的判断中使用的。稍后再看。

    这里看到新增元素后的minCapacity大小与当前集合的大小相比,如果大于当前集合则扩容,调用grow()方法.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
    1
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    可以看到这里先取到当前集合的长度,进行1.5倍增加,这里>>1位运算右移1位,相当于/2即,oldCapacity+oldCapacity/2。

    然后扩容后的长度与minCapactiy相比,如果比所需的最小长度要小,则直接将最小长度指向最小需要的长度。

    如果扩容后的长度比MAX_ARRAY_SIZE还长,则调用hugeCapacity()方法。传入的参数是当前扩容需要的最小长度minCapacity。

    1
    2
    3
    4
    5
    6
    7
    private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
    }

    可以看到如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值。否则返回MAX_ARRAY_SIZE。

    然后回到grow方法,调用Arrays.copyof方法,即复制原数组内容到一个新容量的大数组里。这里Arrays.copyof方法实际是调用System.arraycopy方法。

    应该可以很清楚的知道ArrayList底层扩容的原理了。ArrayList是1.5倍逐渐增长

    当然,如果一开始知道数据量很大的话,可以在初始化时预先指定容量。

  • get方法

    1
    2
    3
    4
    5
    public E get(int index) {
    rangeCheck(index);

    return elementData(index);
    }
    1
    2
    3
    4
    private void rangeCheck(int index) {
    if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    通过数组下标索引来指定返回的内容.

  • Java容器的快速报错机制ConcurrentModificationException

    Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果你在迭代遍历某个容器的过程中,另一个进程介入其中,并且插入,删除或修改此容器的某个对象,就会立刻抛出ConcurrentModificationException。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    public Iterator<E> iterator() {
    return new Itr();
    }

    /**
    * An optimized version of AbstractList.Itr
    */
    private class Itr implements Iterator<E> {
    int cursor; // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    public boolean hasNext() {
    return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
    throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
    throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
    }

    public void remove() {
    if (lastRet < 0)
    throw new IllegalStateException();
    checkForComodification();

    try {
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    lastRet = -1;
    expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
    }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
    Objects.requireNonNull(consumer);
    final int size = ArrayList.this.size;
    int i = cursor;
    if (i >= size) {
    return;
    }
    final Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length) {
    throw new ConcurrentModificationException();
    }
    while (i != size && modCount == expectedModCount) {
    consumer.accept((E) elementData[i++]);
    }
    // update once at end of iteration to reduce heap write traffic
    cursor = i;
    lastRet = i - 1;
    checkForComodification();
    }

    final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
    }
    }

    从上面方法可以看到在迭代遍历的过程中都调用了方法checkForComodification来判断当前ArrayList是否是同步的。现在来举一个栗子,假设你往一个Integer类型的ArrayList插入了10条数据,那么每操作一次modCount(继承自父类AbstractList)就加一所以就变成10,而当你对这个集合进行遍历的时候就把modCount传到expectedModCount这个变量里,然后ArrayList在checkForComodification中通过判断两个变量是否相等来确认当前集合是否是同步的,如果不同步就抛出ConcurrentModificationException。所谓的不同步指的就是,如果你在遍历的过程中对ArrayList集合本身进行add,remove等操作时候就会发生。当然如果你用的是Iterator那么使用它的remove是允许的因为此时你直接操作的不是ArrayList集合而是它的Iterator对象。

    所以ArrayList循环删除元素时,是不可以调用remove方法,会抛出ConcurrentModificationException()异常。

4、Java的4种引用方式

  • 强引用 是指创建一个对象并把这个对象赋给一个引用变量。例如 String a=”ss”;强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

  • 软引用 (SoftReference)如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;只要垃圾回收器没有回收它,该对象就可以被程序使用。在SoftReference指向的对象被回收后, SoftReference本身其实也就没有用了. java提供了一个ReferenceQueue来保存这些所指向的对象已经被回收的reference. 用法是在定义SoftReference的时候将一个ReferenceQueue的对象作为参数传入构造函。

  • 弱引用(WeakReference)弱引用与软引用差不多都属于可有可无;弱引用与软引用的区别在于:只具有弱引用的对象生命周期更短,垃圾回收器一旦发现就只具有弱引用的对象,不管当前内存是否足够,都会将其回收,但是垃圾回收器是一个优先级很低的线程,不一定会很快的发现那些只具有弱引用的对象,弱引用可用于解决内存泄漏的问题

  • 虚引用(PhantomReference)虚引用不论所引用的对象是不是null,不论内存空间是否充足,都会被垃圾回收器回收

    可以看到,只要进行垃圾回收,虚引用就会被回收

    总结:

    对于垃圾回收器回收的顺序为

    虚引用—弱引用—-软引用—强引用。

    多使用软引用做缓存可以很好地避免oom.

5、常见的编码方式

  • Unicode

    如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码。

    Unicode 当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母AinU+0041表示英语的大写字母AU+4E25表示汉字。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表

  • UTF-8

    UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

    UTF-8 的编码规则很简单,只有二条:

    1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

    2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

  • ISO-8859-1

    ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符

  • ASCII

    学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来

  • GB2312

    它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

6、UTF-8编码中的中文占几个字节,int几个字节

​ 如果一个字节,最高位为0,表示这是一个ASCII字符(00~7F)
​ 如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数

中文需要3个字节。int需要1个字节。

7、静态代理和动态代理的区别,什么场景使用?

​ 代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务

Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。

  • 静态代理:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 静态代理事先知道要代理的是什么
  • 动态代理:动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。

8、Java的异常体系

9、Java位运算符

  • &=

    与运算,都为1才为1,如:a=1,b=2, a&=b 即a=a&b a为1

  • |=

    非运算,只要有1即为1,如:a=1,b=2 a|=b 即a=a|b a为3

  • ^=

    异或运算,相同为0不同为1,如:a=1,b=2 a^=b 即a=a^b a为2

  • <<=

    左移b位,如a =3,b=2 ,a为11 ,左移 1100即a=12,也是a 2 2

  • 》》=

    右移2位,如a=3,b=1,a为11,右移 1即a=1,也是a/2

  • 》》》

    与》》的区别是,高位用0补,如a=3,b=1 a为11, a变为001 a为1

  • ~=非运算

    非运算 二进制所有的位数取反,因为为运算都是按补码计算的,正数补码就是原码,负数的补码

    例如~3=-4

    int是4个字节,为32位

    3即00000000 00000000 00000000 00000011

    取反后为,依然是补码,我们需要转为原码

    11111111 11111111 11111111 11111100

    最高位是1 ,负数,所以负数的补码转为原码,左右的第一个1不变,其余数取反。

    即10000000 00000000 00000000 00000100

    即-4