目录
0垃圾的定义
1.什么是垃圾
2.如何定位垃圾对象
引用计数法:
根可达算法:
什么对象是根对象?
线程变量:
静态变量:
常量池:
JNI指针 :
1.GC 简介
1.1. 引言
1.2. 何为 GC
1.2.1. 手动 GC
1.2.2. 自动 GC
引用计数法
可达性分析
2.GC入门分析
2.1.碎片整理
1)对象创建时,执行写入操作越来越耗时
2)内存分配错误
2.2. 分代设想
2.3. 对象分配
对象内存分配过程
2.4. GC 模式分析
3.GC 算法基础
3.1. 标记可达对象
3.2. 移除不可达对象
标记-清除(Mark-Sweep)
标记-清除-整理(Mark-Sweep-Compact)
标记-复制 (Mark and Copy)
4.GC 算法实现
4.1. GC 算法实现简介
4.2. GC 收集器应用分析
4.2.1. Serial 收集器应用分析
4.2.2. Parallel 收集器应用分析
4.2.3. CMS 收集器应用分析
4.2.4. G1 收集器应用分析
0垃圾的定义
1.什么是垃圾
没有任何引用指向的一个对象或者多个对象
2.如何定位垃圾对象
引用计数法:
对于某个对象而言,只要应用程序中持有对该对象的引用,就说明该对象不是垃圾,否则,如果一个对象都没有引用该对象,认为该对象就是垃圾。
弊端:如果A,B两个对象相互持有对方的引用,而没有其他对象来持有A,B的引用,也就是说这来那个对象本质上是垃圾,但是永远不能被回收。
根可达算法:
通过GC的root对象,开始时乡下寻找,看某个对象是否可达,遍历到的对象,就不是垃圾,否则就是垃圾。
什么对象是根对象?
Java 程序运行时会启动一个线程栈,栈里会有main方法里边的马上调用执行的这些开对象,被称为根对象。从类型上分,主要有 线程栈变量,静态变量,常量池,JNI指针。
是不是根对象,首先得看是不是main栈帧里边的对象。
线程变量:
main线程中用到的线程变量
静态变量:
class load到内存够厚哦,马上会对静态变量赋默认值,初始化,所以main线程中的静态变量算根对象。
常量池:
main方法中用到的其他类变量,这些个对象的创建要去常量池中看他的类结构,所以这些个对象也算一个。
JNI指针 :
main方法 中如果用到C++\C写的本地方法,这些本地方法用到的类或者对象,也是根对象
1.GC 简介
1.1. 引言
在理解 GC 之前,先回顾一下 JVM 体系结构,如下图所示:
1.2. 何为 GC
基于正在使用的对象进行遍历,对存活的对象进行标记,其未标记对象可认为是 垃圾对象,然后基于特定算法进行回收,这个过程称之为
GC
(
Garbage Collection
).
所有的
GC
系统可从如下几个方面进行实现:
1.GC
判断策略(例如引用计数,对象可达性分析)
2.GC
收集算法(标记
-
清除,标记
-
清除
-
整理,标记
-
复制
-
清除)
3.GC
收集器
(
例如
Serial,Parallel,CMS,G1)
为何要学习
GC
呢?
深入理解 GC 的工作机制,可以帮你写出更好的 Java 应用,同时也是进军大规模应用开发 的一个前提。
1.2.1. 手动 GC
手动
GC
即显式地进行内存分配
(allocate)
和内存释放
(free)
。如果忘记释放, 则对应的那块内存不能再次使用。内存一直被占着
, 却不能使用,这种情况就称 为内存泄漏
(memory leak)
。所谓站着茅坑bls。
这是一个使用手动内存管理用
C
编写的简单示例:
int send_request() {
size_t n = read_size();
int *elements = malloc(n * sizeof(int));
if(read_elements(n, elements) < n) {
// elements not freed!
return -1;
}
// …
free(elements)
return 0;
}
手动
GC
时忘记释放内存是相当容易的。这样会直接导致内存泄漏。
1.2.2. 自动 GC
自动
GC
一般是在
JVM
系统内存不足时,由
JVM
系统启动
GC 过程,自动对内存 进行垃圾回收。
引用计数法
其中:
1)
绿色云朵是内存中的根对象,表示程序中正在使用的对象。
2)
蓝色圆圈是内存中的活动对象,其中的数字表示其引用计数。
3)
灰色圆圈是内存中没有活动对象引用的对象,表示非活动对象。
对于引用计数法,有一个很大的缺陷就是循环引用,例如:
其中红色对象实际上是应用程序不使用的垃圾。但由于引用计数的限制,仍然存 在内存泄漏。当然也有一些办法来应对这种情况
,
例如 “弱引用”
(
‘
weak’ references)
或者使用其它的算法来排查循环引用等。
可达性分析
可达性分析就是对可达对象进行标记,不可达对象即认为是垃圾,然后进行清除。
除通常有两个步骤:
1)
标记所有可到达的对象 ,此时内存中所有的对象都会被扫描一遍,标记处存活对象,从而能确定出垃圾。比较耗时
2)
清除不可到达对象占用的内存地址。
此方法解决了循环依赖问题,但存在短时间的线程暂停
,我们一般称这种现象为STW
停顿
(Stop The World pause,
全线暂停
)
。
说明:
JVM
中包含了多种
GC
算法收集器
,
他们虽在实现上略有不同
, 但理论上都采用了以上两个步骤。
2.GC入门分析
2.1.碎片整理
系统
GC
时每次执行清除
(sweeping)
操作, JVM 都必须保证“不可达对象“占 用的内存能被回收然后重用。内存是被回收了,但这有可能会产生大量的内存碎 片
(
类似于磁盘碎片
),
进而引发两个问题
:
1)对象创建时,执行写入操作越来越耗时
因为寻找一块足够大的空闲内存会 变得更加麻烦。
2)内存分配错误
对象创建时
, JVM 需要在连续的内存块中为对象分配内存。如果碎片问题很 严重
,
直至没有空闲片段能存放新创建的对象
,就会发生内存分配错误 (allocation error)
。
为了解决碎片问题,
JVM
在启动
GC
执行垃圾收集的过程中
, 不仅仅是标记和清 除
,
还需要执行 “内存碎片整理”。这个过程会让所有可达对象
(reachable
objects)
进行依次移动
,
进而可以消除
(
或减少
)内存碎片,并为新对象提供更大 并且连续的内存空间。示意图如下
说明:内存整理时会将对象移动到靠近内存地址的起始位置。
2.2. 分代设想
我们知道垃圾收集要停止整个应用程序的运行,那么假如这个收集过程需要的时 间很长,就会对应用程序产生很大性能问题,如何解决这个问题呢?通过实验发 现内存中的对象通常可以将其分为两大类:
1)
存活时间较短
(
这样的对象比较多
)
2)
存活时间较长
(
这样的对象比较少
)
基于对如上问题的分析,科学家提出了分代回收思路,将
VM 中内存分为年轻代 (Young Generation)
和老年代
(Old Generation-老年代有时候也称为年老区 (Tenured)
分代设想将内存拆分为两个可单独清理的区域,允许采用不同的算法来大幅提高
GC 的性能。但这种方法也不是没有问题。例如,在不同分代中的对象可能会互 相引用
,
这样的对象就难以回收。
2.3. 对象分配
JAVA 中堆内存的内存结构如下图所示:
对象内存分配过程
基于此内存架构,对象内存分配过程如下:
1
)编译器通过逃逸分析(
JDK8 已默认开启),确定对象是在栈上分配还是在堆上分配。
2
)如果是在堆上分配,则首先检测是否可在
TLAB
(
Thread Local Allocation Buffer
)
上直接分配。
3
)如果
TLAB
上无法直接分配则在
Eden
加锁区进行分配
(
线程共享区
)
。
4
)如果
Eden
区无法存储对象,则执行
Yong GC
(
Minor Collection
)。
5
)如果
Yong GC
之后
Eden
区仍然不足以存储对象,则直接分配在老年代。
说明:在对象创建时可能会触发
Yong GC
,此
GC
过程的简易原理图分析如下
:
其中:
1)
新生代由
Eden
区和两个幸存区构成
(
假定为
s1,s2), 任意时刻至少有一个 幸存区是空的
(empty)
,用于存放下次
GC
时未被收集的对象。
2)GC
触发时
Eden
区所有
”
可达对象
”
会被复制到一个幸存区,假设为
s1,当幸 存区
s1
无法存储这些对象时会直接复制到老年代。
3)GC
再次触发时
Eden
区和
s1
幸存区中的
”
可达对象
”会被复制到另一个幸存区 s2
,同时清空
eden
区和
s1
幸存区
.
4)GC
再次触发时
Eden
区和
s2
幸存区中的
”
可达对象
”会被复制到另一个幸存区 s1
,同时清空
eden
区和
s2
幸存区
.
依次类推。
5)
当多次
GC 过程完成后,幸存区中的对象存活时间达到了一定阀值(可以用参 数
-XX:+MaxTenuringThreshold
来指定上限,默认
15),会被看成是“年 老”的对象然后直接移动到老年代。
备注:
什么样的对象在栈上分配?
满足:
1.线程私有的小对象
2.没有逃逸: 线程私有,其他线程没有调用该对象
3.支持标量替换:用对象中的普通属性代替整个对象,如类 T中就两个成员变量 int a ;int b ; 那么完全可以用这两个属性代替T这个类;没必要在构建T这个对象;
4.无需调整
什么对应会进行线程本地分配TLAB(Thread local allocation buffer)?
设置TLAB的背景:
因为eden是多线程进程的场所,所以就会存在多线程间的同步问题,效率就会相对降低,为了提高效率,产生了TLAB.
TLAB:
1.每个线程默认可以在 eden中取1%的空间,这个空间是该线程独有的
2.每个线程再分配对象时,优先在该线程独有的这块空间分配,这里不会和其他线程产生争抢,所以提高了效率
3.一般无需调整这块的大小
2.4. GC 模式分析
垃圾收集事件
(Garbage Collection events)
通常分为
:
1) Minor GC
(小型
GC
):年轻代
GC
事件,
(
新对象
)
分配频率越高, Minor GC
的频率就越高
2) Major GC (
大型
GC):
老年代
GC
事件
3) Full GC
(完全
GC
)
:
整个堆的
GC
事件
说明:一般情况下可以将
Major GC
与
Full GC
看成是同一种
GC
。
3.GC 算法基础
3.1. 标记可达对象
现在的
GC
算法,基本都是要从标记
”
可达对象
”
开始(Marking Reachable Objects),这些标记为可达的对象即为存活对象。同时我们可以将查找可达对象
时的起始位置对象,认为是根对象(
Garbage Collection Roots
)。
基于根对象标记可访问或可达对象,对于不可达对象,
GC
会认为是垃圾对象。这个过程要遍历所有的对象,比较耗时。
例如:在下面图示 中的绿色云朵为根对象,蓝色圆圈为可达对象,灰色圆圈为垃圾对象。
首先,GC 遍历(traverses)内存中整体的对象关系图(object graph)确定根对象,那什么样的对象可作为根对象呢?GC 规范中指出根对象可以是:
1)
栈中变量直接引用的对象
2)
常量池中引用的对象
3) …
其次,确定了根对象以后,进而从根对象开始进行依赖查找,所有可访问到的对 象都认为是存活对象,然后进行标记(
mark
)。
说明:标记可达对象需要暂停所有应用线程
, 以确定对象的引用关系。其暂停的 时间
,
与堆内存大小、对象的总数没有直接关系
,
而是由存活对象
(aliveobjects)
的数量来决定。
3.2. 移除不可达对象
移除不可达对象(Removing Unused Objects)时会因 GC 算法的不同而不同, 但是大部分的 GC 操作一般都可大致分为三类:
标记清除(Mark-Sweep),标记清除整理(Mark-Sweep-Compact),标记复制(Mark-Copy).
标记-清除(Mark-Sweep)
优点:算法相对简单
缺点:
1.算放上要进行扫描两遍,效率偏低。
2.容易产生内存碎片
适应场景:存活对象比较多,即垃圾较少的对象下效率较高,因为第二次遍历(清理垃圾)时,就快一些。比如Eden中垃圾较多,就不适合这种算法。
两次扫描:第一遍找出所有存活的对象,第二遍清理垃圾对象
对于标记清除算法(Mark and Sweep algorithms)应用相对简单,但内存会产生大量的碎片,这样再创建大对象时,假如内存没有足够连续的内存空间可能会 出现
OutOfMemoryError
。
标记-清除-整理(Mark-Sweep-Compact)
标记清除整理算法中在清除垃圾对象以后会移动可用对象,对碎片进行压缩,这 样会在内存中构建相对比较大的连续空间便于大对象的直接存储,但是会增加 GC
暂停时间。
优点:
清理了垃圾,不产生内存碎片,不造成内存的浪费
缺点:
1.算法设计上要进行两次扫描,效率偏低
2.在压缩(整理)的过程中需要调整对象的引用,效率偏低,
适应场景:
对GC停顿时间要求不太严格的场景
标记-复制 (Mark and Copy)
将堆内存划分为相等的两块区域,每次只使用其中的一块,当其中的一块内存使用完了。就将还存活的对象复制到另一块上面,,然后把已经使用过得内存一次性清除掉。
优点:
1.只扫描1次堆内存,提高了效率
2.没有产生内存碎片
缺点:
1.空间浪费严重
2.移动复制对象的过程中需要调整对象的引用
适用场景:
因为它是一次性清除垃圾,所以更适应于 存活对象较少,即垃圾较多的场景,因为一次性就能删掉很多的垃圾,比如Eden的垃圾较多时,就可以使用标记复制算法。
4.GC 算法实现
4.1. GC 算法实现简介
我们知道,
JVM
系统在运行时,因新对象的创建,可能会触发
GC
事件。无论哪
种
GC
都可能会暂停应用程序的执行,但如何将暂停时间降到最小,这要看我们
使用的
GC
算法。现在对于
JVM
中的
GC
算法无非两大类:一类负责收集年轻代,
一类负责收集老年代。假如没有显式指定垃圾回收算法,一般会采用系统平台默
认算法,当然也可以自己指定,例如
JDK8
中基于特定垃圾回收算法的垃圾收集
器应用组合如下:
Young | Tenured | JVM options |
Serial | Serial | -XX:+UseSerialGC |
Parallel Scavenge | Parallel Old | -XX:+UseParallelGC -XX:+UseParallelOldGC |
Parallel New | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
G1 | -XX:+UseG1GC |
其中:
1)
年轻代和老年代的串行收集器: (
Serial GC)
2)
年轻代和老年代的并行收集器:
(Parallel GC)
3)
年轻代的并行收集器 (Parallel New) + 老年代的并发收集器CMS(Concurrent Mark and Sweep)
4)
年轻代和老年代的
G1
收集器
,
负责回收年轻代和老年代
说明:除了以上几种组合方式外,其它的组合方式要么现在已经不支持,要么不推荐。如何对这些组合进行选择,要结合系统的特点。例如系统是追求高吞吐量 还是响应时间,还是两者都要兼顾。总之,对于
GC 组合的选择没有最好,只有 更好。知己知彼,才能百战不殆。结合当前系统的环境配置,性能指标以及 GC 器特点,不断进行
GC
日志分析,定位系统问题,才是一般是选择哪种
GC
的关
键。
4.2. GC 收集器应用分析
4.2.0 垃圾回收器的诞生历史:
JDK的诞生时serial垃圾回收器就跟随而生,它是单线的,能够满足当时几十兆的内存情形。一开始很好用。伴随着JVM对内存的需求越来越大,诞生了多线程的parallel scavenge (ps)和 parallel old (po) 垃圾回收器。一开始的时候内存也就几G,用的挺好。后来内存需求扩展到几十个G,ps,po的stw问题越来越突出。 为了降低STW,诞生了CMS,为了配合CMS的使用,出现了paralle new ;当然CMS时代也就是针对基本上小于32G的内存。
CMS是里程碑式的GC,它开启了并发回收的先河,但是CMS的问题也比较多,因此目前没有一个JDK的默认垃圾回收器是CMS。目前G1逐渐成熟起来,JDK9默认就是G1。
4.2.1. Serial 收集器应用分析
Serial GC
是最古老也是最基本的收集器,但是现在依然广泛使用,JAVA SE5 和
JAVA SE6
中客户端虚拟机采用的默认配置。
Serial GC
(串行收集器)应用特点:
1)
内部只使用一个线程执行垃圾回收
(
不能充分利用
CPU
的多核特性
),无法并行化。
2) GC
时所有
正在执行的用户线程暂停
并且可能会产生较长时间的停顿(Stop the world)
Serial GC(串行收集器)场景应用:
1)
一般可工作在
JVM
的客户端模式。
2)
适用于
CPU 个数或核数较少且内存空间较小(越大可能停顿时间越长)的场景
Serial GC(串行收集器)算法应用
1)
新生代使用
mark-copy(
标记
-
复制
)
算法
(
新生代存活对象较少
)
2)
老年代使用
mark-sweep-compact(
标记
-
清除
-
整理
)
算法
Serial GC(串行收集器)实践应用
其应用参数配置:
-XX:+UseSerialGC
总之,
Serial GC 一个单线程的收集器,在进行垃圾收集时,必须暂停其他所有 的工作线程。适合单
CPU
小应用,实时性要求不是那么高场景。一般在
JVM 的 客户端模式下应用比较好。
备注:
savepoint 用户线程停止的安全点,程序不是说停止就能立刻停止的,比如说对加了锁的对象得unlock后才能停。savepoint 是STW的开始点
4.2.2. Parallel 收集器应用分析
Parallel
收集器为并行收集器,它可利用多个或多核
CPU 优势实现多线程并行
GC
操作,其目标是减少停顿时间,实现更高的吞吐量(
Throughput
)。
Parallel GC
(并行收集器)应用特点:
1)
可利用
CPU
的多核特性执行多线程下的并行化
GC
操作。
2) GC
期间
,
所有
CPU
内核都在并行清理垃圾
,
所以暂停时间较短。
3)
最大优势是可实现可控的吞吐量与停顿时间。
Parallel GC
(并行收集器)场景应用:
1) GC
操作仍需暂停应用程序(也有可能暂停时间比较长,因为
GC 阶段不能被打断),所以不适合要求低延迟的场景。
2)
因其高吞吐
GC(throughput GC)量的特性,适用于后台计算、后台处理的 弱交互场景而不是
web
交互场景
Parallel GC(并行收集器)算法应用:
1)
在年轻代使用标记
-
复制
(mark-copy)
算法,对应的是 Parallel Scavenge 收集器
2)
在老年代使用标记
-
清除
-
整理
(mark-sweep-compact)算法,对应的是 Parallel Old
收集器。
Parallel GC(并行收集器)实践应用:
使 用
Parallel GC
的参数配置 :
-XX:+UseParallelGC
, 默认开启 - XX:+UseParallelOldGC
其它参数配置:
1) -XX:ParallelGCThreads=20:设置并行收集器的线程数,即:同时多少个 线程一起进行垃圾回收。此值最好配置与处理器数目相等。
2) -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果 无法满足此时间,
JVM
会自动调整年轻代大小,以满足此值。
3) -XX:+UseAdaptiveSizePolicy 设置并行收集器自动选择年轻代区大小和 相应的
Survivor 区比例,以达到目标系统规定的最低响应时间或者收集频 率等,此值建议使用并行收集器时,一直打开。
4) -XX:GCTimeRatio=99
,设置吞吐量大小,默认值就是
99,也就是将垃圾回 收的时间设置成了总时间的
1%
。它的值是一个
0-100 之间的整数。假设 GCTimeRatio
的值为
n
,那么系统将花费不超过
1/(1+n) 的时间用于垃 圾收集。
总之,
Parallel GC
是一种并行收集器,可利用多
CPU
优势,执行并行
GC 操 作,吞吐量较高,并可有效降低工作线程暂停时长。但是因为垃圾收集的所有阶
段都不能被打断,所以
Parallel GC 还是有可能导致长时间的应用暂停。所以 Parallel GC 适合于需要高吞吐量而对暂停时间不敏感的场合,比如批处理任 务。
说明:
所谓吞吐量就是
CPU
用于运行用户代码的时间与
CPU 总消耗时间的比值,即吞 吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
4.2.3. CMS 收集器应用分析
CMS产生的原因是无法忍受STW了。
CMS
的官方名称为 “
Mostly
Concurrent Mark and Sweep Garbage
Collector”
,其设计目标是追求更快的响应时间。
CMS (并发收集器)应用特点:
1)
使用空闲列表
(free-lists)管理内存空间的回收,不对老年代进行碎片整理,减少用户线程暂停时间。
2)
在标记
-
清除阶段的大部分工作和用户线程一起并发执行。
3)
最大优点是可减少停顿时间(可提高服务的响应速度)
,最大缺陷是老年代的内存碎片
CMS (并发收集器)场景应用:
1)
应用于多个或多核处理器,目标降低延迟,缩短停顿时间
,
响应时间优先
.
2) CPU
受限场景下,因与用户线程竞争
CPU
,吞吐量会减少。
CMS (并发收集器)算法应用:
1)
年轻代采用并行方式的
mark-copy (
标记
-
复制
)
算法。
2)
老年代主要使用并发
mark-sweep (
标记
-
清除
)
算法。
CMS (并发收集器)关键步骤分析:
1)
初始标记(
initial mark
)此阶段标记一下
GC Roots 能直接关联到的对象,速度很快,产生stw的时间很小。
2) 并发标记(concurrent mark)此阶段就是进行 GC Roots Tracing 的过程,从直接关联对象遍历所有可达对象,然后进行标记。 (IBM以前做过统计,80%的GC时间都用在并发标记上,而CMS的并发设计,让用后线程和标记线程同时执行,客户会感觉慢了点,但是至少程序没有暂停,较好的解决了STW问题)
3)
重新标记(
final remark)此阶段要修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录 (由于并发标记过程中用用线程也不会太多,标记时间也不会用的太久,产生的新的对象和变动了标记的对象也不会太多,重新标记时又是多线程并行标记,所以这个过程很快,产生的stw也比较少)
4)
并发清除(
concurrent sweep
)此阶段与应用程序并发执行
,
不需要
STW 停顿。目的是删除未使用的对象
,
并收回他们占用的空间。
5)
并发重置
(concurrent Reset)
此阶段与应用程序并发执行
,
重置
CMS 算法相关的内部数据,同时
GC
线程切换到用户线程。 (并发清理的过程中产生的垃圾,没办法 ,只能下一次再清理,这些垃圾被称为浮动垃圾)
CMS (并发收集器)实践应用:
使 用
CMS
的 参 数 配 置 :
-XX:+UseConcMarkSweepGC , 默 认 开 启 - XX:+UseParNewGC其它参数配置:
1) -XX:+ UseCMSCompactAtFullCollection
执行
Full GC 后,进行一次碎 片整理;整理过程是独占的,会引起停顿时间变长
2) -XX:+CMSFullGCsBeforeCompaction
设置进行几次
Full GC 后,进行一次碎片整理 3) -XX:ParallelCMSThreads 设定 CMS 的线程数量(一般情况约等于可用CPU 数量)总之,
优点:并发收集,低停顿
缺点:
1.CMS设计的初衷是为了减少STW,但是由于本身算法的限制(标记清除算法),天然的会产生内存碎片,一旦碎片一多,yong区的大对象进入old区时,一旦找不到足够的空间,就会导致promotionFaild(升级失败),这个时候CMS的解决办法就是调用serialOld垃圾回收器来做标记清除整理回收。这个老太太是单线程的也是针对几十M内存设计的回收器。所以面对大内存的CMS 时代,回收效率会特别慢,可能会产生几个小时到1天的STW。
2.并发清理会产生浮动垃圾
解决办法:
可以降低CMS FGC的阈值,默认是92%才FGC,可以调低一些,比如 60%时就进行FGC,这样old区的垃圾就及时清理了。留出来的空间就多了一些,能够很好的容纳Yong区的垃圾了。–XX:CMSInitiatingOccupancyFraction 92%
4.2.4. parNew垃圾器应用分析
parNew垃圾收集器是配合CMS的产生的新生代的垃圾回收器,是parallel scavege 的 一种新版本。
优点:在多核CPU时,比serial 效率高
缺点:收集过程暂停所有的应用线程,单CPU时比serial 效率差一些
算法:标记复制
适用范围:新生代
应用:运行在服务器上
PS 和 PN区别的延伸阅读:
https://docs.oracle.com/en/java/javase/13/gctuning/
ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73
4.2.5. G1 收集器应用分析
G1(Garbage-First )收集器是一种工作于服务端模式的垃圾回收器,主要面向多核,大内存的服务器。
G1
在实现高吞吐的同时,也最大限度满足了
GC 停顿时间可控的目标。在
Oracle JDK7 update 4
后续的版本中已全面支持
G1 回收器功能。
G1
收集器主要为有如下需求的程序设计:
1)
可以像
CMS
收集器一样能同时和应用线程一起并发的执行;
2)
减少整理内存空间时的停顿时间;
3)
要满足可预测的
GC
停顿时间需求;
4)
不能牺牲太多的吞吐性能;
未来
G1
计划要全面取代
CMS
。
G1
相比
CMS
有更多的优势,
G1 是压缩型收集器,可以实现更有效的空间压缩,消除大部分潜在的内存碎片问题。
G1 提供了更精准的可预测的垃圾停顿时间设置,可满足用户在指定垃圾回收时间上的需求。
在
G1
中,堆不再分成连续的年轻代和老年代空间,而是划分为多个
(
通常是 2048 个
)
可以存放对象的小堆区
(smaller heap regions)。每个小堆区都可能是 Eden
区
, Survivor
区或者
Old
区
.
在逻辑上
,
所有的
Eden
区和 Survivor区合起来就是年轻代
,
所有的
Old
区拼在一起那就是老年代,如下图所示:
这样的划分使得
GC
不必每次都去收集整个堆空间
, 而是以增量的方式来处理。GC
时每次只处理一部分小堆区
,
称为此次的回收集
(collection set). GC 事件的每次暂停都会收集所有年轻代的小堆区
, 同时也可能包含一部分老年代小 堆区,如下图所示:
G1 在并发阶段估算每个小堆区存活对象的总数,垃圾最多的小堆区会被优先收集,这也是 G1 名称的由来。
G1
以一种和
CMS
相似的方式执行垃圾回收。
G1 在并发标记阶段估算每个小堆区存活对象的总数,垃圾最多的小堆区会被优先收集,这也是
G1
名称的由来。
顾名思义,
G1 将其收集和压缩活动集中在堆中可能充满可回收对象(即垃圾)的区域上。
G1 通过用停顿预测模型来满足用户自定义的停顿时间目标,它基于设定的停顿时间来选择要回收的
regions
数量。
G1
基于标记,清理对应的
regions
时
,
会将对象从一个或多个
region 里复制 到另一个
region 里,在这个过程中会伴随着压缩和释放内存。清理过程在多核机器上都采用并行执行,来降低停顿时间,增加吞吐量。因此 G1 在持续的运行中能减少碎片,满足用户自定义停顿时间需求。这种能力是以往的回收器所不具 备的(例如
CMS
回收器不能进行碎片压缩,
ParallelOld 只能进行整堆的压 缩,会导致较长的停顿时间)。
再次强调:
G1 不是一个实时的收集器,它只是最大可能的来满足设定的停顿时间。
G1 会基于以往的收集数据,来评估用户指定的停顿时间可以回收多少regions
,需要花费的时间,然后确定停顿时间内可以回收多少个
regions
。
G1
收集器特点:
1)
将
java
堆均分成大小相同的多个区域(
region
,
1M-32M
,最多
2000 个,最大支持堆内存
64G
)。
2)
内存应用具备极大地弹性
(
一个或多个不连续的区域共同组成
eden、 survivor
或
old
区,但大小不再固定。
)
3)
相对
CMS
有着更加可控的暂停时间
(pause time) 和 更大的吞吐量 (throughput)
以及更少的碎片(标记整理)
4)
支持并行与并发,可充分利用多
CPU
,多核优势,降低延迟,提高响应速度。
G1 场景应用分析:
1) FullGC
发生相对比较频繁或消耗的总时长过长
2)
对象分配率或对象升级至老年代的比例波动较大
3)
较长时间的内存整理停顿
说明:如果你现在用
CMS
或者
ParallelOldGC ,并且你的程序运行很好,没有经历长时间垃圾回收停顿,建议就不用迁移。
G1 算法应用分析:
1)
年轻代标记复制算法
2)
老年代标记清除整理算法。
G1 关键步骤应用分析: (Old Generation)
1)
初始标记
(Initial Mark):
属于
Young GC
范畴,是
stop-the-world 活动。对持有老年代对象引用的
Survivor
区(
Root
区)进行标记。
2)
根区扫描
(Root Region Scan)
:并发执行,扫描那些对
old 区有引用的 Survivor
区,在
yongGC
发生之前该阶段必须完成。
3)
并发标记
(Concurrent Mark):并发执行,找出整个堆中存活的对象,将空区域标记为
”X”,
此阶段也可能会被
Young GC
中断。
4)
再次标记
(Remark)
:完全完成对
heap
存活对象的标记。采用 snapshot-at-the-beginning (SATB)
算法完成,比
CMS
用的算法更快。
5)
清理
(cleanup)
:并发执行,统计小堆区中所有存活的对象
, 并对小堆区进行排序
,
优先清理垃圾多的小堆区,释放内存。
6)
复制
/
清理
(copy/clean):对小堆区未被清理对象对象进行复制,然后再 清理。
G1 实践应用分析:
其应用参数配置:
-XX:+UseG1GC
表示启用
GC
收集器
其它参数配置:
1) -XX:MaxGCPauseMillis=200 -
设置最大
GC
停顿时间(GC pause time) 指标
(target).
这是一个软性指标
(soft goal), JVM 会尽力去达成这个 目标
.
所以有时候这个目标并不能达成
.
默认值为
200
毫秒
.
2) -XX:InitiatingHeapOccupancyPercent=45 -
启动并发
GC 时的堆内存 占用百分比
. G1
用它来触发并发
GC
周期
,
基于整个堆的使用率
,而不只是 某一代内存的使用比例。值为
0
则表示“一直执行
GC
循环
)'. 默认值为 45 (
表示堆使用了
45%).
总之:
G1
是
HotSpot
中最先进的准产品级
(production-ready)垃圾收集器。重要的 是
, HotSpot
工程师的主要精力都放在不断改进
G1
上面
,
在新的
java 版本 中
,
将会带来新的功能和优化。
5.常见垃圾回收器组合参数设定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
-
-XX:+UseParNewGC = ParNew + SerialOld
-
-XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old(CMS一旦产生promotionFailed后才使用)
-
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
-
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-
-XX:+UseG1GC = G1
-
Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
-
Linux下1.8版本默认的垃圾回收器到底是什么?
6.JVM常用命令行参数