如何判断对象可以回收?

引用计数法

两个对象互相引用,会导致两个对象都没法被回收。

可达性分析
  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以 回收
哪些对象可以作为GC root?
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

Java中的四种引用?

实线是强引用。

强引用

new关键字创建的都是强引用。GC root引用链可以找到它就不会被回收。只有当所有的强引用链断开才会被回收。没有GC ROOT 直接或者间接引用改对象才会被回收。

上面图中只有B,C对象都不引用A1对象,它才会被回收。

软引用

内存紧张时候会触发垃圾回收,回收部分软引用所引用的对象。上图中的A2.

B对象不再引用A2时,内存不足,软引用和A2之间的会断开,A2可能被回收。

private static final int _4M = 4 * 1024 * 1024;

public static void main(String[] args) {
        //list和SoftReference之间是强引用,softReference和byte数组之间是软引用
        ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
        SoftReference<byte[]> _4MByte1 = new SoftReference<>(new byte[_4M]);
        SoftReference<byte[]> _4MByte2 = new SoftReference<>(new byte[_4M]);
        SoftReference<byte[]> _4MByte3 = new SoftReference<>(new byte[_4M]);
        SoftReference<byte[]> _4MByte4 = new SoftReference<>(new byte[_4M]);
        SoftReference<byte[]> _4MByte5 = new SoftReference<>(new byte[_4M]);
        list.add(_4MByte1);
        list.add(_4MByte2);
        list.add(_4MByte3);
        list.add(_4MByte4);
        list.add(_4MByte5);
        for (int i = 0 ;i<list.size();i++){
            System.out.println(list.get(i).get());
        }
}
null
null
null
[B@5cad8086
[B@6e0be858
//会自动删除之前的弱引用引用的对象,但是弱引用对象仍然存在但是为null

内存不足时候会回收软引用引用的对象也就是byte数组,但是不会回收软引用对象。

清理软引用对象需要用到引用队列。

//引用队列,用于移除引用为空的软引用对象
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

//list和SoftReference之间是强引用,softReference和byte数组之间是软引用
ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
SoftReference<byte[]> _4MByte1 = new SoftReference<>(new byte[_4M],queue);
SoftReference<byte[]> _4MByte2 = new SoftReference<>(new byte[_4M],queue);
SoftReference<byte[]> _4MByte3 = new SoftReference<>(new byte[_4M],queue);
SoftReference<byte[]> _4MByte4 = new SoftReference<>(new byte[_4M],queue);
SoftReference<byte[]> _4MByte5 = new SoftReference<>(new byte[_4M],queue);
list.add(_4MByte1);
list.add(_4MByte2);
list.add(_4MByte3);
list.add(_4MByte4);
list.add(_4MByte5);

//遍历引用队列,如果有元素,则移除
Reference<? extends byte[]> poll = queue.poll();
while(poll != null) {
    //引用队列不为空,则从集合中移除该元素
    list.remove(poll);
    //移动到引用队列中的下一个元素
    poll = queue.poll();
}
for (int i = 0 ;i<list.size();i++){
    System.out.println(list.get(i).get());
}
弱引用

只有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用所引用的对象.

B对象不再引用A3,A3会直接别回收。

弱引用对象:WeakReference,使用方法和上面的软引用类似。

虚引用

虚引用的对象类垃圾回收时,虚引用自己会进入引用队列,间接的一个线程调用虚引用对象的一个方法调用Unsafe.freeMenory释放内存。NIO操作,释放直接内存。

终结器引用

Object类有一个finalize方法。当某个对象不再被其他的对象所引用时,会先将终结器引用对象放入引用队列中,然后根据终结器引用对象找到它所引用的对象,调用被引用对象的finalize方法,然后该对象可以被垃圾回收。

线程优先级较低,不一定能及时被执行到。

Jvm怎么进行垃圾回收?垃圾回收算法

标记删除

速度快,但会造成内存碎片化。

标记整理

速度慢,没有内存碎片。

复制

不会有内存碎片,需要占用双倍的内存空间。

分代垃圾回收机制

新生代

分为三块区域伊甸园,幸存区1,幸存区2

对象首先分配在伊甸园区,当伊甸园内存不足时候,触发Minor gc。第一次Minor GC时候还存活的对象会copy复制到From区年龄加一,等第二次垃圾回收的时候会扫描from区域,检查时候有可以删除的垃圾。如果第二次Minor GC还是不能删除的话年龄再累加一。当年龄达到最大寿命15(4bit存储在对象头)时,会将幸存区对象放入老年代。

  • Minor GC 会引发 stop the world。暂停其他线程执行,进行垃圾回收。
老年代

老年代内存不足时候先尝试触发Minor GC ,空间仍然不足时候触发Full GCstw时间更长。回收整个Jvm内存,包括新生代

VM参数
描述参数设置
堆初始大小-Xms-Xms20M
堆最大大小-Xmx 或 -XX:MaxHeapSize=size-Xmx20M
新生代大小-Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )-Xmn10M
幸存区比例(动态)-XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例-XX:SurvivorRatio=ratio=8标识伊甸园大小为8剩下两份from,to均分->8-1-1
晋升阈值-XX:MaxTenuringThreshold=threshold新生代晋升老年代的阈值
晋升详情-XX:+PrintTenuringDistribution
GC详情-XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC-XX:+ScavengeBeforeFullGC

/**
     * vm参数设置:
     * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
     * @param args
     */
public static void main(String[] args) {

}
Heap
 def new generation   total 9216K, used 2180K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)  //新生代概述:总内存9M(有1M的To区域复制使用),
  eden space 8192K,  26% used [0x00000000fec00000, 0x00000000fee21378, 0x00000000ff400000)//伊甸园8m
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)//老年代概述:总内存10M
   the space 10240K,   0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)//空间10M,使用率0%
 Metaspace       used 3229K, capacity 4496K, committed 4864K, reserved 1056768K//元空间
  class space    used 349K, capacity 388K, committed 512K, reserved 1048576K
垃圾回收演示

JVM参数设置:-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc

初始化堆大小20M,堆最大20M,新生代10M,使用UseSerialGC ,打印gc详细信息

大对象直接进老年代

新生代内存不足以存储的8M大小的对象,直接进入老年代

public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    list.add(new byte[_8M]);
}
Heap
 def new generation   total 9216K, used 2180K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  26% used [0x00000000fec00000, 0x00000000fee21378, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000)//老年代直接占用80%
 Metaspace       used 3228K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 349K, capacity 388K, committed 512K, reserved 1048576K
正常的GC
public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    list.add(new byte[_7M]);
    list.add(new byte[_7M]);
    list.add(new byte[_1M]);
    list.add(new byte[_1M]);
    list.add(new byte[_512k]);
    list.add(new byte[_128k]);
    list.add(new byte[_128k]);
    list.add(new byte[_128k]);
    list.add(new byte[_128k]);
}
[GC (Allocation Failure) [DefNew: 1852K->669K(9216K), 0.0014500 secs] 1852K->669K(19456K), 0.0014839 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 8328K->4K(9216K), 0.0037866 secs] 8328K->7792K(19456K), 0.0038069 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 7254K->7254K(9216K), 0.0000090 secs][Tenured: 7788K->7788K(10240K), 0.0023580 secs] 15042K->14960K(19456K), [Metaspace: 3215K->3215K(1056768K)], 0.0023994 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
//进行了三次GC
Heap
 def new generation   total 9216K, used 8451K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K, 100% used [0x00000000fec00000, 0x00000000ff400000, 0x00000000ff400000)
  from space 1024K,  25% used [0x00000000ff400000, 0x00000000ff440f30, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 9836K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  96% used [0x00000000ff600000, 0x00000000fff9b080, 0x00000000fff9b200, 0x0000000100000000)
 Metaspace       used 3236K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 350K, capacity 388K, committed 512K, reserved 1048576K