如何判断对象是否是垃圾
创始人
2024-03-07 12:02:10
0

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

我们都Java会自动进行内存管理,JVM会进行垃圾回收,哪它是怎么判定哪些是“垃圾”并决定“垃圾”的生死呢?

判断对象是否为“垃圾”

Java有两种算法判断对象是否是垃圾:引用计数算法和可达性分析算法。

引用计数算法

引用计数(Reference Counting)算法就是给对象加一个引用计数器,当对象被引用,计数器加一;当引用失效时,计数器减一;当对象的引用计数器为0,对象就会被视为垃圾。

优点: 简单、判定垃圾效率高。

缺点:

  • 需要额外的空间存储引用计数器
  • 每当一个引用被赋值给另一个引用时,引用计数器就要进行调整,增加了赋值语句时间
  • 会出现循环引用。比如,对象a引用了对象b,同时对象b也引用了对象a,这就导致两个对象之间循环引用。对象a和对象b的引用都不为0,即使这两个对象已经没有其他引用,由于它们的引用计数都大于0,所以它们就没有办法被回收。如果要解决这个问题就要引入额外机制,这样效率又进一步降低了。

循环引用

引用计数算法在当前主流的JVM中已经没有再被使用了。

简单的例子测试一下

public class ReferenceCountingTest {public Object instance = null;// 10M 占用内存,便于分析private byte[] bytes = new byte[10*1024*1024];public static void main(String[] args) {ReferenceCountingTest objectA = new ReferenceCountingTest();ReferenceCountingTest objectB = new ReferenceCountingTest();//互相引用objectA.instance = objectB;objectB.instance = objectA;//切断可达objectA = null;objectB = null;//强制进行垃圾回收System.gc();}
}

ReferenceCountingTest类中有一个10M的byte数组,
objectA.instance = objectBobjectB.instance = objectA导致objectAobjectB互相引用,如果采用引用计数法的话,这两个对象是没法办法进行回收的,并且每个对象占用不少于10M的内存空间。

在VM options 设置参数 -XX:+PrintGC打印GC情况,来看下运行结果是怎样的:

[GC (System.gc())  24381K->1106K(249344K), 0.0009894 secs]
[Full GC (System.gc())  1106K->957K(249344K), 0.0054511 secs]

从结果24381K->1106K可以看到内存从24381K回收到1106K,回收的空间差不多就是objectAobjectB两个对象占用的空间。这也从侧面说明JVM不是采用引用计数算法判定对象是否存活的。

可达性分析算法

可达性分析算法思路是使用一系列根对象(GC Roots)作为起点,从根节点开始向下进行搜索,搜索过的路径称为引用链(Reference Chain),如果某个对象到根节点没有任何引用链相连或者说从根节点到这个对象不可达,则这个对象就被视为“垃圾”。

如上图所示,白色椭圆形Object4、Object5、Object6之间虽然有关联,但是由于没有和GC Roots关联,所以它们被判定为可回收对象。

在Java中有以下7种GC Roots:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。比如:方法入参、局部变量等

  • 方法区中常量引用的对象

  • 方法区中类静态属性引用的对象:Java类的引用类型静态变量

  • 通过JNI调用本地代码(nactive code)产生的JNI引用。包括JNI的局部变量或者全局变量

  • 被系统类加载器加载的类,这些类不会被回收。它们可以以静态字段的方式去持有对象。

  • 所有被同步锁(synchronized 关键)持有的对象

  • 被JVM保留用于特殊目的的对象。哪些对象被保留取决于虚拟机的实现,可能的有:系统类加载器、一些重要的异常类、做为异常类处理的被预分配对象或者一些自定义的类加载器。

以上8种GC Roots中前4个比较重要,在面试中也会经常被问到,后3个了解一下即可。

可达性分析算法是目前在动态语言中使用最广泛的算法,目前JVM判断对象是否是垃圾用的都是这种算法。

垃圾的回收

Finalize方法

对象通过可达性分析算法被判定为可回收对象,也不是说对象一定要被回收,对象可以通过重写finalize()方法获得一次“免死”机会。当发生GC的时候,JVM会判断可回收的对象是否调用过finalize()方法,如果调用过finalize()方法,对象将会被回收;反之,如果没有调用过 finalize()方法,会将要调用finalize()方法的对象 F-Queue的队列之中等待调用,在调用时如果对象重写了finalize()方法,可以在finalize()方法中“托关系想办法”让自己和GC Roots搭上关系进行一次自我拯救,比如把自己(this关键字) 赋值给某个类变量或者对象的成员变量,对象就会从即将回收的列表中移除,这样对象就完成了一次自我拯救。在执行完finalize()方法后,还会再判断一次对象是否可达,如果不可达,自我拯救失败,最后还是要被回收的。

要注意的一点是:对象finalize()方法只会调用一次,如果对象自我拯救成功一次,当第二次再发生GC的时候会忽略调用对象的finalize()方法,最后都要被回收。这就是JVM世界的法则,只给对象一次不成为垃圾的机会,如果再次成为垃圾,不好意思那只能被回收了。所以机会只有一次,要好好抓住。

下面通过例子测试一下对象的自我拯救:

public class FinalizeGC {private static Object instance;public static void main(String[] args) throws InterruptedException {instance = new FinalizeGC();instance = null;//进行第一次垃圾回收System.gc();//休眠1sThread.sleep(1000);if (instance != null) {System.out.println("I'm still alive.");}else {System.out.println("I'm dead.");}instance = null;//进行第二次垃圾回收System.gc();//休眠1sThread.sleep(1000);if (instance != null) {System.out.println("I'm still alive.");}else {System.out.println("I'm dead.");}}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("Override finalize method execute");instance = this;}    
}

运行结果:

Override finalize method execute
I'm still alive.
I'm dead.

从运行结果可以看到对象只被自我拯救一次,第二次自我拯救失败。

让线程休眠Thread.sleep(1000)1s是因为F-Queue的队列中的finalize()方法,会由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们,休眠是为了等待Finalizer线程去执行finalize()方法。

Thread.sleep(1000)注释掉连续执行多次,你可能会看到如下情况:

Override finalize method execute
I'm dead.
I'm dead.

或者

I'm dead.
Override finalize method execute
I'm dead.

出现上面的原因是finalize()方法执行缓慢,对象还没有自我拯救就会回收了。所以finalize()方法最好不要使用,太不可靠了,也不要想着用finalize()方法进行自我拯救,finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及时。

方法区回收

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。回收废弃常量与回收Java堆中的对象非常类似。举个常量池中字面量回收的例子,假如一个字符串“suncodernote”曾经进入常量池中,但是当前系统又没有任何一个字符串对象的值是“suncodernote”,换句话说,已经没有任何字符串对象引用常量池中的“suncodernote”常量,且虚拟机中也没有其他地方引用这个字面量。如果在这时发生内存回收,而且垃圾收集器判断确有必要的话,这个“suncodernote”常量就将会被系统清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似。

判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了,必须同时满足以下的条件(仅仅是可以,不代表必然,因为还有一些参数可以进行控制):

  1. 该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
  2. 加载该类的 ClassLoader 已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法.
  4. 参数控制:-Xnoclassgc参数可以禁用类的垃圾收集(GC),这可以节省一些GC时间,从而缩短应用程序运行期间的中断

总结

本文主要介绍了JVM是通过可达性分析算法来判断对象是否为可回收对象,同时介绍了哪些对象或者是类可作为可达性分析算法的GC Root,最后介绍了对象在Finalize方法中的回收过程和方法区的回收。想学习关于JVM的文章可关注我,会持续更新。

参考书籍:《深入了解JVM虚拟机》

相关内容

热门资讯

植物观察日记 植物观察日记(精选50篇)  忙碌而又充实的一天又过去了,在你心中有什么感想呢?让我们今天做个总结,...
大蒜发芽观察日记 大蒜发芽观察日记(精选26篇)  不知不觉中一天又要结束了,这一天里,大家身边一定有一些有趣的见闻吧...
伴你入梦散文 伴你入梦散文  “亲爱的,吻你30口,当我吻完的时候,如果你没有说话,我也去睡了。”隔着手机,我温柔...
介绍自己的作文 关于介绍自己的作文7篇  在日常学习、工作和生活中,大家对作文都再熟悉不过了吧,借助作文人们可以实现...
王子和公主童话故事 王子和公主童话故事王子和公主童话故事1  从前有个国王深深地爱着一位公主。可惜公主却不能嫁给任何人,...
6岁儿童睡前童话故事 6岁儿童睡前童话故事(通用14篇)  童话故事是指儿童文学的一种体裁,童话中丰富的想象和夸张可以活跃...
假期生活的作文 关于假期生活的作文(精选20篇)  在平时的学习、工作或生活中,大家都写过作文,肯定对各类作文都很熟...
少儿助眠童话故事 少儿助眠童话故事精选【四篇】少儿助眠童话故事精选【四篇】1  有一棵柿子树,已经很老很老了。  又是...
未来的设计师作文 未来的设计师作文  在学习、工作、生活中,大家最不陌生的就是作文了吧,作文是人们以书面形式表情达意的...
感受的作文 有关感受的作文(通用20篇)  在平凡的学习、工作、生活中,大家对作文都再熟悉不过了吧,作文是人们以...
我家乡的端午节作文 我家乡的端午节作文范文200字10篇  在平日的学习、工作和生活里,许多人都写过作文吧,作文是从内部...
我的成长故事作文 我的成长故事作文(21篇)  在日常生活或是工作学习中,大家都不可避免地要接触到作文吧,作文要求篇章...
人性很自私经典散文 人性很自私经典散文  人们说:生命就是一场选择的过程,你选择了顺其自然,你就要学会随遇而安。你选择了...
大蒜的观察日记 大蒜的观察日记(精选10篇)  已到了一天的末尾,心中一定有不少感想,这时候,最关键的日记怎么能落下...
平淡中也有诗作文 平淡中也有诗作文(通用20篇)  在学习、工作或生活中,大家对作文都不陌生吧,借助作文可以宣泄心中的...
婴儿童话故事 婴儿童话故事婴儿童话故事1  小刺猬买礼貌  小刺猬嘟嘟可真没礼貌,渐渐的,大伙儿都不愿意和它玩儿了...
说明文作文 说明文作文(精选28篇)  在平日的学习、工作和生活里,大家都不可避免地要接触到作文吧,作文可分为小...
动物观察日记 关于动物观察日记(通用60篇)  一天即将完结,相信大家这一天里都收获颇丰吧,想必是时候写一篇日记了...
人生感悟散文 人生感悟散文(通用15篇)  散文,汉语词汇。一指文采焕发;二指犹行文;三指文体名。随着时代的发展,...
天天天蓝散文 天天天蓝散文  折下一支桃花夹在书里面,把你的名字写在我心间……  在这个温馨的年夜,伴随着零点的钟...