一 jvm的垃圾
1.1 jvm垃圾的定义
垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
1.2 判断垃圾对象的两种方式*
1.引用计数法:
对于一个对象A,只要有任何一个对象引用了A ,则A 的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A 的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。
2.可达性分析法:
可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
优点:实现简单,执行高效 ,有效的解决循环引用的问题,防止内存泄漏。
1.3 垃圾收集算法
1.3.1 标记-清除算法
标记-清除算法(Mark-Sweep)
标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
缺点:
1、效率比较低:递归与全堆对象遍历两次
2、在进行GC的时候,需要停止整个应用程序,导致用户体验差
3、这种方式清理出来的空闲内存是不连续的,产生内存碎片。
1.3.2 停止-复制算法
停止-复制算法:应用在年轻代,将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
优点:
没有标记和清除过程,实现简单,运行高效,复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点:
此算法的缺点也是很明显的,就是需要两倍的内存空间。
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
特别的:如果系统中的存活对象很多,复制算法不会很理想。因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行
1.3.3 标记-整理算法
标记-压缩(或标记-整理、Mark - Compact)算法
第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后, 清理边界外所有的空间。
二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。
优点:(此算法消除了“标记-清除”和“复制”两个算法的弊端),消除了复制算法当中,内存减半的高额代价
缺点:
从效率上来说,标记-压缩算法要低于复制算法。效率不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。
对于老年代每次都有大量对象存活的区域来说,极为负重。
在HotSpot中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点。
1.3.4 堆空间分代特点
年轻代(Young Gen)
年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代(Tenured Gen)
老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与标记-整理的混合实现。
Mark阶段的开销与存活对象的数量成正比。
Sweep阶段的开销与所管理区域的大小成正相关。
Compact阶段的开销与存活对象的数据成正比。
1.3.5 增量收集算法
增量收集算法:
让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。
缺点:
因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
1.3.6 分区算法
分区算法:
分区算法将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
1.4 OOM解决总体思路
1、要解决OOM异常或heap space的异常,一般的手段是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
2、如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置。
3、如果不存在内存泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx 与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
1.5 STW
Stop-the-World ,简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。
安全点:
程序执行时并非在所有地方都能停顿下来开始 GC,只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点(Safepoint)”。
1.6 强弱软虚引用
强引用:强关系存在,则不回收;可造成内存泄漏。
弱引用:只要gc运行即回收
软引用:内存不足才回收
虚引用:随时可以被回收
二 GC垃圾回收
2.1 GC的分类
2.1.1 Youg GC
当年轻代空间(Eden)不足时,就会触发Minor GC。这里的年轻代装满指的是Eden区满,Survivor满不会引发GC。(每次Minor GC 会清理年轻代的内存)。因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。
Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
2.1.2 Major Gc
也就是在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足,则触发Major GC。Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。如果Major GC 后,内存还不足,就报OOM了。
Major GC的触发条件:当老年代满时会触发MajorGC,通常至少经历过一次Minor GC,再紧接着进行Major GC。
2.1.3 Full GC
Full GC是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。
总结:
Full GC 是一种代价高昂的垃圾回收操作,应尽量避免频繁发生。通过合理调优 JVM 参数和应用代码,可以有效减少 Full GC 的发生,提高应用的性能和稳定性。
如果在进行Full GC之后仍然出现 "OutOfMemoryError"(OOM)报错,说明即使进行了 Full GC 但是老年代的内存仍然不足。
2.2 触发Full GC的条件*
1)当创建一个大对象,Eden区域当中放不下这个大对象,会直接保存在老年代当中,如果老年代空间也不足,就会触发Full GC;
2)当方法区中没有足够的空间,就出触发一次Full GC;
3)通过Minor GC后进入老年代的平均大小大于老年代的可用内存时,也会触发Full GC;
4)调用System.gc时,系统建议执行Full GC,但是不必然执行;
5)在新生代回收内存时,由Eden区和Survivor From区把存活的对象向Survivor To区复制时,对象大小大于Survivor To空间的可用内存,则把该对象转存到老年代(这个过程称为分配担保),且老年代的可用内存小于该对象大小。即老年代无法存放下新年代过度到老年代的对象的时候,便会触发Full GC。
Major GC和Full GC的区别是什么触发条件呢 • Worktile社区
2.3 GC与堆中分代之间关系*
JVM在进行GC时,并非每次都对上面三个内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:
一种是部分收集(Partial GC);一种是整堆收集(Full GC)
部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
新生代收集(Minor GC / Young GC):只是新生代(Eden\S0,S1)的垃圾收集
老年代收集(Major GC / Old GC):只是老年代的垃圾收集。
目前,只有CMS GC会有单独收集老年代的行为。
注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有G1 GC会有这种行为
整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。
总结:
Minor gc 是清理年轻代
Major GC 是清理老年代。
Full GC 是清理整个堆空间—包括年轻代和老年代。
三 GC垃圾回收器分类
3.1 按执行流程分类
串行回收器:串行回收指的是在同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束。
并行收集器:可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用了“Stop-the-world”机制。
并发式垃圾回收器:与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。
独占式垃圾回收器(Stop the world):一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。
串行回收器:Serial、Serial Old
并行回收器:ParNew、Parallel Scavenge、Parallel Old
并发回收器:CMS、G1
3.2 GC收集器之间的组合关系
1.两个收集器间有连线,表明它们可以搭配使用:
Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;
2.其中Serial Old作为CMS出现"Concurrent Mode Failure"失败的后备预案。
3.(红色虚线)由于维护和兼容性测试的成本,在JDK 8时将Serial+CMS、ParNew+Serial Old这两个组合声明为废弃(JEP 173),并在JDK 9中完全取消了这些组合的支持(JEP214),即:移除。
4.(绿色虚线)JDK 14中:弃用Parallel Scavenge和SerialOld GC组合 (JEP 366)
5.(青色虚线)JDK 14中:删除CMS垃圾回收器 (JEP 363)
为什么要有很多收集器,一个不够吗?因为Java的使用场景很多,移动端,服务器等。所以就需要针对不同的场景,提供不同的垃圾收集器,提高垃圾收集的性能。
虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来。没有一种放之四海皆准、任何场景下都适用的完美收集器存在,更加没有万能的收集器。所以我们选择的只是对具体应用最合适的收集器。
3.3 GC收集器分类详解
3.3.1 Serial Gc
Serial GC: 串行回收
优势:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
运行在Client模式下的虚拟机是个不错的选择。
在HotSpot虚拟机中,使用-XX:+UseSerialGC 参数可以指定年轻代和老年代都使用串行收集器。等价于新生代用Serial GC,且老年代用Serial Old GC
3.3.2 ParNew Gc
ParNew: 并行回收
ParNew收集器则是Serial收集器的多线程版本。
Par是Parallel的缩写,New:只能处理的是新生代
对于新生代,回收次数频繁,使用并行方式高效。
对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)
ParNew 收集器运行在多CPU的环境下,由于可以充分利用多CPU、多核心等物理硬件资源优势,可以更快速地完成垃圾收集,提升程序的吞吐量。
但是在单个CPU的环境下,ParNew收集器不比Serial 收集器更高效
在程序中,开发人员可以通过选项"-XX:+UseParNewGC"手动指定使用ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。
-XX:ParallelGCThreads 限制线程数量,默认开启和CPU数据相同的线程数。
3.3.3 Parallel Scavenge Gc
Parallel Scavenge 并行吞吐量:
Parallel Scavenge收集器同样也采用了复制算法、并行回收和”Stop the World”机制。
和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和”Stop-the-World”机制。
在Java8中,默认是此垃圾收集器。
参数配置:下面两个参数,默认开启一个,另一个也会被开启。(互相激活)
参数 | 说明 |
-XX:+UseParallelGC | 手动指定年轻代使用Parallel并行收集器执行内存回收任务 |
-XX:+UseParallelOldGC | 手动指定老年代都是使用并行回收收集器,分别适用于新生代和老年代。默认jdk8是开启的 |
-XX:ParallelGCThreads | 设置年轻代并行收集器的线程数。一般地,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能。 在默认情况下,当CPU 数量小于8个, ParallelGCThreads 的值等于CPU 数量。 当CPU数量大于8个,ParallelGCThreads 的值等于3+[5*CPU_Count]/8] 。 -XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间)。单位是毫秒。 |
-XX:GCTimeRatio | 垃圾收集时间占总时间的比例(= 1 / (N + 1))。用于衡量吞吐量的大小。 取值范围(0,100)。默认值99,也就是垃圾回收时间不超过1%。 与前一个-XX:MaxGCPauseMillis参数有一定矛盾性。暂停时间越长,Radio参数就容易超过设定的比例。 |
-XX:+UseAdaptiveSizePolicy | 设置Parallel Scavenge收集器具有自适应调节策略。 在这种模式下,年轻代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills),让虚拟机自己完成调优工作。 |
注意事项:
为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集器在工作时会调整Java堆大小或者其他一些参数。
对于用户来讲,停顿时间越短体验越好。但是在服务器端,我们注重高并发,整体的吞吐量。所以服务器端适合Parallel,进行控制。
3.3.4 CMS Gc
3.3.4.1 概述
CMS (Concurrent-Mark-Sweep)收集器:低延迟
这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。
目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
CMS的垃圾收集算法采用标记-清除算法,并且也会”Stop-the-world”
3.3.4.2 CMS收集的4个阶段
CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段。
1)初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为“Stop-the-World”机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快。
2)并发标记(Concurrent-Mark)阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
3)重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(比如:由不可达变为可达对象的数据),这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短。
4)并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的
CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。
CMS收集器的垃圾收集算法采用的是标记—清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer)技术,而只能够选择空闲列表(Free List)执行内存分配。
3.3.4.3 CMS收集的优缺点
CMS的优点:并发收集;低延迟
CMS的弊端:
1)会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配大对象的情况下,不得不提前触发Full GC。
2)CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
3)CMS收集器无法处理浮动垃圾。可能出现“Concurrent Mode Failure”失败而导致另一次 Full GC 的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
3.3.4.4 CMS收集的参数功能
参数 | 说明 |
-XX:+UseConcMarkSweepGC | 手动指定使用CMS 收集器执行内存回收任务 开启该参数后会自动将-XX:+UseParNewGC打开。即:ParNew(Young区用)+CMS(Old区用)+Serial Old的组合。 |
-XX:CMSlnitiatingOccupanyFraction | 设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一次CMS 回收。JDK6及以上版本默认值为92% 如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低Full GC 的执行次数。 |
-XX:+UseCMSCompactAtFullCollection | 用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。 |
-XX:CMSFullGCsBeforeCompaction | 设置在执行多少次Full GC后对内存空间进行压缩整理。 |
-XX:ParallelCMSThreads | 设置CMS的线程数量。CMS 默认启动的线程数是(ParallelGCThreads+3)/4,ParallelGCThreads 是年轻代并行收集器的线程数。当CPU 资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。 |
3.3.5 G1 垃圾回收
3.3.5.1 概述
G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。
3.3.5.2 特点
1)并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力,此时用户线程STW。
2)并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况
3)分代收集:
从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代。
和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代
从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。
4)空间整合:
CMS:“标记-清除”算法、内存碎片、若干次GC后进行一次碎片整理,G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当Java堆非常大的时候,G1的优势更加明显。
可预测的停顿时间模型(即:软实时soft real-time)
3.3.5.3 设置参数
参数 | 说明 |
-XX:+UseG1GC | 手动指定使用G1收集器执行内存回收任务。 |
-XX:G1HeapRegionSize | 设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000 |
-XX:MaxGCPauseMillis | 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms |
-XX:ParallelGCThread | 设置STW时GC线程数的值。最多设置为8 |
-XX:ConcGCThreads | 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。 |
-XX:InitiatingHeapOccupancyPercent | 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。 |
3.3.5.4 设置原则
G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:
第一步:开启G1垃圾收集器
第二步:设置堆的最大内存
第三步:设置最大的停顿时间
3.3.5.5 使用场景
面向服务端应用,针对具有大内存、多处理器的机器。(在普通大小的堆里表现并不惊喜)
最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案;
如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;(G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长)。
3.3.6 G1 收集过程
G1 GC的垃圾回收过程主要包括如下三个环节:
年轻代GC (Young GC)
老年代并发标记过程 (Concurrent Marking)
混合回收(Mixed GC)
1)G1回收过程一:年轻代GC
YGC时,首先G1停止应用程序的执行(Stop-The-World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段
2)G1回收过程二:并发标记过程
3)G1回收过程三:混合回收
由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,-XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。
4)G1回收可选的过程四:Full GC
G1会停止应用程序的执行(Stop-The-World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
3.4 GC的评价指标*
1.吞吐量=程序运行时间/程序运行时间+gc内存回收时间
2.暂停时间
现在JVM调优标准:在最大吞吐量优先的情况下,降低停顿时间。
吞吐量 --->吞吐量越大越好!
暂停时间(或响应时间) --> 追求低延迟
3.5 GC总结*
GC的发展阶段:serial =》 parnew =》cms =》 G1
请记住以下口令:
如果你想要最小化地使用内存和并行开销,请选Serial GC;
如果你想要最大化应用程序的吞吐量,请选Parallel GC;
如果你想要最小化GC的中断或停顿时间,请选CMS GC。
-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
使用命令行指令:jinfo –flag 相关垃圾回收器参数 进程ID
3.6 选择垃圾收集器
1)优先调整堆的大小让JVM自适应完成。
2)如果内存小于100M,使用串行收集器
3)如果是单核、单机程序,并且没有停顿时间的要求,串行收集器
4)如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行收集器
5)如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器
官方推荐G1,性能高。现在互联网的项目,基本都是使用G1。
没有最好的收集器,更没有万能的收集器;优永远是针对特定场景、特定需求,不存在一劳永逸的收集器