• 06-JVM对象内存回收机制深度剖析


    上一篇:05-JVM内存分配机制深度剖析

    堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

    1.引用计数法

    给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。

    这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,如下面代码所示:除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。

    public class ReferenceCountingGc {
       Object instance = null;
    
       public static void main(String[] args) {
          ReferenceCountingGc objA = new ReferenceCountingGc();
          ReferenceCountingGc objB = new ReferenceCountingGc();
          objA.instance = objB;
          objB.instance = objA;
          objA = null;
          objB = null;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.可达性分析算法

    将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
    GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等
    在这里插入图片描述

    3.常见引用类型

    java的引用类型一般分为四种:强引用软引用、弱引用、虚引用
    **强引用:**普通的变量引用

    public static User user = new User();
    
    • 1

    **软引用:**将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。软引用可用来实现内存敏感的高速缓存。

    public static SoftReference<User> user = new SoftReference<User>(new User());
    
    • 1

    软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
    1. 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
    2. 如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

    弱引用: 将对象用WeakReference软引用类型的对象包裹,弱引用跟没引用差不多,GC会直接回收掉,很少用

    public static WeakReference<User> user = new WeakReference<User>(new User());
    
    • 1

    虚引用: 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎不用

    4.finalize()方法最终判定对象是否存活

    即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。
    标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。

    1. 第一次标记并进行一次筛选。
      筛选的条件是此对象是否有必要执行finalize()方法。
      当对象没有覆盖finalize方法,对象将直接被回收。
    2. 第二次标记
      如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次机会,如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
      注意:一个对象的finalize()方法只会被执行一次,也就是说通过调用finalize方法自我救命的机会就一次。

    示例代码:

    public class OOMTest {
    
       public static void main(String[] args) {
          List<Object> list = new ArrayList<>();
          int i = 0;
          int j = 0;
          while (true) {
             list.add(new User(i++, UUID.randomUUID().toString()));
             new User(j--, UUID.randomUUID().toString());
          }
       }
    }
    
    
    //User类需要重写finalize方法
    @Override
    protected void finalize() throws Throwable {
        OOMTest.list.add(this);
        System.out.println("关闭资源,userid=" + id + "即将被回收");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    finalize()方法的运行代价高昂, 不确定性大, 无法保证各个对象的调用顺序, 如今已被官方明确声明为不推荐使用的语法。 有些资料描述它适合做“关闭外部资源”之类的清理性工作, 这完全是对finalize()方法用途的一种自我安慰。 finalize()能做的所有工作, 使用try-finally或者其他方式都可以做得更好、更及时, 所以建议大家完全可以忘掉Java语言里面的这个方法。

    5.如何判断一个类是无用的类

    方法区主要回收的是无用的类,那么如何判断一个类是无用的类呢?
    类需要同时满足下面3个条件才能算是 “无用的类” :

    • 该类所有的对象实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
    • 加载该类的 ClassLoader 已经被回收。
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 相关阅读:
    高等数学(第七版)同济大学 总习题五(前10题) 个人解答
    Oracle 服务器迁移的一些经验
    jsp儿童网站系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
    codesys CNC控制程序
    剑指 Offer 51. 数组中的逆序对
    力扣98. 验证二叉搜索树
    案例介绍:信息抽取技术在汽车销售与分销策略中的应用与实践
    Unity 动画系统(Animation,Animator,Timeline)
    git上传报错:Object too large, rejecting the pack
    Spring注解中的@DependsOn是什么意思
  • 原文地址:https://blog.csdn.net/weixin_40461030/article/details/132734446