# JVM 垃圾回收-01 可达性分析与强弱软虚引用详解

2020-12-10  本文已影响0人  丿易小易

可达型分析

可达性分析的理论

基本思路:
通过一系列GC Roots的根对象作为起始节点集, 根据引用关系向下搜索, 
搜索过程中走过的路径称为引用链,所有在这个引用链上的对象都是可达对象,
而其他的没有与GC Roots根对象关联的单独存在的引用链上的对象则为不可达对象

图示:


image
在内存中的场景:
上图的 object1 中有字段object2 , object3 的引用, 
当我们把object1的引用ref赋值为null, 
那么object1就变为了 上图右侧的不可达对象的图例了..需要被gc回收掉了,如下图引用
image
代码实现举例子:


@Data
public class Test {
    public Test instance;
    public int i;
    public Test(int i) {
        this.i = i;
    }
}


public static void main(String[] args) {
    Test test0 = new Test(0);
    Test test1 = new Test(1);
    Test test2 = new Test(2);
    test0.instance=test1;
    test1=test2;
    System.out.println(test0.getInstance().getI());
}

执行结果 1
为什么不是2呢,test1已经被test2赋值了呀!
这是因为引用变量只能是指向某个对象的,而不能是指向引用的
所以test0.instance 指向的是test1在堆中的对象,
所以在后面改变了test1的指向时, 并没有影响之前的指向.


public static void main(String[] args) {
    Test test0 = new Test(0);
    Test test1 = new Test(1);
    Test test2 = new Test(2);
    test0.instance=test1;
    test1.instance=test2;
    test0=null;
}

ps: test0=null则 test0的原映射对象是不在引用链上了,会被gc回收.
且test0.instance的由上例子可知是引用的对象test1, 但这里不会回收test1,
因为test1也自己定义了一个根对象, 所以test1还是在test1引用链上,但不在test0的引用链上了,
如果将test1=null也这样设置,那么test1也将会被gc回收;

问题:那么在平时的代码中我们要不要在使用后对象就将对象置空呢?
这个问题要看 JVM 垃圾回收-判断对象是否可以回收 中的描述了

可达性根枚举对象

GC Roots的对象
1. 虚拟机栈中的引用的对象            如:Object o = new Object(); o即为虚拟栈中的引用对象
2. 方法区中类静态属性引用的对象       如:public static String static_str="111";  static_str 即为引用对象
3. 方法区中常量引用的对象
4. 本地方法栈中 JNI(native方法)引用的对象
5. 被同步锁synchronize持有的对象

ps:这里提到了很多引用,我们下面将详细描述引用的分类,引用之所以进行设定不同的分类, 
   是因为在内存中数据的要求是多样的,
   比如 我们希望在内存充足的情况下,保留这些类,但内存不足的时候,就进行回收(缓存机制)

引用是如何分类

强引用

使用方法

正常的写的java代码都是强引用99.999%
Object o = new Object();

这种就是强引用了,是不是在代码中随处可见,最亲切。 
只要某个对象有强引用与之关联,这个对象永远不会被回收
即使内存不足,JVM宁愿抛出OOM,也不会去回收

回收的方法:

o = null;

示例:

我们需要新写一个类,然后重写finalize方法

public class Student {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Student 被回收了");
    }
}

public static void main(String[] args) {
        Student student = new Student();
        student = null;
        System.gc();
}

运行结果:
Student 被回收了

注释:finalize方法是当gc时,系统来调用该方法
释放该对象在堆中占用的内存. 该方法在Object中

软引用

使用方法

用java.lang.ref.SoftReference 进行包装
SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());

回收方法

gc自动处理
当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉
也就是只有在内存不足,JVM才会回收该对象

示例:

修改idea的堆内存大小 -Xmx20m  最大堆内存为20m , 设置内存追踪 -XX:+PrintGCDetails

  SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
  System.out.println(softReference.get());
  System.gc();
  System.out.println(softReference.get());
  byte[] bytes = new byte[1024 * 1024 * 10];
  System.out.println(softReference.get());
  
  创建一个软引用对象,里面包裹了byte[],byte[]占用了10M,然后又创建了10Mbyte[]
  
结果:

[GC (Allocation Failure)  5632K->1393K(19968K), 0.0049815 secs]
[B@763d9750
[GC (System.gc())  16187K->12252K(19968K), 0.0014227 secs]
[Full GC (System.gc())  12252K->11862K(19968K), 0.0101228 secs]
[B@763d9750
[GC (Allocation Failure)  12032K->11926K(19968K), 0.0004047 secs]
[GC (Allocation Failure)  11926K->11926K(19968K), 0.0002842 secs]
[Full GC (Allocation Failure)  11926K->11784K(19968K), 0.0049854 secs]
[GC (Allocation Failure)  11784K->11784K(19968K), 0.0002981 secs]
[Full GC (Allocation Failure)  11784K->1495K(16896K), 0.0066588 secs]
null
 
分析: 当gc回收时, 软 

注释:一般是缓存使用

弱引用

使用方法

java.lang.ref.WeakReference包装
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
System.out.println(weakReference.get());

回收方式

System.gc();
不管内存是否足够,只要发生GC,都会被回收

示例

WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());

结果:
[GC (Allocation Failure)  5632K->1410K(19968K), 0.0016663 secs]
[B@763d9750
[GC (System.gc())  5726K->2034K(19968K), 0.0015112 secs]
[Full GC (System.gc())  2034K->1914K(19968K), 0.0113147 secs]
null

注释:弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。

虚引用

使用方法:

 当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中
 ReferenceQueue queue = new ReferenceQueue();
 PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
 System.out.println(reference.get());
 
 作用时,当gc回收的时候会产生一条记录到ReferenceQueue中

回收方式:

System.gc() 自动回收

示例:

@Data
@Builder
public class Student {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Student 被回收了");
    }
}


public static void main(String[] args) {
        ReferenceQueue queue = new ReferenceQueue();
        List<byte[]> bytes = new ArrayList<>();
        PhantomReference<Student> reference = new PhantomReference<Student>(new Student(),queue);
        new Thread(() -> {
            for (int i = 0; i < 100;i++ ) {
                bytes.add(new byte[1024 * 1024]);
            }
        }).start();

        new Thread(() -> {
            while (true) {
                Reference poll = queue.poll();
                if (poll != null) {
                    System.out.println("虚引用被回收了:" + poll);
                }
            }
        }).start();
        Scanner scanner = new Scanner(System.in);
        scanner.hasNext();
    }

结果: 

[GC (Allocation Failure)  5632K->1372K(19968K), 0.0011861 secs]
[GC (Allocation Failure)  7004K->2185K(19968K), 0.0017764 secs]
[GC (Allocation Failure)  7205K->6394K(19968K), 0.0028557 secs]
[GC (Allocation Failure)  11653K->11554K(19968K), 0.0027832 secs]
[Full GC (Ergonomics)  11554K->11374K(19968K), 0.0134628 secs]
[Full GC (Ergonomics)  16624K->16456K(19968K), 0.0055213 secs]
[Full GC (Ergonomics)  18620K->18488K(19968K), 0.0098597 secs]
[Full GC (Allocation Failure)  18488K->18402K(19968K), 0.0115410 secs]
Student 被回收了
[Full GC (Ergonomics)  18949K->17924K(19968K), 0.0106182 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
    at com.logistic.JVMTest.lambda$main$0(JVMTest.java:16)
    at com.logistic.JVMTest$$Lambda$1/747464370.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)
虚引用被回收了:java.lang.ref.PhantomReference@5eb93cd7

注释:
第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC
第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来

上一篇下一篇

猜你喜欢

热点阅读