finalize方法与Java GC

转自http://www.threaddeath.com/

闲逛ITEye时看到了译帝的一篇翻译博客,其中提到了关于Java类重写finalize方法后带来的诡异的GC overhead limit问题。博客的结尾非常详细的说明了这个问题产生的原理,但是始终有一个地方没有得到清晰的答案:由于finalize方法是Object类的protected方法,即无论重写与否,所有的Java类都会带有finalize方法,但为什么只有重写之后才会出现GC问题,不重写与重写的真实差别到底在哪儿?

通过思考始终得不到答案,索性打开Eclipse直接调试代码:

  • 首先验证了博客中的现象的确可以重现。
  • 中间一度怀疑问题的原因可能是finalize方法中引用了类的静态变量AtomicInteger会引起GC的问题(实际上这个方向是错误的,GC是根据对象是否存在被引用关系来判断对象是否回收,惭愧),删掉原来的方法体然后随意在finalize方法中声明了一个变量,结果运行时问题仍然存在。
  • 到了此时,开始怀疑是不是和finalize方法体有关系了,直接删除掉finalize方法体中的所有内容,运行后发现问题真的不存在了!
  • 由于出现GC问题时内存堆中存在大量Finalizer对象,而Finalizer对象只能通过Finalizer类的静态register方法创建,因此尝试在register方法中加上断点对两种情况分别运行,发现方法体为空时,register方法不会被调用,这样自定义对象便没有被任何对象引用,可以轻松的被GC回收掉。
  • 由于register方法是被VM所调用,只能求助于莫枢大神,而莫枢也确认了只要Java类以及它所有的祖先类中不含有finalize方法或者finalize方法体为空时,VM便不会将该类的对象实例注册为finalizable对象(地址)。

至此,算是把整个问题的来龙去脉给整理的差不多了:

  1. JVM创建自定义对象。
  2. JVM检测对象类以及祖先类是否含有非空的finalize方法定义,如果均没有,则不进行后续的Finalizer相关处理。
  3. JVM调用Finalizer类的静态register方法,创建包含该对象引用的Finalizer对象,同时Finalizer静态类将该Finalizer对象加入到一个单向链表中。
  4. 运行过程中,JVM会将这些Finalizer对象的状态更新为pending(这部分可以参考java.lang.ref.Reference类中的说明)。
  5. Reference类中的ReferenceHandler线程会扫描到这些状态为pending的Finalizer对象,将这些对象enqueue到Finalizer静态类引用的ReferenceQueue(非Finalizer中的单向链表)当中。
  6. Finalizer线程从ReferenceQueue中逐一弹出Finalizer对象,首先将Finalizer对象从Finalizer的单向链表中删除,解除了Finalizer静态类对Finalizer对象的引用关系,之后调用Finalizer对象引用的自定义对象的finalize方法,Finalizer对象以及自定义对象此时均可被GC回收。
  7. 由于Finalizer线程的低优先级,可能引起旧对象的释放速度无法跟上新对象的创建速度,引起OutOfMemory问题(例子中的GC overhead limit原因是由于新对象创建的代价太低儿就对象回收的代价较高导致CPU用于GC回收的时间比例超过98%)

最后,网易研究院的马进在他的博客中非常详细的阐述了finalize的原理以及因此引发的案例,也非常值得一读。

时间: 2024-07-31 19:26:42

finalize方法与Java GC的相关文章

java ----------finalize方法总结、GC执行finalize的过程

java finalize方法总结.GC执行finalize的过程 分类: Java2013-10-06 16:42 73人阅读 评论(0) 收藏 举报 finalizejavajvm 目录(?)[+] 注:本文的目的并不是鼓励使用finalize方法,而是大致理清其作用.问题以及GC执行finalize的过程. 1. finalize的作用 finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法. finalize()与C

Java GC与finalize方法

1. Object类有一个finalize()方法,所有类都有这个方法. 2.JVM在回收(GC)一个对象时会调用这个对象的finalize()方法. 但是 GC是靠不住的. 3. JVM只有在内存不够用的情况下才会调用GC,调用finalize()方法.如果内存够用,对象不会被GC,finalize()方法不会被调用. 4. 因此程序中应该主动回收资源,而不是在finalize()方法中回收资源.

[java]final关键字、finally关键字与finalize()方法

final关键字: final关键字通常指的是“无法改变的”,使用“无法改变”这样修饰可能出于两个原因:设计或者效率. final可以修饰变量.方法和类. 一.final变量 一个既是static又是final的域只占据一段不能改变的存储空间. 当对对象引用而不是基本类型运用final修饰时,其含义会有一点迷惑.对于基本类型,final使数值恒定不变.而用于对象引用,final使引用恒定不变.一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象.然而,对象自身却是可以修改的,java并未

JVM【第十三回】:【Java对象存活------finalize()方法】

在根搜索算法中不可达的对象,也并非是"非死不可"的,这个时候他们暂时处于"缓刑"阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是对象是否有必要执行finalize()方法.当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为"没有必要执行". 如果这个对象被判定为有必要

Android内存优化1 了解java GC 垃圾回收机制2 GC执行finalize的过程

1. finalize的作用 finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法. finalize()与C++中的析构函数不是对应的.C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性 不建议用finalize方法完成"非内存资源"的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象):② 作为确保某些非内存资源(如Socket.

Java连载57-equals重写、finalize方法、hashCode方法?

一.关于java语言中如何比较两个字符串是否一致 1.不能使用双等号来比较两个字符串是否相等,应该使用equals方法进行比较,如例子 package com.bjpowernode.java_learning; ? public class D57_1_ { public static void main(String[] args){ String s1 = new String("ABC"); String s2 = new String("ABC"); Sy

【C#】GC和析构函数(Finalize 方法)

析构函数: (来自百度百科)析构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数.析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放). C#中的析构函数定义与C++ 类似,~+函数名的方法: 1 public class FinalizeClass 2 { 3 ~FinalizeClass() 4 { 5 //在这里,清理非托管资源 6 } 7 }

Java GC机制和对象Finalize方法的一点总结

GC是什么? 为什么要有GC? GC是垃圾收集的意思(Garbage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的. 有向图垃圾回收机制 .NET的垃圾回收采用引用计数,java的垃圾回收机制采取的是有向图的方式来实现,具体的说,java程序中的每个线程对象就可以看作是一个有向图的起点,有向边从栈中的引用者指向堆中的引用对象.在这个有向图中,如果

JAVA中GC时finalize()方法是不是一定会被执行?

在回答上面问题之前,我们一定要了解JVM在进行垃圾回收时的机制,首先: 一.可达性算法  要知道对象什么时候死亡,我们需要先知道JVM的GC是如何判断对象是可以回收的.JAVA是通过可达性算法来来判断对象是否存活的.这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的. 在JAVA语言中,可以作为GC Roots的对象包括下面几种: * 虚拟机栈(