技术自查番外篇一:JVM垃圾回收的java.lang.ref.Finalizer

本文探讨了FinalReference在JVM中的特殊角色,尤其关注其在对象生命周期管理和内存溢出中的影响,通过实例演示了为何 finalize() 方法可能导致堆内存溢出,并提供了MAT工具分析案例和排查技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

通过额外的知识,补充对JVM的了解和调优,不可小看这些额外的知识点,这是经验的积累

引用对象的类型

引用对象的类型第一篇已说过,分为以下类型

强引用>软引用>弱引用>虚引用,

涉及到的相关类:

软引用:softReference 

弱引用:weekRefernce

虚引用:pathomReference

但还有一个特殊的引用:FinalReference类,该类也是JVM调优的常见的调优场景之一。

要了解FinalReference,先了解final()

JAVA程序猿都知道经典的面试题:final,finally和finalize()的区别?

前两者我相信大多数人都能答出,但finalize()虽然也有人答出,但可能只是硬搬面试题答案。

1. final:修饰词,作用于成员变量,方法和类。

  •         成员变量,基本数据类型表示该成员变量不可变更数值,对象表示该对象引用地址不可改变。
  •         方法,表示该方法不可重载
  •         类,表示该类不可被继承

2. finally:是异常处理机制的一部分,表示总是执行这段代码,先执行异常,后执行finally

@Slf4j
public class FinallyDemo {

    public static void main(String[] args) {
        try {
            int i = 1 / 0;
            log.info("1/0");
        } catch (Exception e) {
            log.info("执行异常");
        } finally {
            log.info("执行finally片段");
        }
    }
}
// 打印结果:
// 10:36:09.777 [main] INFO com.gc.demo.finalize.finlly.FinallyDemo - 执行异常
// 10:36:09.780 [main] INFO com.gc.demo.finalize.finlly.FinallyDemo - 执行finally片段

3. finalize():是object的一个方法。简简单单的一句话,我想大多数人懂得,但其实我们应该继续深入。

Finalize()

Object源码

public class Object {
    private static native void registerNatives();
    static {
        registerNatives();
    }
    .......
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
}

上述代码可以看出:finalize()已被弃用,不建议使用

为什么finalize()已被弃用,不建议使用?

原因:容易导致堆内存溢出。

为什么不直接删除呢?

1. 兼容老旧项目

2. 是GC回收对象前,对该对象生前(被回收前)必须执行的逻辑业务,保证程序正常运行。例如:FileInputStream,源码如下

public class FileInputStream extends InputStream{
    ......
    static class AltFinalizer {
    private final FileInputStream fis;
    AltFinalizer(FileInputStream fis) {
        this.fis = fis;
    }
    @Override
    @SuppressWarnings("deprecation")
    protected final void finalize() {
        try {
            if ((fis.fd != null) && (fis.fd != FileDescriptor.in)) {
                /* if fd is shared, the references in FileDescriptor
                 * will ensure that finalizer is only called when
                 * safe to do so. All references using the fd have
                 * become unreachable. We can call close()
                 */
                fis.close();
            }
        } catch (IOException ioe) {
            // ignore
        }
    }
}

FileInputStream类的close()方法大家都不陌生,用于关闭流输入,但他被写到finalize()方法内。

原理

简单概括

JVM启动时,检测所有对象,是否实现了finalize()方法,如果实现了,就会该对象标记为finalizer类,且交由finalizerThread线程管理,当GC回收时,finalizerThread线程就会处理finalizer类,检测是否有执行finalize()方法,如果有,则可以把finalizer从线程中去除,对应的对象就可以被回收;如果没有,则一直交由finalizerThread线程管理。

详细流程

  1. JVM启动时,检测对象是否实现了finalize()方法,检测方法:判断has_finalizer_flag和registerfinalizerrsaltInit字段
  2. A对象实现了finalize()方法,就会把A对象的finalize()函数注册到finalizerthread线程和referencequeue队列中,注册完毕后,且称该函数注册的B对象称为Finalizer类,指向原有的A对象。
  3. GC回收垃圾时,启动FinalizerThread线程,检测Finalizer类对应的A对象的finalize()方法是否已执行,已调用,则可以把Finalizer类去除,回收Finalizer类,从而回收A对象;如果没有调用,则继续交由FinalizerThread线程管理,不回收。

上面也抛出一个问题:

为什么finalize()已被弃用,不建议使用?

原因:容易造成堆内存溢出

从上面原因引申出:为什么堆内存溢出?

GC回收时,用户线程、GC回收线程和FinalizerThread线程都在运行,但由于FinalizerThread线程优先度比GC回收线程和用户线程低,所以导致回收速度比对象创建速度慢,最终导致堆内存溢出。

Demo

案例地址:https://gitee.com/banbeisudashui/gc-demo.git 

环境说明:

JDK11、G1垃圾收集器

JVM设置

-Xms10m
-Xmx10m
-Xlog:ref*=debug
-Xlog:gc:gc.log
-Xlog:gc+heap=trace
-verbose:gc
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\mycode\gc-demo\a.dump
public class FinalizeGcDemo {

    private static class GcDemo {
        private byte[] content = new byte[1024 * 1024];

        @Override
        protected void finalize() throws Throwable {
            System.out.println("执行了finalize方法");
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            GcDemo gcDemo = new GcDemo();
        }
    }

}

日志如下:

[0.453s][debug][gc,heap       ] GC(23) Heap after GC invocations=21 (full 13): garbage-first heap   total 10240K, used 10123K [0x00000000ff600000, 0x0000000100000000)
[0.453s][debug][gc,heap       ] GC(23)   region size 1024K, 0 young (0K), 0 survivors (0K)
[0.453s][debug][gc,heap       ] GC(23)  Metaspace       used 6220K, capacity 6395K, committed 6528K, reserved 1056768K
[0.453s][debug][gc,heap       ] GC(23)   class space    used 538K, capacity 606K, committed 640K, reserved 1048576K
[0.453s][info ][gc            ] GC(23) Pause Full (G1 Evacuation Pause) 9M->9M(10M) 5.420ms
[0.455s][debug][gc,heap       ] GC(24) Heap before GC invocations=21 (full 13): garbage-first heap   total 10240K, used 10123K [0x00000000ff600000, 0x0000000100000000)
[0.455s][debug][gc,heap       ] GC(24)   region size 1024K, 0 young (0K), 0 survivors (0K)
[0.455s][debug][gc,heap       ] GC(24)  Metaspace       used 6220K, capacity 6395K, committed 6528K, reserved 1056768K
[0.455s][debug][gc,heap       ] GC(24)   class space    used 538K, capacity 606K, committed 640K, reserved 1048576K
[0.455s][debug][gc,ref        ] GC(24) Skipped phase1 of Reference Processing due to unavailable references
[0.455s][debug][gc,ref        ] GC(24) Skipped phase2 of Reference Processing due to unavailable references
[0.455s][debug][gc,ref        ] GC(24) Skipped phase3 of Reference Processing due to unavailable references
[0.455s][debug][gc,ref        ] GC(24) Skipped phase4 of Reference Processing due to unavailable references
[0.455s][debug][gc,phases,ref ] GC(24)     Reference Processing: 0.0ms
[0.455s][debug][gc,phases,ref ] GC(24)       Reconsider SoftReferences: 0.0ms
[0.455s][debug][gc,phases,ref ] GC(24)         SoftRef (ms):             skipped
[0.455s][debug][gc,phases,ref ] GC(24)       Notify Soft/WeakReferences: 0.0ms
[0.455s][debug][gc,phases,ref ] GC(24)         SoftRef (ms):             skipped
[0.455s][debug][gc,phases,ref ] GC(24)         WeakRef (ms):             skipped
[0.455s][debug][gc,phases,ref ] GC(24)         FinalRef (ms):            skipped
[0.455s][debug][gc,phases,ref ] GC(24)         Total (ms):               skipped
[0.455s][debug][gc,phases,ref ] GC(24)       Notify and keep alive finalizable: 0.0ms
[0.455s][debug][gc,phases,ref ] GC(24)         FinalRef (ms):            skipped
[0.455s][debug][gc,phases,ref ] GC(24)       Notify PhantomReferences: 0.0ms
[0.455s][debug][gc,phases,ref ] GC(24)         PhantomRef (ms):          skipped
[0.455s][debug][gc,phases,ref ] GC(24)       SoftReference:
[0.455s][debug][gc,phases,ref ] GC(24)         Discovered: 0
[0.455s][debug][gc,phases,ref ] GC(24)         Cleared: 0
[0.455s][debug][gc,phases,ref ] GC(24)       WeakReference:
[0.455s][debug][gc,phases,ref ] GC(24)         Discovered: 0
[0.455s][debug][gc,phases,ref ] GC(24)         Cleared: 0
[0.455s][debug][gc,phases,ref ] GC(24)       FinalReference:
[0.455s][debug][gc,phases,ref ] GC(24)         Discovered: 0
[0.455s][debug][gc,phases,ref ] GC(24)         Cleared: 0
[0.455s][debug][gc,phases,ref ] GC(24)       PhantomReference:
[0.455s][debug][gc,phases,ref ] GC(24)         Discovered: 0
[0.455s][debug][gc,phases,ref ] GC(24)         Cleared: 0
[0.455s][info ][gc,heap       ] GC(24) Eden regions: 0->0(1)
[0.455s][trace][gc,heap       ] GC(24)  Used: 0K, Waste: 0K
[0.455s][info ][gc,heap       ] GC(24) Survivor regions: 0->0(1)
[0.455s][trace][gc,heap       ] GC(24)  Used: 0K, Waste: 0K
[0.455s][info ][gc,heap       ] GC(24) Old regions: 2->2
[0.455s][trace][gc,heap       ] GC(24)  Used: 1931K, Waste: 116K
[0.455s][info ][gc,heap       ] GC(24) Humongous regions: 8->8
[0.455s][trace][gc,heap       ] GC(24)  Used: 8192K, Waste: 0K
[0.455s][debug][gc,heap       ] GC(24) Heap after GC invocations=22 (full 13): garbage-first heap   total 10240K, used 10123K [0x00000000ff600000, 0x0000000100000000)
[0.455s][debug][gc,heap       ] GC(24)   region size 1024K, 0 young (0K), 0 survivors (0K)
[0.455s][debug][gc,heap       ] GC(24)  Metaspace       used 6220K, capacity 6395K, committed 6528K, reserved 1056768K
[0.455s][debug][gc,heap       ] GC(24)   class space    used 538K, capacity 606K, committed 640K, reserved 1048576K
[0.455s][info ][gc            ] GC(24) Pause Young (Concurrent Start) (G1 Evacuation Pause) 9M->9M(10M) 0.585ms
[0.455s][debug][gc,heap       ] GC(25) Heap before GC invocations=22 (full 13): garbage-first heap   total 10240K, used 10123K [0x00000000ff600000, 0x0000000100000000)
[0.455s][debug][gc,heap       ] GC(25)   region size 1024K, 0 young (0K), 0 survivors (0K)
[0.455s][debug][gc,heap       ] GC(25)  Metaspace       used 6220K, capacity 6395K, committed 6528K, reserved 1056768K
[0.455s][debug][gc,heap       ] GC(25)   class space    used 538K, capacity 606K, committed 640K, reserved 1048576K
[0.455s][info ][gc            ] GC(26) Concurrent Cycle
[0.457s][debug][gc,ref        ] GC(25) Skipped phase3 of Reference Processing due to unavailable references
[0.457s][debug][gc,phases,ref ] GC(25) Reference Processing: 0.0ms
[0.457s][debug][gc,phases,ref ] GC(25)   Reconsider SoftReferences: 0.0ms
[0.457s][debug][gc,phases,ref ] GC(25)     SoftRef (ms):             Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.457s][debug][gc,phases,ref ] GC(25)   Notify Soft/WeakReferences: 0.0ms
[0.457s][debug][gc,phases,ref ] GC(25)     SoftRef (ms):             Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.457s][debug][gc,phases,ref ] GC(25)     WeakRef (ms):             Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.457s][debug][gc,phases,ref ] GC(25)     FinalRef (ms):            Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.457s][debug][gc,phases,ref ] GC(25)     Total (ms):               Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.457s][debug][gc,phases,ref ] GC(25)   Notify and keep alive finalizable: 0.0ms
[0.457s][debug][gc,phases,ref ] GC(25)     FinalRef (ms):            skipped
[0.457s][debug][gc,phases,ref ] GC(25)   Notify PhantomReferences: 0.0ms
[0.457s][debug][gc,phases,ref ] GC(25)     PhantomRef (ms):          Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.457s][debug][gc,phases,ref ] GC(25)   SoftReference:
[0.457s][debug][gc,phases,ref ] GC(25)     Discovered: 6
[0.457s][debug][gc,phases,ref ] GC(25)     Cleared: 6
[0.457s][debug][gc,phases,ref ] GC(25)   WeakReference:
[0.457s][debug][gc,phases,ref ] GC(25)     Discovered: 5
[0.457s][debug][gc,phases,ref ] GC(25)     Cleared: 5
[0.457s][debug][gc,phases,ref ] GC(25)   FinalReference:
[0.457s][debug][gc,phases,ref ] GC(25)     Discovered: 0
[0.457s][debug][gc,phases,ref ] GC(25)     Cleared: 0
[0.457s][debug][gc,phases,ref ] GC(25)   PhantomReference:
[0.457s][debug][gc,phases,ref ] GC(25)     Discovered: 66
[0.457s][debug][gc,phases,ref ] GC(25)     Cleared: 66
[0.460s][info ][gc,heap       ] GC(25) Eden regions: 0->0(1)
[0.460s][trace][gc,heap       ] GC(25)  Used: 0K, Waste: 0K
[0.460s][info ][gc,heap       ] GC(25) Survivor regions: 0->0(1)
[0.460s][trace][gc,heap       ] GC(25)  Used: 0K, Waste: 0K
[0.460s][info ][gc,heap       ] GC(25) Old regions: 2->2
[0.460s][trace][gc,heap       ] GC(25)  Used: 1931K, Waste: 116K
[0.460s][info ][gc,heap       ] GC(25) Humongous regions: 8->6
[0.460s][trace][gc,heap       ] GC(25)  Used: 6144K, Waste: 0K
[0.460s][debug][gc,heap       ] GC(25) Heap after GC invocations=23 (full 14): garbage-first heap   total 10240K, used 8075K [0x00000000ff600000, 0x0000000100000000)
[0.460s][debug][gc,heap       ] GC(25)   region size 1024K, 0 young (0K), 0 survivors (0K)
[0.460s][debug][gc,heap       ] GC(25)  Metaspace       used 6220K, capacity 6395K, committed 6528K, reserved 1056768K
[0.460s][debug][gc,heap       ] GC(25)   class space    used 538K, capacity 606K, committed 640K, reserved 1048576K
[0.460s][info ][gc            ] GC(25) Pause Full (G1 Evacuation Pause) 9M->7M(10M) 5.032ms
[0.461s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28c8
执行了finalize方法
执行了finalize方法
执行了finalize方法
[0.461s][info ][gc            ] GC(26) Concurrent Cycle 5.981ms
[0.462s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28d0
[0.462s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28d8
[0.462s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f44b8
[0.462s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b710
[0.462s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b718
[0.462s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f29c0
[0.462s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b720
[0.462s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b728
[0.462s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28e0
[0.462s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28e8
[0.463s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f29c8
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b730
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b740
[0.463s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28f0
[0.463s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f28f8
[0.463s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f2900
[0.463s][info ][oopstorage,ref] StringTable weak: allocated 0x00000187729f2908
[0.463s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f29d0
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b748
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b750
[0.463s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f29d8
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b758
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b760
[0.463s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b768
[0.463s][info ][oopstorage,ref] JNI Global: released 0x000001877265b670
[0.464s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b670
[0.464s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b770
[0.464s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f29e0
[0.464s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b778
[0.464s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b780
[0.464s][info ][oopstorage,ref] JNI Weak: allocated 0x00000187729f29e8
[0.464s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b788
[0.464s][info ][oopstorage,ref] JNI Global: allocated 0x000001877265b790
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.gc.demo.finalize.FinalizeGcDemo.main(FinalizeGcDemo.java:26)

日志最后抛出堆内存溢出(java.lang.OutOfMemoryError: Java heap space)异常。

通过MAT工具分析刚才程序运行的dump文件

 

 上面的分析报告,明确指出了Finalizer类已经占用堆内存的51.88%以上,且主要因为GcDemo类下finalize()方法。

打印Reference类的JVM参数设置

JDK1.9之前 -XX:+PrintReferenceGC
JDK1.9(含1.9)以后 -Xlog:ref*=debug

日志打印

[0.323s][info ][gc            ] GC(3) Pause Full (G1 Humongous Allocation) 8M->7M(10M) 5.099ms
[0.324s][debug][gc,ref        ] GC(4) Skipped phase3 of Reference Processing due to unavailable references
[0.324s][debug][gc,phases,ref ] GC(4) Reference Processing: 0.0ms
[0.324s][debug][gc,phases,ref ] GC(4)   Reconsider SoftReferences: 0.0ms
[0.324s][debug][gc,phases,ref ] GC(4)     SoftRef (ms):             Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.324s][debug][gc,phases,ref ] GC(4)   Notify Soft/WeakReferences: 0.0ms
[0.324s][debug][gc,phases,ref ] GC(4)     SoftRef (ms):             Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.324s][debug][gc,phases,ref ] GC(4)     WeakRef (ms):             Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.324s][debug][gc,phases,ref ] GC(4)     FinalRef (ms):            Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.324s][debug][gc,phases,ref ] GC(4)     Total (ms):               Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.324s][debug][gc,phases,ref ] GC(4)   Notify and keep alive finalizable: 0.0ms
[0.324s][debug][gc,phases,ref ] GC(4)     FinalRef (ms):            skipped
[0.324s][debug][gc,phases,ref ] GC(4)   Notify PhantomReferences: 0.0ms
[0.324s][debug][gc,phases,ref ] GC(4)     PhantomRef (ms):          Min:  0.0, Avg:  0.0, Max:  0.0, Diff:  0.0, Sum:  0.0, Workers: 1
[0.324s][debug][gc,phases,ref ] GC(4)   SoftReference:
[0.324s][debug][gc,phases,ref ] GC(4)     Discovered: 34
[0.324s][debug][gc,phases,ref ] GC(4)     Cleared: 5
[0.324s][debug][gc,phases,ref ] GC(4)   WeakReference:
[0.324s][debug][gc,phases,ref ] GC(4)     Discovered: 5
[0.324s][debug][gc,phases,ref ] GC(4)     Cleared: 5
[0.324s][debug][gc,phases,ref ] GC(4)   FinalReference:
[0.324s][debug][gc,phases,ref ] GC(4)     Discovered: 0
[0.324s][debug][gc,phases,ref ] GC(4)     Cleared: 0
[0.324s][debug][gc,phases,ref ] GC(4)   PhantomReference:
[0.324s][debug][gc,phases,ref ] GC(4)     Discovered: 62
[0.324s][debug][gc,phases,ref ] GC(4)     Cleared: 62
[0.324s][info ][oopstorage,ref] VM Weak Oop Handles: released 0x0000018bb1d48748
[0.324s][info ][oopstorage,ref] VM Weak Oop Handles: released 0x0000018bb1d487b0
[0.324s][info ][oopstorage,ref] VM Weak Oop Handles: released 0x0000018bb1d48660

附其他自查篇章

技术自查(JAVA方向)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值