JVM之垃圾回收

1.JVM内存模型

在博客JVM之加载class文件-CSDN博客中对运行时数据区进行描述了很多,其实重点存储数据的是堆和方法区(非堆),所以内存的设计也着重从这两方面展开。对于虚拟机栈,本地方法栈,程序计数器都是线程私有的。内存设计的结构图:

思考:堆栈区为何这么设计?

a.假设我们的堆只有一个区,如图所示:我们在java的运行过程中,绝大部分对象都是随着线程的启动而创建,线程结束就销毁了,也就是说大部分的对象的生命周期都是比较短的,即很快就会被垃圾回收回收掉。如果堆只有一个区,那么一方面该堆中每次垃圾回收的成本就很大,并且频次还会很高,另外,当绝大部分垃圾被回收后,就会造成内存碎片,使得大对象没有连续的内存空间进行存放,就会OOM了

b.针对a所说,我们把堆区分为一个更大的老年区,一个较小的young区,设置一个阈值,只有长期不被清理的对象则放入老年区,这样就可以大大减少了每次gc的成本,但是young区仍然会存在内存碎片的问题。

c.针对b所说,young区在发生gc后,仍然会存在空间碎片的问题,那么就再设置一个survive区,也就是young区发生gc后,存活的对象则放入survive区,这样young区的空间碎片就有效的得到了解决,但是如果survive区也发生了gc呢?那么survive区是不是也就出现了空间碎片呢。

d.针对c所说,我们顺着这个思路,将survive区一分为2,每次发生整个young区gc的时候,就把所有的存活对象放入survive区中,当再次发生的时候,就全部放入到另一个survive区中。这样就有效解决了空间碎片的问题。

总结: S0和S1可以发现在任何时候,一定会有一个被保留的空间;age到一定的年龄会存入到old区中;下面有几个经典的问题:1.age到一定的年龄会怎么样? 分配到old区;2.如果S0或者S1空间不够了怎么办?会跟old区借一部分空间用于存储,空间担保机制;3.old区空间也不够了,怎么办?那就触发old区的垃圾回收,full GC;4.old区的垃圾回收也没有足够的空间使用了,怎么办? OOM 5.有一个超大的对象, 如果Eden区分配不下了怎么办?直接分配到老年代。

2.对象在内存中的分配回收流程

3.垃圾收集

通过1.2的描述,我们了解到了堆内存中的内存模型、对象分配的流程以及会对没有用的对象进行垃圾回收,那么JVM是如何确定哪些是垃圾对象呢?JVM又是如何把这些垃圾对象进行回收的呢?

3.1 垃圾对象的确定

a.引用计数法

对于某个对象而言,只要应用程序中持有该对象的引用,就说明了该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。

b.可达性分析

通过GC Root的对象,开始向下寻找,看某个对象是否可达

思考1:为什么要使用可达性分析?因为单靠引用的话,就可能存在obj7和obj8的互相引用的情况,然后没有任何线程使用到这两个对象了,所以它们其实是属于垃圾对象的。

思考2:可达性分析哪些变量能作为GC Root呢?肯定是能够说明这个链条下的对象一定被使用着的这些变量。那么比如说 虚拟机栈中的局部变量表(随着线程的启动就创建开始使用),static成员、常量引用、本地方法栈的变量、Thread、类加载器等

3.2 垃圾收集的基础算法

3.2.1 标记-清除

**标记:**找到内存中需要回收的对象,并且把它们标记出来

清除: 清除掉被标记需要回收的对象,释放出对应的内存空间

缺点:

a. 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不 得不提前触发另一次垃圾收集动作。

b.标记和清除两个过程都比较耗时,效率不高

c. 会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

3.2.2 标记-整理

标记

整理

缺点:

耗时,因为在清除之后,还要进行一个对象的重新整理

3.3.3 标记-复制

将内存分为两块内存区域,一块清除后,存活对象复制到另一块中

标记

复制

缺点:

a. 存在大量的复制操作,效率会降低

b. 空间利用率降低

3.3 垃圾收集算法的选择

young区:复制算法(对象在被分配之后,可能生命周期比较端,young区复制效率比较高)

old区:标记清除或标记整理(old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理)

3.4 垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现

下面介绍几种常见的垃圾回收器

(1)串行:Serial 适合内存比较小的嵌入式设备
(2)并行:Parallel 更加关注吞吐量:适合科学计算、后台处理等若交互场景
(3)并发:CMS、G1 更加关注停顿时间:适合web交互场景

什么是吞吐量?什么是停顿时间?这两个是评价垃圾收集器好坏的重要指标。吞吐量是指 运行用户代码时间/(运行用户代码时间+垃圾收集时间);停顿时间是指垃圾收集器进行垃圾回收终端应用执行响应的时间

3.4.1 Serial

可以用于新老年代

新生代:复制算法

老年代: 标记-整理算法

3.4.2 Parallel

可以用于新老年代

新生代:复制算法

老年代:标记整理算法

3.4.3 CMS(ConcMarkSweepGC)

官网:Concurrent Mark Sweep (CMS) Collector (oracle.com)

可以用于老年代

采用标记-清除算法

回收过程官网: Getting Started with the G1 Garbage Collector (oracle.com)
( 1 ) 初始标记 CMS initial mark 标记 GC Roots 直接关联对象,不用 Tracing ,速度很快
( 2 ) 并发标记 CMS concurrent mark 进行 GC Roots Tracing
( 3 ) 重新标记 CMS remark 修改并发标记因用户程序变动的内容
( 4 ) 并发清除 CMS concurrent sweep 清除不可达对象回收空间,同时有新垃圾产生,留着下次清理称为浮动垃圾

优缺点:
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量

3.4.4 G1(Garbage First)

可以用于新老年代

整体上采用标记-整理算法

回收过程: Getting Started with the G1 Garbage Collector (oracle.com)

( 1 ) 初始标记( Initial Marking ) 标记以下 GC Roots 能够关联的对象,并且修改 TAMS 的值,需要暂停用户线程
( 2 ) 并发标记( Concurrent Marking ) 从 GC Roots 进行可达性分析,找出存活的对象,与用户线程并发执行
( 3 ) 最终标记( Final Marking ) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
( 4 ) 筛选回收( Live Data Counting and Evacuation ) 对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间制定回收
计划

对G1的理解
1 使用 G1 收集器时, Java 堆的内存布局与就与其他收集器有很大差别,它将整个 Java 堆划分为多个大小相等的独立区域( Region ),虽然还保留有
新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分 Region (不需要连续)的集合
2 每个 Region 大小都是一样的,可以是 1 M 到 32 M 之间的数值,但是必须保证是 2 的 n 次幂
3 如果对象太大,一个 Region 放不下 [ 超过 Region 大小的 50 % ] ,那么就会直接放到 H 中
4 设置 Region 大小: ‐ XX : G1HeapRegionSize = M
5 所谓 Garbage ‐ Frist ,其实就是优先回收垃圾最多的 Region 区域

3.4.5 什么时候会发生垃圾收集

GC是由JVM自动完成的,根据JVM系统环境而定的,所以时机是不确定的。当然我们也可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行依次垃圾回收,但是具体什么时候回收由JVM决定。
a. 当Eden区或者S区不够用了: Young GC或Minor GC
b. 老年代空间不够用了: Old GC或Major GC
c. 方法区空间不够用了: Mestaspace GC
d. System.gc()
Full GC =Young GC + Old GC + Metaspace GC

4. JVM垃圾回收总结

java程序中的对象实例、变量等都需要在JVM中进行存储,而对于Java的一个进程来说,每一次查询操作都是一次线程,在若干多个线程中,有许许多多的对象实例存活的生命周期是很短的,JVM作为一个有限的内存空间,如何合理利用,那么就是把这些信息主要存储在方法区和堆栈中,而堆栈中存储的大部分实例对象都是很短的生命周期的,因此,如何有效的清理掉无用的实例对象就是GC所考虑到的问题。两个确定垃圾对象原则(3.1),3个垃圾收集方法论(3.2),常见的5种垃圾收集算法(以吞吐量和停顿时间作为衡量指标)。

相关推荐
爱棋笑谦3 小时前
JVM基础
jvm
懒洋洋大魔王13 小时前
7.Java高级编程 多线程
java·开发语言·jvm
只吹45°风13 小时前
JVM-类加载器的双亲委派模型详解
jvm·类加载器·双亲委派
五味香16 小时前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
longlongqin18 小时前
JVM 虚拟机的编译器、类加载过程、类加载器有哪些?
jvm
niceffking19 小时前
JVM HotSpot 虚拟机: 对象的创建, 内存布局和访问定位
java·jvm
刘大猫.1 天前
Arthas dashboard(当前系统的实时数据面板)
jvm·arthas·dashboard·当前系统的实时数据面板·dashboard命令·arthas命令
longlongqin1 天前
JVM 内存结构?
jvm
Joeysoda1 天前
Java数据结构 时间复杂度和空间复杂度
java·开发语言·jvm·数据结构·学习·算法
18你磊哥1 天前
java重点学习-JVM组成
java·开发语言·jvm