15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > 《Java 虚拟机原理》5.1 GC垃圾收集及案例分析

《Java 虚拟机原理》5.1 GC垃圾收集及案例分析

时间:2023-06-27 15:45:01 | 来源:网站运营

时间:2023-06-27 15:45:01 来源:网站运营

《Java 虚拟机原理》5.1 GC垃圾收集及案例分析:一、GC什么对象

GC的对象是没有存活的对象,判断没有存活的对象有两种常用方法:引用计数和可达性分析

1.1 java的GCRoots引用对象

在 Java 虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间

a. 虚拟机栈中引用的对象。

b. 方法区中静态属性引用的对象。

c.方法区中常量引用的对象。

d.本地方法中JNI引用的对象。

说明:当前对象到GCRoots中不可达时候,即会满足被垃圾回收的可能。这些对象但不是就非死不可,此时只能宣判它们存在于一种“缓刑”的阶段,要真正的宣告一个对象死亡。至少要经历两次标记:

第一次:对象可达性分析之后,发现没有与GCRoots相连接,此时会被第一次标记并筛选。

第二次:对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,此时会被认定为没必要执行。

1.2 结合GC对象回顾java虚拟机内存

说明:a. 虚拟机栈中引用的对象、b. 方法区中静态属性引用的对象(b.1基本类型数据是存储在运行时常量池)、c.方法区中常量引用的对象,d.本地方法中JNI引用的对象,这些对象都存储在java堆。







图1 GC对象在java虚拟机内存图

二、什么时候GC

2.1 判断没有存活的对象有两种常用方法

如何辨别一个对象的存亡是关键问题。

1.引用计数

每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

优点:实现简单,判定效率高效,被actionscript3和python中广泛应用。

缺点:无法解决对象之间的循环引用问题。







图2 循环引用场景

2.可达性分析

目前 Java 虚拟机的主流垃圾回收器采取的是可达性分析算法。该算法的实质:将一系列 GC Roots 作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。

从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的不可达对象。如下图所示,右侧的对象是到GCRoot时不可达的,可以判定为可回收对象。







图3 可达性分析

思考题:什么是GC Roots?GC Roots与GC对象的关系?

解答:由堆外指向堆内的引用,一般而言,GC Roots 包括(但不限于)下列几种,Java 方法栈桢中的局部变量已加载类的静态变量JNI handles已启动且未停止的 Java 线程。因此,GC Roots是GC对象的引用

可达性分析法的问题:在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。误报使得Java 虚拟机损失该次垃圾回收的机会。漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象,则很有可能会直接导致 Java 虚拟机崩溃。

2.2 触发GC的动作及时机

(1)动作:程序调用System.gc时可以触发。

(2)时机:系统自身来决定GC触发的时机

根据Eden区和From Space区的内存大小来决定,当内存大小不足时,则会启动GC线程并停止应用线程,GC又分为 Minor GC 和 Full GC。

Minor GC触发条件:① 当 Eden 区满时,触发 Minor GC。

② 当 FromSuv 或者 ToSuv 区满时,触发 Minor GC。

Full GC触发条件: ① 调用System.gc时,系统建议执行Full GC,但是不必然执行

② Heap 的老年区空间不足

③ Metaspace 空间不足

④ 通过Minor GC后进入老年代的平均大小大于老年代的可用内存

⑤ 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

三、如何进行GC

GC算法是内存回收的理论方法,而GC垃圾收集器则是是内存回收的具体实现。下面的内容先讲GC常用算法。

3.1 GC算法理论基础

GC算法是内存回收的理论方法。GC常用算法理论有:标记-清除算法标记-压缩算法复制算法分代收集算法。即回收垃圾对象的内存共有三种方式,分别为:会造成内存碎片的清除、性能开销较大的压缩、以及堆使用效率较低的复制。目前主流的JVM(HotSpot)采用的是分代收集算法。

3.1.1 标记清除法

标记清除法是垃圾回收算法的思想基础。标记清除算法将垃圾分为两个阶段:标记阶段和清除阶段。

标记阶段:通过根节点,标记所有从根节点开始的可达对象,未标记过的对象就是未被引用的垃圾对象。

清除阶段:清除所有未被标记的对象。







图4 标记清除算法

3.1.2 复制算法

复制算法是,将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在适用的内存中存活对象复制到未使用的内存块,然后清除使用的内存块中所有的对象。







图5 复制算法

3.1.3 标记压缩算法

标记压缩算法是一种老年代的回收算法。

标记阶段:与标记清除算法一致,对可达对象做一次标记。

清理阶段:为了避免内存碎片产生,将所有的存活对象压缩到内存的一端。







图6 标记压缩算法

四、Java虚拟机的堆划分

Java 虚拟机将堆划分为新生代老年代。其中,新生代又被划分为 Eden 区,以及两个大小相同的 Survivor 区即FromSuvToSuv







当调用new 指令时,java虚拟机在Eden区中划出一块作为存储对象的内存。由于堆空间是线程共享的,因此直接在Eden区是需要进行同步的。new 指令,便可以直接通过指针加法(bump the pointer)来实现,即把指向空余内存位置的指针加上所请求的字节数。

问题1:两个线程同时new Object1对象,则堆如何划分内存?

解答:由于堆内存是线程共享的,同步为两个线程分别划分object1的内存空间,即有2个object1对象。该技术被称为TLABThread Local Allocation Buffer,对应虚拟机参数 -XX:+UseTLAB,默认开启)。

问题2:当 Eden 区的空间耗尽了怎么办?

解答:这个时候Java虚拟机会触发一次Minor GC,来收集新生代的垃圾。存活下来的对象,则会被送到Survivor区

问题3:新生代的两个Survivor 区,即FromSuvToSuv有什么用处?

解答:当Minor GC时,Eden和FromSuv中的存活对象会被复制到ToSuv中,然后交换FromSuv和ToSuv指针,以保证下一次Minor GC时,ToSuv还是空的。满足两种情况之一,可以使对象移动到老年代:1. Minor GC,存活对象从FromSuv复制到ToSuv,其对象的age+1,当超过(默认值)15的时候,转移到老年代;2. 动态对象,如果survivor空间中相同年龄所有的对象大小总和,大于survivor空间的一半,则年级大于或等于该年级的对象就可以直接进入老年代

注意Minor GC只针对新生代进行垃圾回收,所以在枚举 GC Roots 的时候,需要考虑从老年代到新生代的引用。为了避免扫描整个老年代,Java 虚拟机引入卡表(Card Table)的技术,大致地标出可能存在老年代到新生代引用的内存区域

五、GC案例分析

从一个object1分析该对象在分代垃圾回收算法中的回收轨迹

Minor GC是指发生在新生代的GC,因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;Full GC是指发生在老年代的GC,出现Full GC一般会伴随至少一次的Minor GC,其速度一般比Minor GC慢10倍以上。

步骤1:实例化object1,出生于新生代的Eden区域







步骤2Minor GC,object1移动到新生代的Fromsuv区域,object1还存活。







步骤3Minor GC,通过复制算法将object1移动到新生代的ToSuv区域,同时object1的年龄age+1,object1 依然存活;







步骤4Minor GC,在新生代的survivor区域中,与object1同龄的对象并没有达到survivor的一半。因此,通过复制算法将FromSuv和ToSuv 区域进行互换,object1对象被移动到了新生代的ToSuv,object1 依然存活;







步骤5Minor GC,此时survivor中和object1同龄的对象已经达到survivor的一半以上,object1被移动到了老年代区域,object1 依然存活。







满足两种情况之一,都可以使object1对象移动到老年代:

1. Minor GC,存活于survivor 区域的object1对象的age+1,当超过(默认值)15的时候,转移到老年代;注意:minor GC下,步骤2/3/4中的移动/复制全部Tosuv/Fromsuv区域的对象。

2. 动态对象,如果survivor空间中相同年龄所有的对象大小总和,大于survivor空间的一半,则年级大于或等于该年级的对象就可以直接进入老年代

步骤6Full GC会触发stop the world。object1存活一段时间后,此时GC Roots不可达object1,而且此时老年代空间比率已经超过了阈值,触发了Full GC,此时object1被回收。

注意:object1 被回收的必要条件是 object1 不可达(GC Roots),即 object1 的引用是弱引用







以上的步骤采用分代垃圾收集的思想,描述object1对象从存活到死亡的过程。新生代:采用复制算法,老年代:采用标记-清除算法或者标记-整理算法

stop the world是一种简单除暴的方式,即停止其他非垃圾回收线程的工作,直到完成垃圾回收。Java 虚拟机中的 stop the world 是通过安全点(safepoint)机制来实现的。当 Java 虚拟机收到 Stop-the-world 请求,它便会等待所有的线程都到达安全点,才允许请求 stop the world 的线程进行独占的工作。安全点的初始目的并不是让其他线程停下,而是找到一个稳定的执行状态。例如,java执行某个JNI本地方法时,不访问Java对象、调用Java方法、返回至原Java方法,则Java虚拟机的堆栈不会发生改变,所以这段代码可以作为安全点。因此,Java虚拟机在这个安全点,可以同时进行垃圾回收和执行这段代码



关键词:收集,分析,垃圾,虚拟,原理

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭