目录
GC(Garbage Collection 垃圾收集)
如何确定一个对象是垃圾
引用计数法
对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任
何指针对其
引用,它就是垃圾。
弊端:如果AB相互持有引用,导致永远不能被回收。
可达性分析
通过GC Root的对象,开始向下寻找,看某个对象是否可达;
能作为GCroot的:类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量。
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中
JNI
(即一般说的
Native
方法)引用的对象。
垃圾回收算法
标记-清除(Mark-Sweep)
找出内存中需要回收的对象,并且把它们标记出来,
此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时。
缺点:
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程
序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(1)
标记和清除两个过程都比较耗时,效率不高
(2)
会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无
法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法(Copying)
将内存划分为两块相等的区域,每次只使用其中一块,
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次
清除掉。
缺点:空间利用率低
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果
不想浪费
50%
的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都有
100%
存活的极端情况,所以老年代一般不能直接选用这种算法。
标记-整理(Mark-Compact)
让所有存活
的对象都向一端移动,然后直接清理掉端边界以外的内存。
分代回收算法
Young
区:复制算法
(
对象在被分配之后,可能生命周期比较短,
Young
区复制效率比较高
)
Old
区:标记清除或标记整理
(Old
区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理
)
垃圾收集器
serial收集器
Serial
收集器是最基本、发展历史最悠久的收集器,曾经(在
JDK1.3.1
之前)是虚拟机新生代收集的唯
一选择.
它是一种单线程收集器,不仅仅意味着它只会使用一个
CPU
或者一条收集线程去完成垃圾收集工作,更
重要的是其在进行垃圾收集的时候需要暂停其他线程。
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
适用范围:新生代
应用:
Client
模式下的默认新生代收集器
ParNew收集器
可以把这个收集器理解为
Serial
收集器的多线程版本。
优点:在多
CPU
时,比
Serial
效率高。
缺点:收集过程暂停所有应用程序线程,单
CPU
时比
Serial
效率差。
算法:复制算法
适用范围:新生代
应用:运行在
Server
模式下的虚拟机中首选的新生代收集器
Parallel Scavenge收集器
Parallel Scavenge
收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集
器,看上去和
ParNew
一样,但是
Parallel Scanvenge
更关注
系统的吞吐量
。
PS:
吞吐量
=
运行用户代码的时间
/(
运行用户代码的时间
+
垃圾收集时间
)
比如虚拟机总共运行了
100
分钟,垃圾收集时间用了
1
分钟,吞吐量
=(100-1)/100=99%
。
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用
CPU
资源,尽快完成程序
的运算任务。
-XX:MaxGCPauseMillis
控制最大的垃圾收集停顿时间,
-XX:GCTimeRatio
直接设置吞吐量的大小。
Serial Old收集器
Serial Old
收集器是
Serial
收集器的老年代版本,也是一个单线程收集器,不同的是采用
"
标记
-
整理算
法
"
,运行过程和
Serial
收集器一样。
Parallel Old收集器
Parallel Old
收集器是
Parallel Scavenge
收集器的老年代版本,使用多线程和
"
标记
-
整理算法
"
进行垃圾
回收。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,采用的是标记清除算法,整个过程分四步。
(1)
初始标记
->速度很快 标记
GC Roots
能关联到的对象 stop the world
(2)并发标记 进行
GC Roots Tracing
(3)重新标记 修改并发标记因用户程序变动的内容 stop the world
(4)
并发清除
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来
说,
CMS
收集器的内存回收过程是与用户线程一起并发地执行的
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量
G1收集器
G1
收集器在
JDK 7
正式作为商用的收集器。
JDK 7
开始使用,
JDK 8
非常成熟,
JDK 9
默认的垃圾收集器,适用于新老生代。
与前几个收集器相比,
G1
有以下特点:
并行与并发
分代收集(仍然保留了分代的概念)
空间整合(整体上属于
“
标记
-
整理
”
算法,不会导致空间碎片)
可预测的停顿(比
CMS
更先进的地方在于能让使用者明确指定一个长度为
M
毫秒的时间片段内,消耗在垃圾收集
上的时间不得超过
N
毫秒)
G1
收集器会先收集存活对象少的区域,也就是垃圾对象多的区域,这样可以有大量的空间可以释放出来,这就
是
Garbage First
的由来
使用
G1
收集器时,
Java
堆的内存布局与就与其他收集器有很大差别,它将整个
Java
堆划分为多个
大小相等的独立区域(
Region
),虽然还保留有新生代和老年代的概念,但新生代和老年代不再
是物理隔离的了,它们都是一部分
Region
(不需要连续)的集合。
工作过程可以分为如下几步:
初始标记:标记一下
GC Roots
能够关联的对象
,需要暂停用户线程
并发标记:
从
GC Roots
进行可达性分析,找出存活的对象,与用户线程并发
执行
最终标记:修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
筛选回收:
对各个
Region
的回收价值和成本进行排序,根据
用户所期望的
GC
停顿时间制定回收计划
是否使用
G1
收集器?
(
1
)
50%
以上的堆被存活对象占用
(
2
)对象分配和晋升的速度变化非常大
(
3
)垃圾回收时间比较长
垃圾收集器分类
- 串行收集器:serial和serial old 只能有一个垃圾回收线程执行,用户线程暂停。适用于内存比较小的嵌入式设备。
- 并行收集器[吞吐量优先]:parallel Scanvenge、Parallel Old 多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。适用于科学计算、后台处理等若交互场景。
- 并发收集器[停顿时间优先]:CMS 、G1 用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。适用于相对时间有要求的场景,比如Web。
吞吐量和停顿时间
停顿时间->垃圾收集器进行垃圾回收终端应用执行响应的时间
吞吐量->运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适合需要和用户交互的程序,良好的响应速度能提升用户体验;
高吞吐量则可以高效地利用
CPU
时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任
务。
如何选择合适的垃圾收集器
-
优先调整堆的大小让服务器自己来选择
-
如果内存小于 100M ,使用串行收集器
-
如果是单核,并且没有停顿时间要求,使用串行或 JVM 自己选
-
如果允许停顿时间超过 1 秒,选择并行或 JVM 自己选
-
如果响应时间最重要,并且不能超过 1 秒,使用并发收集器 对于 G1 收集
如何开启需要的垃圾收集器
(1)串行:
-XX
:
+UseSerialGC
-XX
:
+UseSerialOldGC
(
2
)并行
(
吞吐量优先
)
:
-XX
:
+UseParallelGC
-XX
:
+UseParallelOldGC
(
3
)并发收集器
(
响应时间优先
)
-XX
:
+UseConcMarkSweepGC
-XX
:
+UseG1GC