同步操作将从 flatfish/Java-Review 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
垃圾回收器没有在规范中进行过多的规定,可以由不同厂商、不同版本的JVM来实现
由于JDK的版本在高速迭代,现在已经有了很多版本
从不同角度分析垃圾回收器,可以将GC分为不同的类型
按照线程数分,可以分为串行垃圾回收器和并行垃圾回收器
串行回收指的是在同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束
在诸如但CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发回收器。所以 串行回收默认被应用在客户端的Client模式下的JVM中
在并发能力较强的CPU上,并行回收器产生的停顿时间要短于串行回收器
和串行回收相反,并行收集可以运用多个CPU共同执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用 “Stop the World”机制
按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器
吞吐量:运行用户代码的时间占总运行时间的比例
垃圾收集器开销:吞吐量的补数,垃圾收集所有时间与总运行时间的比例
暂停时间:执行垃圾回收的时候,程序的工作线程被暂停的时间
收集频率:相对于应用程序的执行,收集操作发生的频率
内存占用:Java堆区所占用的内存大小
快速:一个对象从诞生到回收所经历的时间
吞吐量、暂停时间、内存占用 共同构成了一个“不可能三角”
简单来说:吞吐量、暂停时间
jstat -gc 2764 250 20 //2764表示进程id ,250表示250毫秒打印一次 ,20表示一共打印20次
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
7款经典垃圾收集器(在JDK11的ZGC之前)
7款经典垃圾回收器与垃圾分代之间的关系
组合关系
两个收集器间有连线,表明它们可以搭配使用:
Serial/Serial old、Seria1/CMS、ParNew/Serial old、ParNew/CMS、Parallel Scavenge/Serial old、Parallel Scavenge/Parallel old、G1;
其中serial old作为CMS出现"concurrent Mode Failure"失败的后备预案。
(红色虚线)由于维护和兼容性测试的成本,在JDK 8时将serial+CMS、ParNew+Serial old这两个组合声明为废弃(JEP 173),并在JDK 9中完全取消了,这些组合的支持(EP214),即:移除。
(绿色虚线)JDK14中:弃用Parallel Scavenge和Serial0ld Gc组合(JEP336)
(青色虚线)DK 14中:]删除CMS垃圾回收器(EP 363)
为什么要有很多收集器,一个不够吗?闳为Java的使用场景很多,移动端,服务器等。所以就需要针对不同的场景,提供不同的垃圾收集器,提高垃圾收集的性能。
虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来。没有一种放之四海皆准、任何场景下都适用的完美收集器存在,更加没有万能的收集器。所以我们选择的只是对具体应用最合适的收集器。
-XX:InitialHeapSize=266668608 -XX:MaxHeapSize=4266697728 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
这个收集器是一个单线程的收集器,但它的”单线程“的意义并不仅仅说明它 只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候,必须暂停其他所有的工作线程,直到它收集结束(Stop - The - World)
优势:简单而高效(和其他收集器的单线程比),对于限定的单个CPU的环境来说,Serial 收集器由于没有线程相互的开销,专心做垃圾收集自然可以获得最高的单线程的收集效率
在用户的桌面应用场景中,可用内存一般不大(几十MB至一两百MB),可以在较短时间内完成垃圾收集(几十ms至一百多ms),只要不频繁发生,。使用串行回收器是可以接受的。
在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器。
总结
如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器则是serial收集器的多线程版本。
ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。ParNew收集器在年轻代中同样也是采用复制算法、"stop-the-World"机制
ParNew是很多JVM运行在server模式下新生代的默认垃圾收集器。
对于新生代,回收次数频繁,使用并行方式高效
对于老年代,回收次数少,使用串行方式节省资源(CPU并行需要切换线程,串行可以省去切换线程的资源)
ParNew收集器是基于并行回收,就可以判断ParNew收集器的回收效率在任何场景下都会比Serial收集器更高效
因为除了 Serial 外,目前只有ParNew GC 能与CMS收集器配合工作
参数配置:-XX:UseParNewGC 手动设置是否开启 -XX:ParallelGCThreads 限制线程数量,默认开启个CPU数据相同的线程数
HotSpot的年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel scavenge收集器同样也采用了复制算法、并行回收和"stop the World"机制。
那么Parallel收集器的出现是否多此一举?
高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任别主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用,例如:批处理、订单处理、工资支付、科学计算的应用程序
Parallel old收集器,用来代替老年代的serial old收集器。
Parallel old收集器采用了标记-压缩算法,但同样也是基于并行回收和”Stop - the World“机制
在程序吞吐量优先的应用场景中,Parallel 收集器和Parallel Old的结合在Server模式下的内存回收性能很不错
Java8中默认是此收集器
参数设置
在JDK1.5 时期,Hotspot推出了一款在 强交互应用中几乎可认为时代意义的垃圾收集器:CMS (Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验
CMS的垃圾收集算法采用标记-清除算法,并且也会"Stop-the-world"
CMS 不能作为老年代的收集器,却无法与JDK1.4 中已经存在新生代收集器Parallel Scavenge 配合工作,所以采用CMS来收集老年代,新生代只能选择ParNew或者Serial收集器中选择一个
在G1出现之前,CMS使用还是非常广泛的,一直到今天仍然有很多系统使用CMS GC
CMS工作原理
CMS整个过程比之前的收集器都要复杂,整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段
初始标记(Initial-Mark)阶段:在这个阶段,程序中所有的工作线程都会因为”STW“机制而出现短暂的暂停,这个阶段的任务仅仅是为了标记出GC Roots能直接关联的对象 一旦标记完成之后就会恢复之前被暂停的所有应用线程,由于直接关联对象比较小,所以这里 速度非常快
并发标记(Concurrent-Mark)阶段:从GC Roots的 直接关联对象开始遍历整个对象图的过程,这个过程耗时长但是不需要停顿用户线程,可以与垃圾回收线程一起并发执行
重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了 修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象标记记录,这个阶段的停顿稍长,但是也远比并发标记阶段段。
并发清除(Concurrent-Sweep)阶段:清理删除掉标记的已经判断为死亡的对象,释放内存空间 因为不需要移动 存活对象,所以也是并发执行
尽管CMS收集器采用的并发回收(非独占式),但是在其 初始化标记进而再次标记这两个阶段仍需要执行”Stop the world“机制来暂停工作线程。只不过时间很短
由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的
另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CNS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
CMS使用标记清除算法,不可避免会有内存碎片。CMS只能选择空闲列表进行内存分配。无法使用指针碰撞
有人觉得MarkSweep会造成内存碎片,那么为什么不把算法换成Mark Compact?
CMS优点
CMS的弊端
CMS设置的参数
Hotspot有这么多的垃圾回收器,那么如果有人问:Serial GC、Parallel GC、Concurrent Mark Sweep GC 这三个有什么不同呢?
口令
如果你要最小化使用内存和并行开销,请选Serial GC
如果你想要最大化应用程序的吞吐量,请选Parallel GC
如果你想要最小化GC的中断或停顿时间,请选CMS GC
其他:JDK9中CMS标记过时。JDK14全面删除CMS
既然已经有了前面几个强大的GC,为什么还要发布Garbage First (G1)GC?
因为应用程序对应的 业务越来越庞大、复杂、用户越来越多,而实际需求不满足。就迫使新的GC出现
也是为了适应现在 不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量
官方给G1设定的目标式在延迟可控的情况下获得尽可能高的吞吐量,素以才担当起”全能收集器“的重任与期望
为什么名字叫做Garbage First(G1)?
因为G1是一个并行回收器,他把内存分割为很多不相关的区域(Region)(物理上不连续的) 使用不同的Region来表示Eden、幸存者0区、幸存者1区、老年代
G1 GC 有计划的避免Java堆中进行全区域的垃圾扫描。而是跟踪各个Region里面垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First) 。
Gl (Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核cPu及大蜜量内存的机器,以极高概率满足cC停顿时间的同时,还兼具高吞吐量的性能特征。
在JDK1.7版本正式启用,移除了Experimental的标识,是JDK 9以后的默认垃圾回收器,取代了cMS回收器以及Parallel + Parallel old组合。被oracle官方称为**“全功能的垃圾收集器”。**
与此同时,CMS已经在JDK 9中被标记为废弃(deprecated)。在jdk8中还不是默认的垃圾回收器,需要使用-XX:+UseG1Gc来启用。
并行与并发
分代收集
空间整合
可预测的停顿时间模型 (软实时:Soft Real-Time)
-XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务。
-XX:G1HeapRegionsize设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
-XX:MaxGCPauseMillis设置期望达到的最大Gc停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
-XX:ParallelGCThread设置STW工作线程数的值。最多设置为8
-XX:ConcGCThreads设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGcThreads)的1/4左右。
-XX:InitiatingHeapoccupancyPercent 设置触发并发Gc周期的Java堆占用率阈值。超过此值,就触发Gc。默认值是45。
G1回收器的常见操作步骤
G1的设置原则就是简化JVM的性能调优,开发人员只需要简单的三步即可完成调优
第一步:开启G1垃圾收集器
第二步:设置堆的最大内存
第三步:设置最大的停顿时间
G1中提供了三种垃圾回收模式:YoungGC、Mixed GC和Full GC 在不同的条件下被触发
一个region有可能属于Eden,Survivor或者old/Tenured内存区域。但是一个region只可能属于一个角色。图中的E表示该region属于Eden内存区域,s表示属于survivor内存区域,o表示属于old内存区域。图中空白的表示未使用的内存空间
Gl垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。主要用于存储大对象,如果超过1.5个region,就放到H。
设置H的原因
主要包括以下三个环节
顺时针,Young GC -> Young GC + Concurrent mark -> Mixed GC 顺序进行垃圾回收
应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程;G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 Gc暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到survivor区间或者老年区间,也有可能是两个区间都会涉及。
当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。
标记完成马上开始混合回收过程。对于一个混合回收期,G1 Gc从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的
举个例子:一个web服务器,Java进程最大堆内存为4G,每分钟响应1500个请求,每45秒钟会新分配大约2G的内存。G1会每45秒钟进行一次年轻代回收,每31个小时整个堆的使用率会达到45%,会开始老年代并发标记过程,标记完成后开始四到五次的混合回收。
一个对象被不同区域引用的问题
一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,
判断对象存活时,是否需要扫描整个Java堆才能保证准确?
在其他的分代收集器,也存在这样的问题(而G1更突出)
回收新生代也不得不同时扫描老年代?
这样的话会降低Minor Gc的效率;
解决方法
当JVM启动的时候,G1先准备好Eden区,程序在执行的过程中不断创建对象到Eden区,当Eden区空间耗尽的时候,G1会启动一次年轻代垃圾回收过程
年轻代垃圾回收只会回收Eden区和Survivor区
YGC时,首先G1停止应用程序的执行(stop-The-world),G1创建回收集(Collection set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和survivor区所有的内存分段。
第一阶段。扫描根
第二阶段,更新RSet。
第三阶段,处理RSet。
第四阶段,复制对象。
第五阶段,处理引用。
如何选择垃圾回收器
观点
package cn.icanci.jvm.metaspace;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/**
* @Author: icanci
* VM:-XX:+PrintGCDetails -Xloggc:./logs/gc.log
*/
public class BufferTest {
// 20MB
private static final int BUFFER = 1024 * 1024 * 20;
public static void main(String[] args) {
ArrayList<ByteBuffer> list = new ArrayList<>();
int count = 0;
try {
while (true) {
ByteBuffer allocate = ByteBuffer.allocateDirect(BUFFER);
list.add(allocate);
count++;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(count);
}
}
}
Java HotSpot(TM) 64-Bit Server VM (25.131-b11) for windows-amd64 JRE (1.8.0_131-b11), built on Mar 15 2017 01:23:53 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16666788k(8554300k free), swap 19188920k(4455436k free)
CommandLine flags: -XX:InitialHeapSize=266668608 -XX:MaxHeapSize=4266697728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
2.293: [GC (System.gc()) [PSYoungGen: 6560K->1128K(76288K)] 6560K->1136K(251392K), 0.0023961 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2.296: [Full GC (System.gc()) [PSYoungGen: 1128K->0K(76288K)] [ParOldGen: 8K->1017K(175104K)] 1136K->1017K(251392K), [Metaspace: 3993K->3993K(1056768K)], 0.0065845 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 76288K, used 1748K [0x000000076b380000, 0x0000000770880000, 0x00000007c0000000)
eden space 65536K, 2% used [0x000000076b380000,0x000000076b535018,0x000000076f380000)
from space 10752K, 0% used [0x000000076f380000,0x000000076f380000,0x000000076fe00000)
to space 10752K, 0% used [0x000000076fe00000,0x000000076fe00000,0x0000000770880000)
ParOldGen total 175104K, used 1017K [0x00000006c1a00000, 0x00000006cc500000, 0x000000076b380000)
object space 175104K, 0% used [0x00000006c1a00000,0x00000006c1afe470,0x00000006cc500000)
Metaspace used 4025K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 451K, capacity 460K, committed 512K, reserved 1048576K
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。