哪些内存要回收?html
方法区和堆的内存只有在程序运行期间才能直到会建立哪些对象,这部份内存分配和回收都是动态的。这部份内存是垃圾收集器所关注的。
java
程序计数器,虚拟机栈,本地方法栈是线程隔离的,当方法结束或线程结束时,内存就回收了。
算法
何时回收?安全
(1)可达性分析算法中不可达的对象(第一次标记)
数据结构
(2)筛选出有必要执行finalize()方法的对象(此对象没有覆盖finalize()方法,或者finalize()方法已被虚拟机调用过则视为不必执行)
多线程
(3)放置在F-Queue队列中并发
如何回收?ide
垃圾收集器
性能
一. 判断对象须要回收spa
1.可达性分析算法:
当一个对象到GC Root没有任何引用链相连时,则证实此对象是不可用的。
2.引用
强引用>弱引用>软引用>虚引用
3.是否肯定死亡
4.回收方法区
回收两部份内容:废弃常量和无用的类。
废弃常量:没有任何地方引用这个字面量,若是这时发生内存回收且有必要的话,该常量会被系统清理出常量池。
无用的类:
-- 该类全部的实例都已经被回收,java堆中不存在该类的任何实例;
-- 加载该类的ClassLoader已经被回收;
--该类对应的java.lang.Class对象没有在任何地方被引用,没法在任何地方经过反射访问该类的方法。
二. 垃圾收集算法
1.标记-清除算法
标记出全部须要回收的对象,标记完后赞成回收被标记的对象。
2.复制算法(新生代)
将可用内存两等分,当一块用完后,将还存活的对象复制到另外一块中,把已使用的一块清除。
对象存活率较高时要进行较多复制操做,效率低,还须要有额外的空间进行分配担保,故不适合老年代。
3.标记-整理算法(老年代)
标记出全部须要回收的对象,让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。
三. HotSpot算法实现
1.枚举根节点:
在进行可达性分析时必须停顿全部Java执行线程。
在HotSpot中使用一组OopMap的数据结构来得知哪些地方存放着对象引用。
在类加载完成时,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程当中也会在特定的位置记录下栈和寄存器中哪些位置是引用。
在OopMap协助下,HotSpot能够快速且准确地完成GC Root枚举。
2.安全点(SafePoint):
程序执行时只有在到达安全点才能暂停,开始GC。
安全点的选定:程序”是否具备让程序长时间执行的特征“为标准进行选定。
-- 由于每条指令执行的时间都很是短,程序不太可能由于指令流长度太长这个缘由而过长时间运行,“长时间执行”的最明显特性就是指令序列复用,例如方法调用、循环跳转、异常跳转等。
如何在发生GC时让全部线程都跑到最近的安全点上再停顿下来:
-- 抢先式中断(不采用):不须要线程执行代码主动去配合。在GC发生时,首先把全部线程所有中断,若是发现有线程中断的地方不在安全点上,就回复线程,让它跑到安全点上。
-- 主动式中断:当GC须要中断线程时,不直接对线程操做,设置一个标志,各线程执行时主动去轮询这个标志,发现中断标志位真时就本身中断挂起。
轮询标志的地方和安全点是重合的。
Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。
3.安全区域(Safe Region):
在线程处于sleep状态或Blocked状态,程序没法响应JVM的中断请求,走到安全的地方去中断挂起,这种状况须要安全区域来解决。
安全区域:在一段代码片断中,引用关系不会发生变化。在这个区域的任意地方开始GC都是安全的。Safe Region能够当作是被扩展的Safepoint。
当线程执行到Safe Region中的代码,首先标识本身已经进入Safe Region,当在这点时间里JVM发起GC时,就不用管标识本身为Safe Region状态的线程了。
在线程要离开Safe Region时要检查系统是否已经完成了根节点枚举(或是整个GC过程),若是完成线程就继续执行,不然它必须等待直到收到能够安全离开Safe Region的信号为止。
四. 垃圾收集器
垃圾收集器是内存回收的具体实现。
图为HotSpot虚拟机的垃圾收集器:(存在连线说明他们之间能搭配使用)
并行和并发在垃圾收集器中的解释:
并行(Parallel):指多条垃圾收集线程并行工做,但此时用户线程仍然处于等待状态;
并发(Concurrent):指用户线程与垃圾收集器线程同时执行(单不必定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另外一个CPU上。
1.Serial(串行GC)收集器
Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,它不只只会使用一个CPU或者一条收集线程去完成垃圾收集做,并且必须暂停其余全部的工做线程(用户线程),直到它收集完成。
Serial/Serial Old收集器运行示意图(表示Serial和Serial Old搭配使用)
是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来讲,简单高效,Serial收集器因为没有线程交互的开销,专心作垃圾收集天然能够得到最高的单线程收集效率,所以是运行在Client模式下的虚拟机的不错选择(好比桌面应用场景)。
2.ParNew(并行GC)收集器
ParNew收集器其实就是serial收集器的多线程版本,使用复制算法。除了使用多条线程进行垃圾收集以外,其他行为与Serial收集器同样。
ParNew/Serial Old收集器运行示意图(表示ParNew和Serial Old搭配使用)
是运行在Service模式下虚拟机中首选的新生代收集器,其中一个与性能无关的缘由就是除了Serial收集器外,目前只有ParNew收集器能与CMS收集器配合工做。
PreNew收集器在单CPU环境中绝对没有Serial的效果好,因为存在线程交互的开销,该收集器在超线程技术实现的双CPU中都不能必定超过Serial收集器。默认开启的垃圾收集器线程数就是CPU数量,可经过-XX:parallelGCThreads参数来限制收集器线程数。
PreNew收集器是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可使用-XX:+UseParNewGC选项来强制指定它。
3.Parallel Scavenge(吞吐量优先)收集器
Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。parallel Scavenge收集器的特色是它的关注点与其余收集器不一样,CMS等收集器的关注点是尽量地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。
Parallel Scavenge/Parallel Old收集器运行示意图(表示Parallel Scavenge和Parallel Old搭配使用)
短停顿时间适合和用户交互的程序,体验好。高吞吐量适合高效利用CPU,主要用于后台运算不须要太多交互。
提供了两个参数来精确控制吞吐量:1.最大垃圾收集器停顿时间(-XX:MaxGCPauseMillis 大于0的毫秒数,停顿时间小了就要牺牲相应的吞吐量和新生代空间),2.设置吞吐量大小(-XX:GCTimeRatio 大于0小于100的整数,默认99,也就是容许最大1%的垃圾回收时间)。
还有一个参数表示自适应调节策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)。这是一个开关参数,当这个参数打开以后,就不用手动设置新生代大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio)此生老年代对象大小(-XX:PretenureSizeThreshold),会根据当前系统的运行状况手机监控信息,动态调整停顿时间和吞吐量大小。也是其与PreNew收集器的一个重要区别,也是其没法与CMS收集器搭配使用的缘由(CMS收集器尽量地缩短垃圾收集时用户线程的停顿时间,以提高交互体验)。
4.Serial Old(MSC)收集器
Serial Old是Serial收集器的老年代版本,它一样使用一个单线程执行收集,使用“标记-整理”算法。主要使用在Client模式下的虚拟机。
Serial/Serial Old收集器运行示意图(表示Serial和Serial Old搭配使用)
若是在Service模式下使用:1.一种是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,由于那时尚未Parallel Old老年代收集器搭配;2.另外一种就是做为CMS收集器的后备预案,在并发收集发生Concurrent Model Failure时使用。
5.Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法,JDK1.6才提供。
因为以前有一个Parallel Scavenge新生代收集器,,可是却无老年代收集器与之完美结合,只能采用Serial Old老年代收集器,可是因为Serial Old收集器在服务端应用性能上低下(毕竟单线程,多CPU浪费了),其吞吐量反而不必定有PreNew+CMS组合。
Parallel Scavenge/Parallel Old收集器运行示意图(表示Parallel Scavenge和Parallel Old搭配使用)
6.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是HotSpot虚拟机中的一款真正意义上的并发收集器,第一次实现了让垃圾回收线程和用户线程(基本上)同时工做。用CMS收集老年代的时候,新生代只能选择Serial或者ParNew收集器。
CMS收集器是基于“标记-清除”算法实现的,整个收集过程大体分为4个步骤:
①.初始标记(CMS initial mark)
②.并发标记(CMS concurrenr mark)
③.从新标记(CMS remark)
④.并发清除(CMS concurrent sweep)
其中初始标记、从新标记这两个步骤任然须要停顿其余用户线程(Stop The World)。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会断定对象是否存活。而从新标记阶段则是为了修正并发标记期间,因用户程序继续运行而致使标记产生变更的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。
因为整个过程当中耗时最长的并发标记和并发清除过程当中,收集器线程均可以与用户线程一块儿工做,因此总体来讲,CMS收集器的内存回收过程是与用户线程一块儿并发执行的。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是HotSpot虚拟机中的一款真正意义上的并发收集器,第一次实现了让垃圾回收线程和用户线程(基本上)同时工做。用CMS收集老年代的时候,新生代只能选择Serial或者ParNew收集器。
CMS收集器是基于“标记-清除”算法实现的,整个收集过程大体分为4个步骤:
①.初始标记(CMS initial mark)
②.并发标记(CMS concurrenr mark)
③.从新标记(CMS remark)
④.并发清除(CMS concurrent sweep)
其中初始标记、从新标记这两个步骤任然须要停顿其余用户线程(Stop The World)。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会断定对象是否存活。而从新标记阶段则是为了修正并发标记期间,因用户程序继续运行而致使标记产生变更的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。
因为整个过程当中耗时最长的并发标记和并发清除过程当中,收集器线程均可以与用户线程一块儿工做,因此总体来讲,CMS收集器的内存回收过程是与用户线程一块儿并发执行的。
CMS收集器运行示意图
CMS收集器的优势:并发收集、低停顿,可是CMS还远远达不到完美,器主要有三个显著缺点:
1.CMS收集器对CPU资源很是敏感。在并发(并发标记、并发清除)阶段,虽然不会致使用户线程停顿,可是会占用CPU资源而致使应用程序变慢,总吞吐量降低。CMS默认启动的回收线程数是:(CPU数量+3) / 4。收集器线程所占用的CPU数量为:(CPU+3)/4=0.25+3/(4*CPU)。所以这时垃圾收集器始终不会占用少于25%的CPU,所以当进行并发阶段时,虽然用户线程能够跑,可是很缓慢,特别是双核CPU的时候,已经占用了5/8的CPU,吞吐量会很低。为了解决这种状况,产生了“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)。就是采用抢占方式来模拟多任务机制,就是在并发(并发标记、并发清除)阶段,让GC线程、用户线程交替执行,尽可能减小GC线程独占CPU,这样垃圾收集过程更长,可是对用户程序影响小一些。实际上i-CMS效果很通常,目前已经被声明为“deprecated”。
2.CMS收集器没法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而致使另外一次Full GC的产生。因为CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出如今标记过程以后,CMS没法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是因为在垃圾收集阶段用户线程还须要运行,即须要预留足够的内存空间给用户线程使用,所以CMS收集器不能像其余收集器那样等到老年代几乎彻底被填满了再进行收集,须要预留一部份内存空间提供并发收集时的程序运做使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也能够经过参数-XX:CMSInitiatingOccupancyFraction的值来提升触发百分比,以下降内存回收次数提升性能。JDK1.6中,CMS收集器的启动阈值已经提高到92%。要是CMS运行期间预留的内存没法知足程序其余线程须要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来从新进行老年代的垃圾收集,这样停顿时间就很长了。因此说参数-XX:CMSInitiatingOccupancyFraction设置的太高将会很容易致使“Concurrent Mode Failure”失败,性能反而下降。
3.最后一个缺点,CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来不少麻烦,好比说大对象,内存空间找不到连续的空间来分配不得不提早触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC以后增长一个内存碎片的合并整理过程,可是内存整理过程是没法并发的,所以解决了空间碎片问题,却使停顿时间变长。还可经过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC以后,跟着来一次碎片整理过程(默认值是0,表示每次进入Full GC时都进行碎片整理)。
7.G1收集器
G1收集器具体介绍:https://www.cnblogs.com/jing99/p/6072059.html
五.GC日志查看
2018-04-24T09:52:41.903+0800: 20.076: [GC (Metadata GC Threshold) [PSYoungGen: 1208285K->104653K(1273344K)] 1257730K->154106K(4135424K), 0.1108126 secs] [Times: user=0.20 sys=0.02, real=0.11 secs]
20.076::GC发生的时间,这个数字的含义是从Java虚拟机启动以来通过的秒数;
[GC或[Full GC:此次垃圾收集的停顿类型,不是用来区分新生代GC和老年代GC的,若是有Full表明此次GC是发生了Stop-The-World,若是是[Full GC (System)则说明是调用System.gc()方法所触发的收集;
[PSYoungGen:表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的。
区域 | GC收集器 |
[DefNew |
Serial收集器中的新生代名为“Default New Generation” |
[Tenured |
|
[Perm |
|
[ParNew |
Parallel New Generation |
[PSYoungGen |
Parallel Scavenge收集器 |
方括号内的1208285K->104653K(1273344K):GC前该内存区域已使用容量 -> GC后该内存区域已使用容量(该内存区域总容量)
方括号外的1257730K->154106K(4135424K):GC前Java堆已使用容量 -> GC后Java堆已使用容量(Java堆总容量)
0.1108126 secs:表示该内存区域GC所占用的时间,单位是秒。
[Times: user=0.20 sys=0.02, real=0.11 secs]:更具体的时间。用户态消耗的CPU时间、内核态消耗的CPU时间、操做从开始到结束所通过的墙钟时间。
墙钟时间包括各类非运算的等待耗时,但当系统有多CPU或者多核的话,多线程操做会叠加这些CPU时间,因此读者看到user或sys时间超富哦real时间是彻底正常的。
2018-04-24T09:52:42.014+0800: 20.187: [Full GC (Metadata GC Threshold) [PSYoungGen: 104653K->0K(1273344K)] [ParOldGen: 49452K->129000K(2862080K)] 154106K->129000K(4135424K), [Metaspace: 58095K->58034K(1101824K)], 0.2967997 secs] [Times: user=0.88 sys=0.05, real=0.30 secs]
2018-04-24T09:52:45.314+0800: 23.487: [GC (Allocation Failure) [PSYoungGen: 1121792K->68747K(1282560K)] 1250792K->197755K(4144640K), 0.0823790 secs] [Times: user=0.14 sys=0.00, real=0.08 secs]