JVM堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor,其中两个Survivor区的大小一致。
\(堆新生代老年代老年代java堆 = 新生代(Young) + 老年代(Old) = 老年代(Old) + Eden + From Survivor + To Survivor\)
特殊参数说明:
JVM参数 | 默认值 | 说明 |
---|---|---|
–XX:NewRatio | 2 | 老年代(Old):新生代的值(Young),默认为2的情况下:老年代占整个堆的2/3,新生代占1/3 |
–XX:SurvivorRatio | 8 | Eden:一个Survivor区的值,默认为8的情况下:Eden区占整个新生代的8/10,FromSurvivor和ToSurvivor各占1/10 |
新生代GC的触发情况很简单,就是当在新生代Eden或者某个Survivor区创建对象内存不够的时候,就会尝试Minor GC。
GC-Garbage Collection。垃圾回收,也就是回收"垃圾"对象。那么如何判断一个对象是否是垃圾对象呢?
当一个对象不被程序中的任何对象引用的时候,我们可以认为该对象是垃圾对象,可以被收集掉。具体算法包括:
每个对象都有一个引用计数器,当有对象引用它时,计数器+1;当引用失效时,计数器-1;任何时刻计数器为0时就是不可能再被使用的。
下图中左图是对象的引用关系,中图有一个引用失效,右图是清理引用计数器=0的对象后。
缺点:
关于循环引用的对象无法回收的分析参照下图:
根搜索算法的出现,就是为了解决循环引用的时候引用计数法无法判断对象是否可以被回收的问题。
其算法的核心在于,只要对象没有一条到根对象的可达路径,就可以被回收。
那么什么对象才可以认为是根对象呢?
分为两个阶段:
存在的两个问题:
在标记-清除算法上做的简单优化,主要思路是:将可用内存分为相等大小的两份(活动区间和空闲区间),每次只使用其中的一份(活动区间)。在进行GC的时候,首先将活动区间的存活对象依次复制到空闲区间,然后再全量清除活动区间即可。每次GC之后,活动区间和空闲区间就会交换过来。
这种GC算法的优点有:
当然,这种GC算法的缺点也很明显:
针对前两个GC算法的改进。标记的过程和Mark-Sweep算法一致。第二个整理的步骤为:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。
该算法在Mark-Compact算法的基础上进一步做了改进。结合使用了Mark-Sweep和Mark-Compact算法。并不是每次标记清理都会执行压缩,而是多次GC之后,才会执行一次压缩。这种实现,减少了移动对象的成本。
严格的来讲,这个不能叫做一个算法。而只能叫做JVM垃圾收集的一个实现策略。该策略基于以下的弱分代假设:
JVM也根据这个特性,将堆内存分为两部分:新生代和老年代。针对不同的部分采用不同的垃圾收集算法:
GC算法 | 优点 | 缺点 | 存活对象移动 | 内存碎片 | 适用场景 |
---|---|---|---|---|---|
Mark-Sweep | 不需要额外空间 | 两次扫描,耗时严重 | N | Y | 老年代 |
Copying | 没有标记和清除 | 需要额外空间 | Y | N | 新生代 |
Mark-Compact | 没有内存碎片 | 需要移动对象的成本 | Y | N | 老年代 |
前面所讲的堆的布局,堆被分为新生代、老年代。其实就是利用了大部分对象存活时间都较短这个特性。合理的利用不同的垃圾收集算法来处理收集对象。对应各种不同的垃圾收集算法,也就产生了各种GC收集器。具体列表如下:
新生代收集器 | 老年代收集器 |
---|---|
Serial | SerialOld |
ParNew | ParallelOld |
Parallel Scavenge | CMS |
下面就来对各种收集器进行一一说明:
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 采用Copying算法进行垃圾回收。它的特点是 只用一个CPU/一条收集线程去完成GC工作, 且在进行垃圾收集时会发生STW(Stop The World).虽然是单线程收集, 但它省去了多线程上下文切换的开销,简单而高效, 在JVM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。当然缺点也很明显:没法利用目前主流机器的多核多CPU优势。
参数控制:-XX:+UseSerialGC** **串行收集器
ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是JVM启用CMS收集器-XX: +UseConcMarkSweepGC
的默认新生代收集器)。
由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(默认ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads
参数控制GC线程数)。
参数控制:-XX:+UseParNewGC ParNew收集器 -XX:ParallelGCThreads 限制线程数量
与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量
系统吞吐量=运行用户代码时间(运行用户代码时间+垃圾收集时间) 停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务。
相关的参数如下:
Parallel Scavenge参数 | 描述 |
---|---|
-XX:+UseParallelGC | 使用Parallel收集器+ 老年代串行 |
MaxGCPauseMillis | (毫秒数) 收集器将尽力保证内存回收花费的时间不超过设定值, 但如果太小将会导致GC的频率增加. |
GCTimeRatio | (整数:0 < GCTimeRatio < 100) 是垃圾收集时间占总时间的比率 |
-XX:+UseAdaptiveSizePolicy | 启用GC自适应的调节策略: 不再需要手工指定-Xmn、-XX:SurvivorRatio、-XX:PretenureSizeThreshold等细节参数, VM会根据当前系统的运行情况收集性能监控信息, 动态调整这些参数以提供最合适的停顿时间或最大的吞吐量 |
是Serial收集器的老年代版本。同样是单线程收集器,使用Mark-Compact算法。
应用场景:
Parallel Old是Parallel Scavenge收老年代版本, 使用多线程和Mark-Compact算法, 吞吐量优先, 主要与Parallel Scavenge配合在 注重吞吐量 及 CPU资源敏感 系统内使用。
参数控制:-XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
Concurrent Mark Sweep。从全称上就可以看出,CMS收集器是基于Mark-Sweep算法实现的。CMS收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中部署在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的相应速度,希望系统停顿时间最短,以给用户带来较好的体验。
CMS收集器是Sun HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集器线程和用户线程同时工作。CMS收集器的工作机制也比其他收集器更加复杂,具体来说整个过程可以分为以下4个阶段:
由于耗时最长的并发标记和并发清除阶段,垃圾收集线程和用户线程是一起工作的,所以总体来说,减少了STW的时间,缩短了应用的相应时间。但是这样的设计也存在以下不足:
25%
的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低。-XX:CMSInitiatingOccupancyFraction
参数来设置GC的触发百分比(以及-XX:+UseCMSInitiatingOccupancyOnly
来启用该触发百分比), 当老年代的使用空间超过该比例后CMS就会被触发(JDK 1.6之后默认92%). 但当CMS运行期间预留的内存无法满足程序需要, 就会出现上述Promotion Failure等失败, 这时JVM将启动后备预案: 临时启用Serial Old收集器来重新执行Full GC(CMS通常配合大内存使用, 一旦大内存转入串行的Serial GC, 那停顿的时间就是大家都不愿看到的了)。-XX:+UseCMSCompactAtFullCollection
开关参数, 用于在Full GC后再执行一个碎片整理过程. 但内存整理是无法并发的, 内存碎片问题虽然没有了, 但停顿时间也因此变长了, 因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction
用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理)。相关参数设置如下:
参数名称 | 描述 |
---|---|
-XX:+UseConcMarkSweepGC | 使用CMS收集器 |
-XX:+ UseCMSCompactAtFullCollection | Full GC后,进行一次碎片整理;整理过程会STW |
-XX:+CMSFullGCsBeforeCompaction | 设置进行几次Full GC后,进行一次碎片整理 |
-XX:ParallelCMSThreads | 设定CMS的线程数量(一般情况约等于可用CPU数量) |
JDK7引入了G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存.
>- G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS).
>- -XX:+UseG1GC 启用G1收集器.
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:
前面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收. 而是当线程并发寻找可回收的对象时, 有些区块包含可回收的对象要比其他区块多很多. 虽然在清理这些区块时G1仍然需要暂停应用线程, 但可以用相对较少的时间优先回收垃圾较多的Region(这也是G1命名的来源). 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率.
G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.
G1的新生代收集特点如下:
G1老年代垃圾收集的步骤:
[GC pause (young)]
. Or both young and old generation regions which are logged as [GC Pause (mixed)]
.选择”活跃度”最低的区域(这些区域可以最快的完成回收). 拷贝/转移存活的对象到新的尚未使用的regions.
G1收集器的相关特性:
新生代GC策略 | 老年代GC策略 | 说明 |
---|---|---|
Serial | Serial Old | Serial和Serial Old都是单线程进行GC,特点就是GC时暂停所有应用线程。 |
Serial | CMS+Serial Old | CMS(Concurrent Mark Sweep)是并发GC,实现GC线程和应用线程并发工作,不需要暂停所有应用线程。另外,当CMS进行GC失败时,会自动使用Serial Old策略进行GC。 |
ParNew | CMS | 使用-XX:+UseParNewGC选项来开启。ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量。可以使用-XX:ParallelGCThreads选项指定GC的线程数。如果指定了选项-XX:+UseConcMarkSweepGC选项,则新生代默认使用ParNew GC策略。 |
ParNew | Serial Old | 使用-XX:+UseParNewGC选项来开启。新生代使用ParNew GC策略,年老代默认使用Serial Old GC策略。 |
Parallel Scavenge | Serial Old | Parallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间),可见这会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。 |
Parallel Scavenge | Parallel Old | Parallel Old是Serial Old的并行版本 |
G1GC | G1GC | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #开启- XX:MaxGCPauseMillis =50 #暂停时间目标 -XX:GCPauseIntervalMillis =200 #暂停间隔目标 -XX:+G1YoungGenSize=512m #年轻代大小 -XX:SurvivorRatio=6 #幸存区比例 |
在${JAVA_HOME}/bin/目录下Sun/Oracle给我们提供了一些处理应用程序性能问题、定位故障的工具, 包含
工具名称 | 描述 | 功能 |
---|---|---|
jps | 打印Hotspot VM进程 | VMID、JVM参数、main()函数参数、主类名/Jar路径 |
jstat | 查看Hotspot VM 运行时信息 | 类加载、内存、GC[可分代查看]、JIT编译 |
jinfo | 查看和修改虚拟机各项配置 | -flag name=value |
jmap | heapdump: 生成VM堆转储快照、查询finalize执行队列、Java堆和永久代详细信息 | jmap -dump:live,format=b,file=heap.bin [VMID] |
jstack | 查看VM当前时刻的线程快照: 当前VM内每一条线程正在执行的方法堆栈集合 | Thread.getAllStackTraces()提供了类似的功能 |
javap | 查看经javac之后产生的JVM字节码代码 | 自动解析.class文件, 避免了去理解class文件格式以及手动解析class文件内容 |
jcmd | 一个多功能工具, 可以用来导出堆, 查看Java进程、导出线程信息、 执行GC、查看性能相关数据等 | 几乎集合了jps、jstat、jinfo、jmap、jstack所有功能 |
jconsole | 基于JMX的可视化监视、管理工具 | 可以查看内存、线程、类、CPU信息, 以及对JMX MBean进行管理 |
jvisualvm | JDK中最强大运行监视和故障处理工具 | 可以监控内存泄露、跟踪垃圾回收、执行时内存分析、CPU分析、线程分析… |
参数 | 描述 |
---|---|
-Xms | 最小堆大小 |
-Xmx | 最大堆大小 |
-Xmn | 新生代大小 |
-XX:PermSize | 永久代大小 |
-XX:MaxPermSize | 永久代最大大小 |
-XX:+PrintGC | 输出GC日志 |
-verbose:gc | - |
-XX:+PrintGCDetails | 输出GC的详细日志 |
-XX:+PrintGCTimeStamps | 输出GC时间戳(以基准时间的形式) |
-XX:+PrintHeapAtGC | 在进行GC的前后打印出堆的信息 |
-Xloggc:/path/gc.log | 日志文件的输出路径 |
-XX:+PrintGCApplicationStoppedTime | 打印由GC产生的停顿时间 |
http://www.importnew.com/14630.html https://mp.weixin.qq.com/s/HS1VT9ww7XOWqKciUOhwUw http://epy.iteye.com/blog/1914455 https://www.cnblogs.com/duke2016/p/6250766.html https://www.cnblogs.com/ityouknow/p/5614961.html https://www.cnblogs.com/sunfie/p/5125283.html http://zqhxuyuan.github.io/2016/07/26/JVM/ http://www.importnew.com/23035.html http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html#t5 http://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf