文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

java中如何判断对象是否是垃圾

2023-05-18 05:42

关注

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

判断对象是否为“垃圾”

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

引用计数算法

引用计数(Reference Counting)算法就是给对象加一个引用计数器,当对象被引用,计数器加一;当引用失效时,计数器减一;当对象的引用计数器为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:

以上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();
        //休眠1s
        Thread.sleep(1000);
        if (instance != null) {
            System.out.println("I'm still alive.");
        }else {
            System.out.println("I'm dead.");
        }

        
        instance = null;
        //进行第二次垃圾回收
        System.gc();
        //休眠1s
        Thread.sleep(1000);
        if (instance != null) {
            System.out.println("I'm still alive.");
        }else {
            System.out.println("I'm dead.");
        }
    }

    @Override
    protected 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时间,从而缩短应用程序运行期间的中断

到此这篇关于java中如何判断对象是否是垃圾的文章就介绍到这了,更多相关java判断垃圾内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯