按线程数分,能够分为串行垃圾回收器和并行垃圾回收器html
按照工做模式分,能够分为并发式垃圾回收器和独占式垃圾回收器java
按碎片处理方式分,可分为压缩式垃圾回收器和非压缩式垃圾回收器git
按工做的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器github
==吞吐量:运行用户代码的时间占总运行时间的比例==web
垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例。面试
==暂停时间:执行垃圾收集时,程序的工做线程被暂停的时间==算法
收集频率:相对于应用程序的执行,收集操做发生的频率。windows
==内存占用: Java堆区所占的内存大小==bash
快速:一个对象从诞生到被回收所经历的时间。服务器
这三者共同构成一个“不可能三角”。三者整体的表现会随着技术进步而愈来愈好。一款优秀的收集器一般最多同时知足其中的两项。
这三项里,暂停时间的重要性日益凸显。由于随着硬件发展,内存占用 多些愈来愈能容忍,硬件性能的提高也有助于下降收集器运行时对应用程序的影响,即提升了吞吐量。而内存的扩大,对延迟反而带来负面效果。
简单来讲,主要抓住两点:
垃圾收集机制是Java的招牌能力,极大地提升了开发效率。这固然也是面试的热点。
那么,Java常见的垃圾收集器有哪些?
有了虚拟机,就必定须要收集垃圾的机制,这就是Garbage Collection, 对应的产品咱们称为Garbage Collector.
查看默认的垃圾收集器
/**
* -XX:+PrintCommandLineFlags
*
* -XX:+UseSerialGC:代表新生代使用Serial GC ,同时老年代使用Serial Old GC
*
* -XX:+UseParNewGC:标明新生代使用ParNew GC
*
* -XX:+UseParallelGC:代表新生代使用Parallel GC
* -XX:+UseParallelOldGC : 代表老年代使用 Parallel Old GC
* 说明:两者能够相互激活
*
* -XX:+UseConcMarkSweepGC:代表老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
*/
public class GCUseTest {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
while(true){
byte[] arr = new byte[100];
list.add(arr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
复制代码
输出
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
复制代码
或命令行
jdk8 使用的是parallel
jdk9 使用的是G1
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
若是说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器则是Serial收集器的多线程版本。
ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。ParNew收集器在年轻代中一样也是采用复制算法、"Stop一 the一World"机制。
ParNew是不少JVM运行在Server模式下新生代的默认垃圾收集器。
对于新生代,回收次数频繁,使用并行方式高效。
对于老年代,回收次数少,使用串行方式节省资源。(CPU并行 须要切换线程,串行能够省去切换线程的资源)
因为ParNew收集器是基于并行回收,那么是否能够判定ParNew收集器的回收效率在任何场景下都会比Serial收集器更高效?| I
由于除Serial外,目前只有ParNew GC能与CMS收集器配合工做
在程序中,开发人员能够经过选项"一XX: +UseParNewGC"手动指定使用.ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。
一XX:ParallelGCThreads 限制线程数量,默认开启和CPU数据相同的线程数。.
CMS整个过程比以前的收集器要复杂,整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、从新标记阶段和并发清除阶段。
尽管CMS收集器采用的是并发回收(非独占式),可是在其初始化标记和再次标记这两个阶段中仍然须要执行“Stop一the一World”机制暂停程序中的工做线程,不过暂停时间并不会太长,所以能够说明目前全部的垃圾收集器都作不到彻底不须要“Stop一the一World”,只是尽量地缩短暂停时间。
因为最耗费时间的并发标记与并发清除阶段都不须要暂停工做,因此总体的回收是低停顿的。
另外,因为在垃圾收集阶段用户线程没有中断,因此在CMS回收过程当中,还应该确保应用程序用户线程有足够的内存可用。所以,CMS收集器不能像其余收集器那样等到老年代几乎彻底被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工做过程当中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存没法知足程序须要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial 0ld收集器来从新进行老年代的垃圾收集,这样停顿时间就很长了。
CMS收集器的垃圾收集算法采用的是标记一清除算法,这意味着每次执行完内存回收后,因为被执行内存回收的无用对象所占用的内存空间极有多是不连续的一些内存块,不可避免地将会产生一些内存碎片。 那么CMS在为新对象分配内存空间时,将没法使用指针碰撞(Bump the Pointer) 技术,而只可以选择空闲列表(Free List) 执行内存分配。
有人会以为既然Mark Sweep会形成内存碎片,那么为何不把算法换成Mark Compact呢?
答案其实很简答,由于当并发清除的时候,用Compact整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“Stop the World”这种场景”下使用
HotSpot有这么多的垃圾回收器,那么若是有人问,Serial GC、 Parallel GC、Concurrent Mark Sweep GC这三个GC有什么不一样呢?
请记住如下口令:
若是你想要最小化地使用内存和并行开销,请选Serial GC;
若是你想要最大化应用程序的吞吐量,请选Parallel GC;
若是你想要最小化GC的中断或停顿时间,请选CMS GC。
既然咱们已经有了前面几个强大的GC,为何还要发布Garbage First (G1)GC?
缘由就在于应用程序所应对的业务愈来愈庞大、复杂,用户愈来愈多,没有GC就不能保证应用程序正常进行,而常常形成STW的GC又跟不上实际的需求,因此才会不断地尝试对GC进行优化。G1 (Garbage一First) 垃圾回收器是在Java7 update4以后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一。
与此同时,为了适应如今不断扩大的内存和不断增长的处理器数量,进一步下降暂停时间(pause time) ,同时兼顾良好的吞吐量。
==官方给G1设定的目标是在延迟可控的状况下得到尽量高的吞吐量,因此才担当起“全功能收集器”的重任与指望==
为何名字叫作Garbage First (G1)呢?
与其余GC收集器相比,G1使用了全新的分区算法,其特色以下所示:
G1的设计原则就是简化JVM性能调优,开发人员只须要简单的三步便可完成调优:
G1中提供了三种垃圾回收模式: YoungGC、 Mixed GC和Full GC, 在不一样的条件下被触发。
使用G1收集器时,它将整个Java堆划分红约2048个大小相同的独立Region块,每一个Region块大小根据堆空间的实际大小而定,总体被控制在1MB到32MB之间,且为2的N次幂,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。能够经过一 XX:G1HeapRegionSize设定。全部的Region大小相同,且在JVM生命周期内不会被改变。
虽然还保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔离的了,它们都是一部分Region (不须要连续)的集合。经过Region的动态分配方式实现逻辑_上的连续。
G1 GC的垃圾回收过程主要包括以下三个环节:
当愈来愈多的对象晋升到老年代oldregion时,为了不堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC, 该算法并非一个0ldGC,除了回收整个Young Region,还会回收一部分的0ldRegion。这里须要注意:是一部分老年代, 而不是所有老年代。能够选择哪些0ldRegion进行收集,从而能够对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并非Fu1l GC。
G1的初衷就是要避免Full GC的出现。可是若是上述方式不能正常工做,G1会中止应用程序的执行(Stop一 The一World),使用单线程的内存回收算法进行垃圾回收,性能会很是差,应用程序停顿时间会很长。
要避免Full GC的发生,一旦发生须要进行调整。何时会发生Full GC呢?好比堆内存过小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc, 这种状况能够经过增大内存解决。
致使G1Full GC的缘由可能有两个:
从Oracle官方透露出来的信息可获知,回收阶段(Evacuation)其实.本也有想过设计成与用户程序一块儿并发执行,但这件事情作起来比较复杂,考虑到G1只是回收一部分Region, 停顿时间是用户可控制的,因此并不迫切去实现,而选择把这个特性放到了G1以后出现的低延迟垃圾收集器(即ZGC)中。另外,还考虑到G1不是仅仅面向低延迟,停顿用户线程可以最大幅度提升垃圾收集效率,为了保证吞吐量因此才选择了彻底暂停用户线程的实现方案。
截止JDK 1.8,一共有7款不一样的垃圾收集器。每一款不一样的垃圾收集器都有不一样的特色,在具体使用的时候,须要根据具体的状况选用不一样的垃圾收集器。
不一样厂商、不一样版本的虚拟机实现差异很大。HotSpot 虚拟机在JDK7/8后全部收集器及组合(连线),以下图:
怎么选择垃圾回收器
经过阅读GC日志,咱们能够了解Java虛拟机内存分配与回收策略。内存分配与垃圾回收的参数列表
[GC (Allocation Failure) 80832K一>19298K(227840K),0.0084018 secs]
[GC (Metadata GC Threshold) 109499K一>21465K (228352K),0.0184066 secs]
[Full GC (Metadata GC Threshold) 21 465K一>16716K (201728K),0.0619261 secs ]
复制代码
GC、Full GC: GC的类型,GC只在新生代上进行,Full GC包括永生代,新生代, 老年代。
Allocation Failure: GC发生的缘由。
80832K一> 19298K:堆在GC前的大小和GC后的大小。
228840k:如今的堆大小。
0.0084018 secs: GC持续的时间。
复制代码
-打开GC日志: 一verbose:gc一 XX: +PrintGCDetaiis
[GC (Allocation Failure) [ PSYoungGen: 70640K一> 10116K(141312K) ] 80541K一>20017K (227328K),0.0172573 secs] [Times: user=0.03 sys=0.00, real=0.02 secs ]
[GC (Metadata GC Threshold) [PSYoungGen:98859K一>8154K(142336K) ] 108760K一>21261K (228352K),
0.0151573 secs] [Times: user=0.00 sys=0.01, real=0.02 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 8154K一>0K(142336K) ] [ParOldGen: 13107K一>16809K(62464K) ] 21261K一>16809K (204800K),[Metaspace: 20599K一>20599K (1067008K) ],0.0639732 secs]
[Times: user=0.14 sys=0.00, real=0.06 secs]
复制代码
GC,Full FC:一样是GC的类型
Allocation Failure: GC缘由
PSYoungGen:使用了Parallel Scavenge并行垃圾收集器的新生代GC先后大小的变化
ParOldGen:使用了Parallel Old并行垃圾收集器的老年代Gc先后大小的变化
Metaspace: 元数据区GC先后大小的变化,JDK1.8中引入了 元数据区以替代永久代
xxx secs : 指Gc花费的时间
Times: user: 指的是垃圾收集器花费的全部CPU时间,sys: 花费在等待系统调用或系统事件的时间, real :GC从开始到结束的时间,包括其余进程占用时间片的实际时间。
复制代码
一verbose:gc 一XX: +PrintGCDetails 一XX:+PrintGCTimeStamps 一 XX: +PrintGCDateStamps
2019一09一24T22:15:24.518+0800:3.287: [GC(Allocation Failure) [ PSYoungGen: 1361 62K一>5113K(136192K) ] 141425K一>17632K (222208K) ,0.0248249 secs] [Times: user=0.05sys=0.00, real=0.03 secs ]
2019一09一24T22:15:25.559+0800:4.329: [ GC(Metadata GC Threshold)[PSYoungGen:97578K一>10068K(274944K) ] 110096K一>22658K (360960K),0.0094071 secs]
[Times: user=0. 00sys=0.00, real=0. 01 secs]
2019一09一24T22:15:25.569+0800:4.338: [Full GC (Metadata GC Threshold)[ PSYoungGen:10068K一>0K(274944K) ] [ ParoldGen: 12590K一>13564K (56320K) ] 22658K一>13564K (331264K) ,
[Metaspace: 20590K一>20590K(1067008K)], 0. 0494875 secs]
[Times: user=0.17 sys=0. 02,real=0.05 secs ]
复制代码
说明:带上了日期和时间
Minor GC
Full GC
/**
* 在jdk7 和 jdk8中分别执行
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
*/
public class GCLogTest1 {
private static final int _1MB = 1024 * 1024;
public static void testAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] agrs) {
testAllocation();
}
}
复制代码
能够用一些工具去分析这些gc日志。
经常使用的日志分析.工具备: GCViewer、GCEasy、GCHisto、GCLogViewer 、Hpjmeter、garbagecat等。
GC仍然处于飞速发展之中,目前的默认选项G1 GC在不断的进行改进,不少咱们原来认为的缺点,例如串行的Full GC、Card Table扫描的低效等,都已经被大幅改进,例如,JDK 10之后,Fu1l GC已是并行运行,在不少场景下,其表现还略优于Parallel GC的并行Full GC实现。
即便是Serial GC,虽然比较古老,可是简单的设计和实现未必就是过期的,它自己的开销,无论是GC相关数据结构的开销,仍是线程的开销,都是很是小的,因此随着云计算的兴起,在Serverless等新的应用场景下,Serial GC找到了新的舞台。
比较不幸的是CMS GC, 由于其算法的理论缺陷等缘由,虽然如今还有很是大的用户群体,但在JDK9中已经被标记为废弃,并在JDK14版本中移除。
Open JDK12 的Shenandoah GC:低停顿时间的GC (实验性)
官网连接
ZGC与Shenandoah目标高度类似,在尽量对吞吐量影响不大的前提下,实如今任意堆内存大小下均可以把垃圾收集的停顿时间限制在十毫秒之内的低延迟。
《深刻理解Java虚拟机》一书中这样定义ZGC: ZGC收集器是一款基于Region内存布局的,(暂时) 不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记一压缩算法的,以低延迟为首要目标的一款垃圾收集器。
ZGC的工做过程能够分为4个阶段:并发标记一并发预备重分配一并发重分配一并发重映射等。
ZGC几乎在全部地方并发执行的,除了初始标记的是sTW的。因此停顿时间.几乎就耗费在初始标记上,这部分的实际时间是很是少的。
测试数据如图:
劣势比较
优点比较
在ZGC的强项停顿时间测试上,它绝不留情的将Parallel、G1拉开了两个数量级的差距。不管平均停顿、958停顿、998停顿、99. 98停顿,仍是最大停顿时间,ZGC 都能绝不费劲控制在10毫秒之内。
JEP 364: ZGC应用在macOS上
JEP 365: ZGC应用在windows上 JDK14以前,ZGC仅Linux才支持
AliGC是阿里巴巴JVM团队基于G1算法,面 向大堆(LargeHeap)应用场景。指定场景下的对比: 固然,其余厂商也提供了各类独具一格的GC实现,例如比较有名的低延迟GC,Zing ( www.infoq.com/articles/az…)
【代码】
github.com/willShuhuan…
【笔记】
JVM_01 简介
JVM_02 类加载子系统
JVM_03 运行时数据区1- [程序计数器+虚拟机栈+本地方法栈]
JVM_04 本地方法接口
JVM_05 运行时数据区2-堆
JVM_06 运行时数据区3-方法区
JVM_07 运行时数据区4-对象的实例化内存布局与访问定位+直接内存
JVM_08 执行引擎(Execution Engine)
JVM_09 字符串常量池StringTable
JVM_10 垃圾回收1-概述+相关算法
JVM_11 垃圾回收2-垃圾回收相关概念
JVM_12 垃圾回收3-垃圾回收器