关于JVM垃圾回收的java.lang.ref.Finalizer问题
作者:影࿐ེ
JVM垃圾回收的java.lang.ref.Finalizer
基础知识
Shallow Size
- 对象自身占用的内存大小,不包括它引用的对象
Retained Size
- Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和
可达性分析算法:
基本思路是通过一系列成为GC ROOTS 的对象作为起点,当一个对象到 GC ROOTS 没有任何相连,证明此对象是不可达,即被判断为可回收的对象。
之后的过程是:
被标记不可达的对象以后,进行第一次标记,和第一次筛选,条件是该对象 有没有必要执行finalize方法
没有必要执行finalize方法的情况是
1.finalize已经执行过(finalize 方法只会被执行一次)
2.该对象没有重写finalize方法
如果要执行finalize方法,该对象进入一个F-Queue队列,稍后有 一个优先级为8的 finalizer线程来执行(注意:如果一个对象在 finalize 方法中运行缓慢,将会导致队列后的其他对象永远等待,严重时将会导致系统崩溃)
GC对队列中进行第二次标记,如果在执行finalize方法的时候将自己和GC ROOTS关联上,该对象即可逃离回收,否则,被回收掉
重要
对象是在已经被GC识别为是垃圾后才丢到Queue中的,在queue中依然占用内存
引用对象的类型
引用对象的类型第一篇已说过,分为以下类型
强引用>软引用>弱引用>虚引用,
涉及到的相关类:
- 软引用:softReference
- 弱引用:weekRefernce
- 虚引用:pathomReference
但还有一个特殊的引用:FinalReference类,该类也是JVM调优的常见的调优场景之一。
要了解FinalReference,先了解final()
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线程管理。
详细流程
JVM启动时,检测对象是否实现了finalize()方法,检测方法:判断has_finalizer_flag和registerfinalizerrsaltInit字段
A对象实现了finalize()方法,就会把A对象的finalize()函数注册到finalizerthread线程和referencequeue队列中,注册完毕后,且称该函数注册的B对象称为Finalizer类,指向原有的A对象。
GC回收垃圾时,启动FinalizerThread线程,检测Finalizer类对应的A对象的finalize()方法是否已执行,已调用,则可以把Finalizer类去除,回收Finalizer类,从而回收A对象;如果没有调用,则继续交由FinalizerThread线程管理,不回收。
从上面原因引申出:为什么堆内存溢出?
GC回收时,用户线程、GC回收线程和FinalizerThread线程都在运行,但由于FinalizerThread线程优先度比GC回收线程和用户线程低,所以导致回收速度比对象创建速度慢,最终导致堆内存溢出。
示例程序:
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
总结
1. finalize占用内存搞这和GC的机制有关
实现了object的finalize()的类在创建时会新建一个FinalizerReference,这个对象是强引用类型,封装了override finalize()的对象,下面直接叫原对象。
原对象没有被其他对象引用时(FinalizeReference除外),执行GC不会马上被清除掉,而是放入一个静态链表中(ReferenceQueue),有一个守护线程专门去维护这个链表,如何维护呢?
就是轮到该线程执行时就弹出里面的对象,执行它们的finalize(),对应的FinalizerReference对象在下次执行GC时就会被清理掉。
一个堆的FinalizerReference会组成一条双向链表,垃圾回收器应该会持有链表头(链表头在FinalizerReference中为一个静态成员)。
2. 为什么会泄漏
直接原因就是守护线程优先级比较低,运行的时间比较少。
如果较短时间内创建较多的原对象,就会因为守护线程来不及弹出原对象而使FinalizerReference和原对象都得不到回收。
无论怎样调用GC都没有用的,因为只要原对象没有被守护线程弹出执行其finalize()方法,FinalizerReference对象就不会被GC回收。
3. 知道这些机制后,应该注意以下几点
(1)紧缺资源不要依赖finalize()来释放。
(2)尽量不要重载finalize()。
(3)如果必须重载finalize(),一定要记得调用super.finalize,也建议把类实现成单例模式(减少FinalizerReference占用)。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。