JVM专题-垃圾回收

Posted IT-老牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM专题-垃圾回收相关的知识,希望对你有一定的参考价值。

文章目录

1.如何判断对象可以回收

1.1.引用计数法

  • 当一个对象被其他变量引用,该对象计数加一,当某个变量不在引用该对象,其计数减一
  • 当一个对象引用没有被其他变量引用时,即计数变为0时,该对象就可以被回收

缺点:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放

1.2.可达性分析算法

  • JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看能否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以回收
  • 可以作为GC Root的对象
    – 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    – 方法区中类静态属性引用的对象
    – 方法区中常量引用的对象
    – 本地方法栈中JNI(即一般说的Native方法)引用的对象
    – 所有被同步锁(synchronized关键字)持有的对象。

示例

/**
 * 演示GC Roots
 */
public class Demo2_2 

    public static void main(String[] args) throws InterruptedException, IOException 
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println(1);
        System.in.read();

        list1 = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end...");
    

运行程序,分别生成垃圾回收前后的dunp文件

jps // 查看进程号
// format=b 生成文件格式为二进制;live主动触发垃圾回收,保留存活对象;file表示存放位置
jmap -dump:format=b,live,file=1.bin 进程号51125

list1置空前,生成dunmp文件1.bin

List<Object> list1 = new ArrayList<>();

list1是局部变量,存在于活动栈帧;new ArrayList<>()产生的对象才是存在于堆中的对象。即此处new ArrayList<>()对应的那个对象才能作为根对象。

list1置空后,生成dunmp文件2.bin

因为在执行

jmap -dump:format=b,live,file=2.bin 51125

使用了live参数,主动调用了垃圾回收。由于list1被置空,list对象无人引用,所以被垃圾回收了。所以在根对象中找不到了。

2.五种引用

强引用

只有所有 GC Roots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

强引用对象回收

软引用

仅有【软引用】引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
可以配合【引用队列】来释放软引用自身

软引用对象回收


此时A2对象可能被回收。

  • A2对象仅仅只被软引用对象引用
  • 在执行GC时,内存空间不足了,才会被垃圾回收
  • 回收后,软引用对象本身可以通过进入引用队列进行释放

弱引用

仅有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合【引用队列】来释放弱引用自身

弱引用对象的回收

此时A3对象可能会被回收

  • A3对象仅仅被弱引用对象引用
  • 当执行GC时,无论内存是否不足,都会被垃圾回收
  • 回收后,弱引用对象本身可以通过进入引用队列进行释放

虚引用

必须配合【引用队列】使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将【虚引用】入队, 由 Reference Handler 线程调用虚引用相关方法释放【直接内存】
如上图,B对象不再引用ByteBuffer对象,ByteBuffer就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner放入引用队列中,然后调用它的clean方法来释放直接内存

虚引用对象的回收


虚引用一般是对直接内存分配的应用。

  • 当声明ByteBuffer时,ByteBuffer会分配一块直接内存,并把直接内存的地址传递给虚引用对象Cleaner。

  • ByteBuffer不再被强引用时,被回收后,直接内存还没有被释放。这时会将虚引用放入虚引用的引用队列,由Reference Handler线程监控,发现虚引用对象,调用虚引用相关方法Unsafe.freeMemory释放直接内存。

终结器引用

无需手动编码,但其内部配合【引用队列】使用,在垃圾回收时,【终结器引用】入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过【终结器引用】找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
如上图,B对象不再引用A4对象。这时终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的finalize方法。调用以后,该对象就可以被垃圾回收了

终结器引用的回收

  • 所有的类都继承自Object类,里面有一个终结方法finalize()方法,当对象重写了finalize()方法,且没有强引用引用它时,它就可以被当成垃圾进行垃圾回收。
  • 当对象没有被强引用时,会由jvm为该对象创建一个对应的终结器引用。当这个对象被垃圾回收时,会将终结器引用加入引用队列,但是对象不会被垃圾回收。
  • 再由一个优先级较低的Finalizer线程去监控引用队列是否有终结器引用,如果有,就通过终结器引用找到A4对象,调用其finalize()方法,等调用之后,等下一次垃圾回收时,就可以被垃圾回收了。
  • 工作效率低,第一次GC不会回收对象,先将终结器引用入队,等到第二次垃圾回收才有可能被回收。

6.代码示例

6.1软引用

# 虚拟机参数
-Xmx20m -XX:+PrintGCDetails -verbose:gc
/**
 * 演示软引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_3 

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



    public static void main(String[] args) throws IOException 
        /*List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) 
            list.add(new byte[_4MB]);
        

        System.in.read();*/
        soft();


    

    public static void soft() 
        // list --> SoftReference --> byte[]

        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) 
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());

        
        System.out.println("循环结束:" + list.size());
        for (SoftReference<byte[]> ref : list) 
            System.out.println(ref.get());
        
    


/**
 * 演示软引用, 配合引用队列
 */
public class Demo2_4 
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) 
        List<SoftReference<byte[]>> list = new ArrayList<>();

        // 引用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) 
            // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        

        // 从队列中获取无用的 软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while( poll != null) 
            list.remove(poll);
            poll = queue.poll();
        

        System.out.println("===========================");
        for (SoftReference<byte[]> reference : list) 
            System.out.println(reference.get());
        

    


6.2 弱引用

虚拟机参数

-Xmx20m -XX:+PrintGCDetails -verbose:gc
/**
 * 演示弱引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_5 
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) 
        //  list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) 
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) 
                System.out.print(w.get()+" ");
            
            System.out.println();

        
        System.out.println("循环结束:" + list.size());
    


以上是关于JVM专题-垃圾回收的主要内容,如果未能解决你的问题,请参考以下文章

JVM专题-垃圾回收

JVM专题-垃圾回收

JVM专题-垃圾回收

JVM专题-垃圾回收

JVM专题-垃圾回收

JVM调优面试题——垃圾回收专题