新生代回收器:Serial、ParNew、parallel
老年代回收器:Serial Old、CMS、Parallel Old
新生代和老年代回收器:G1
收集器设置
-XX:+UseSerialGC
: 设置串行收集器
-XX:+UseParallelGC
: 设置并行收集器
-XX:+UseParalledlOldGC
: 设置并行年老代收集器
-XX:+UseConcMarkSweepGC
: 设置并发收集器
-XX:+UseG1GC
:
1. GC垃圾回收器
我们生产环境使用CMS
-
CMS全称 Concurrent Mark Sweep
-
标记-清除算法的垃圾回收器
-
使用场景:GC 过程短暂停,适合对时延要求较高的服务,用户线程不允许长时间的停顿。
-
优点:并行执行,低停顿
-
缺点:
1、不停顿,耗线程,耗内存,整体效率低
2、标记清除法会产生垃圾碎片 容易FullGC
3、会产生浮动垃圾容易FullGC -
实现机制:根据 GC 的触发机制分为:周期性Old GC(被动)和主动Old GC
CMS 和 G1 比较:
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现,在标记清理过程中不会导致用户线程无法定位引用对象。仅作用于老年代收集。它的步骤如下:
- 初始标记(CMS initial mark):独占CPU,stop-the-world, 仅标记GCroots能直接关联的对象,速度比较快;
- 并发标记(CMS concurrent mark):可以和用户线程并发执行,通过GCRoots Tracing 标记所有可达对象;
- 重新标记(CMS remark):独占CPU,stop-the-world, 对并发标记阶段用户线程运行产生的垃圾对象进行标记修正,以及更新逃逸对象;
- 并发清理(CMS concurrent sweep):可以和用户线程并发执行,清理在重复标记中被标记为可回收的对象。
CMS的优点:
- 支持并发收集
- 低停顿,因为CMS可以控制将耗时的两个stop-the-world操作保持与用户线程恰当的时机并发执行,并且能保证在短时间执行完成,这样就达到了近似并发的目的.
CMS的缺点:
- CMS收集器对CPU资源非常敏感,在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分CPU资源,如果在CPU资源不足的情况下应用会有明显的卡顿。
- 无法处理浮动垃圾:在执行"并发清理"步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理是才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’,一旦出现此错误时便会切换到SerialOld收集方式。
- CMS清理后会产生大量的内存碎片,当有不足以提供整块连续的空间给新对象/晋升为老年代对象时又会触发FullGC。且在1.9后将其废除。
使用场景
CMS关注的是垃圾回收最短的停顿时间(低停顿),适用于在老年代并不频繁GC的场景下
G1收集器
G1收集器的内存结构完全区别去CMS,弱化了CMS原有的分代模型(分代可以是不连续的空间),将堆内存划分成一个个Region(1MB~32MB, 默认2048个分区),这么做的目的是在进行收集时不必在全堆范围内进行。它主要特点在于达到可控的停顿时间,用户可以指定收集操作在多长时间内完成,即G1提供了接近实时的收集特性。它的步骤如下:
- 初始标记(Initial Marking):标记一下GC Roots能直接关联到的对象,伴随着一次普通的Young GC发生,并修改NTAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,此阶段是stop-the-world操作。
- 根区间扫描,标记所有幸存者区间的对象引用,扫描 Survivor到老年代的引用,该阶段必须在下一次Young GC 发生前结束。
- 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行,该阶段可以被Young GC中断。
- 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,此阶段是stop-the-world操作,使用snapshot-at-the-beginning (SATB) 算法。
- 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,回收没有存活对象的Region并加入可用Region队列。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
G1的特点
- 并行与并发:G1充分发挥多核性能,使用多CPU来缩短Stop-The-world的时间,
- 分代收集:G1能够自己管理不同分代内已创建对象和新对象的收集。
- 空间整合:G1从整体上来看是基于‘标记-整理’算法实现,从局部(相关的两块Region)上来看是基于‘复制’算法实现,这两种算法都不会产生内存空间碎片。
- 可预测的停顿:它可以自定义停顿时间模型,可以指定一段时间内消耗在垃圾回收商的时间不大于预期设定值。
Remembered Set
我们之前说过,G1在回收每个Region上的垃圾时,每个Region之间又有相互依赖引用关系,想要做到对全部Region进行扫描清理,那么不得不做一次全堆扫描。这样就降低了垃圾回收的效率。所以HotSpot引入了Remembered Set来专门存储于管理对象的引用依赖关系,这样当每次回收时,只需要根据Remembered Set上面的对应关系找到相对的区域进行清理,这样就可以避免扫描整个堆内存又不会遗漏某一个区域。
G1适用场景
G1 GC切分堆内存为多个区间(Region),从而避免很多GC操作在整个Java堆或者整个年轻代进行。G1 GC只关注你有没有存货对象,都会被回收并放入可用的Region队列。G1 GC是基于Region的GC,适用于大内存机器。即使内存很大,Region扫描,性能还是很高的。
CMS 和 G1 的区别
最大的区别是G1出现了Region区块概念,可对回收价值和成本进行排序回收,根据GC期望时间回收,还出现了Remembered set概念,将回收对象放入其中,避免全堆扫描,G1 GC是设计用来取代CMS的,同CMS相比G1有以下优势:
(1)功能上
- 1、可预测的停顿模型
- 2、避免了CMS的大量的垃圾碎片
- 3、超大堆的表现更出色
(2)设计上
-
堆空间上分配的不同:
1 CMS收集器:将堆空间分成Eden、Servivor、old,并且他们是固定大小,JVM启动的时候设定且不能改变。
2 G1收集器:将堆空间分成多个大小相同的Region区域,逻辑上分Eden/Servivor、old,且大小是可变的,每次会根据GC的信息做出调整。 -
压缩策略的不同:
CMS:存在minor GC、full GC,且CMS的youngGC依赖并行GC(ParNew)去完成,只有老年代使用CMS GC去完成。
G1:区分三种策略,minor Gc、mixed GC 、full GC, 而mixed GC会回收整个yong区,回收部分old区。 -
可预测停顿模型:
CMS和G1都是最求最低停顿时间,而CMS可以建立可可预测的停顿时间模型,能让使用者明确指定一段长度为M毫秒的时间片段内,消耗在垃圾回收上的时间不超过N毫秒。
2. JVM 参数配置
(1) 配置1
"-Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties",
"-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager",
"-Duser.timezone=GMT+08",
"-Xms3840m",
"-Xmx3840m",
"-XX:NewSize=1024m",
"-XX:MaxNewSize=1024m",
"-XX:MaxDirectMemorySize=256m",
"-XX:MetaspaceSize=128m",
"-XX:MaxMetaspaceSize=256m",
"-XX:+UseParNewGC",
"-XX:+UseCMSInitiatingOccupancyOnly",
"-XX:+UseGCLogFileRotation",
"-XX:NumberOfGCLogFiles=10",
"-XX:GCLogFileSize=1024M",
"-XX:+ExplicitGCInvokesConcurrent",
"-XX:-UseGCOverheadLimit",
"-XX:+UseConcMarkSweepGC",
"-XX:CMSInitiatingOccupancyFraction=65",
"-XX:CMSFullGCsBeforeCompaction=2",
"-XX:+PrintGCDetails",
"-XX:+PrintGCTimeStamps",
"-XX:+PrintGCDateStamps",
"-javaagent:/usr/local/apm_agent/apm.agent.bootstrap.jar",
"-Xloggc:/data/logs/xxx.xxx_gc.log",
"-Dapm.applicationName=xxx",
"-Dapm.agentId=xxxip-xxxport",
"-Dapm.env=product",
"-Djava.endorsed.dirs=/usr/local/tomcat/endorsed",
"-Dcatalina.base=/usr/local/tomcat",
"-Dcatalina.home=/usr/local/tomcat",
"-Djava.io.tmpdir=/usr/local/tomcat/temp"
(2)配置2
最小堆1G、最大堆内存设为2G,使用G1垃圾回收器,最长的GC暂停时间设为200毫秒,如果时间过长,会相应调整空间的大小(单位是毫秒),新生代最小比例20%,最大比例30%
-Xms1G
-Xmx2G
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+UnlockExperimentalVMOptions
-XX:G1NewSizePercent=20
-XX:G1MaxNewSizePercent=30
-XX:+DisableExplicitGC
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCCause
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=$USER_HOME/java_error.hprof
Xloggc:$USER_HOME/$project_name/online/logs/gc_log.log
可以设置GC日志的路径,%t 会记录当前日期,+UseGCLogFileRotation开启滚动日志,NumberOfGCLogFiles设置文件数量5个,GCLogFileSize滚动文件大小50M
-Xloggc:$USER_HOME/$project_name/logs/gc-log-%t.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=50M
(3)配置3
-Xmx512M
-Xms512M
-Xss256K
-XX:NewRatio=2
-XX:SurvivorRatio=4
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=80
-XX:CMSFullGCsBeforeCompaction=0
-XX:CMSWaitDuration=3000
-XX:+ScavengeBeforeFullGC
-XX:+CMSParallelRemarkEnabled
-XX:+CMSScavengeBeforeRemark
-XX:+UseCMSCompactAtFullCollection
-XX:+CMSClassUnloadingEnabled
-XX:+DisableExplicitGC
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-Xloggc:../logs/gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=../../log/heap_dump.log
-DappName=your-app-name
(4) 配置4
export JAVA_OPTS="-Djava.library.path=/usr/local/lib -server
-Xss256k
-Xms4g
-Xmx4g
-server
-XX:NewRatio=1
-XX:SurvivorRatio=8
-XX:+UseConcMarkSweepGC
-XX:ReservedCodeCacheSize=64m
-XX:TLABWasteTargetPercent=10
-XX:+CMSParallelRemarkEnabled
-XX:+ParallelRefProcEnabled
-XX:+CMSClassUnloadingEnabled
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=4
-XX:MaxDirectMemorySize=256m
-XX:MaxTenuringThreshold=3
-XX:ParGCCardsPerStrideChunk=32768 //不推荐分配内存比较小使用
-XX:+AlwaysPreTouch
"
3. 参数配置说明
//设置初始 Java™ 堆大小, 只要启动,就占用的堆大小。最小大小为 1 MB。
"-Xms3840m",
//-Xmx 选项用于设置最大 Java™ 堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。
"-Xmx3840m",
//栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。
// 当出现大量局部变量,递归时,会发生栈空间OOM(java.lang.StackOverflowError)之类的错误。
"-Xss",
//设置年轻代初始值大小
"-XX:NewSize=1024m",
//设置年轻代最大值
"-XX:MaxNewSize=1024m",
//设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。
"-XX:NewRatio=1"
//年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。
"-XX:SurvivorRatio=8"
//设置持久代大小。java.lang.OutOfMemoryError:PermGenspace这个OOM错误需要合理调大PermSize和MaxPermSize大小。
"-XX:MaxPermSize"
//设置的是年龄阈值,默认15(对象被复制的次数)
"-XX:MaxTenuringThreshold=3"
//当应用抛出OutOfMemoryError时自动生成dump文件。
"-XX:HeapDumpOnOutOfMemoryError"
//导出堆的转储文件路径,也可以指定文件名称,如果不指定文件名,默认为:java_<pid>_<date>_<time>_heapDump.hprof
"-XX:HeapDumpPath"
//OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径
"-XX:OnOutOfMemoryError"
//用来存储已编译方法生成的本地代码缓存
"-XX:ReservedCodeCacheSize=64m"
//TLAB空间所占用Eden空间的百分比大小。虚拟机会为每个Java线程分配一块TLAB空间
"-XX:TLABWasteTargetPercent=10"
//直接内存指定DirectMemory容量,若未指定,则默认与Java堆最大值一样。
//每个进程能使用的内存大小受操作系统的限制,在堆等的内存大小不变时,配置的栈内存越小,能开启的线程数越多。
//用于设置NIO(java.nio) direct-buffer allocations的最大大小,size的单位可以使用k/K、m/M、g/G;如果没有设置该参数则默认值为0,意味着JVM自己自动给NIO direct-buffer allocations选择最大大小;从代码java.base/jdk/internal/misc/VM.java中可以看到默认是取的Runtime.getRuntime().maxMemory()
"-XX:MaxDirectMemorySize=256m",
//【方法区】初始空间大小,达到该值就会触发垃圾收集器进行类型卸载。
// 同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放
// 了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
"-XX:MetaspaceSize=128m",
//【方法区】最大空间,默认没有限制。
"-XX:MaxMetaspaceSize=256m",
//在启动时把所有参数定义的内存全部捋一遍。使用这个参数可能会使启动变慢,但是在后面内存使用过程中会更快。可以保证内存页面连续分配,新生代晋升时不会因为申请内存页面使GC停顿加长。通常只有在内存大于32G的时候才会有感觉。
"-XX:+AlwaysPreTouch"
// 指定创建的对象超过多大会直接进入老年代
// 此参数只能在serial收集器和parnew收集器才有效
"-XX:PretenureSizeThreshold=1M"
/*
如果是布尔类型的选项,它的格式为-XX:+flag或者-XX:-flag,分别表示开启和关闭该选项。
设置年轻代为多线程收集。可与CMS收集同时使用。在serial基础上实现的多线程收集器。
*/
"-XX:+UseParNewGC",
//降低标记停顿
"-XX:+CMSParallelRemarkEnabled"
//默认为false,并行的处理Reference对象,如WeakReference,除非在GC log里出现Reference处理时间较长的日志,否则效果不会很明显。
"-XX:+ParallelRefProcEnabled"
//在CMS中清理永久代中的过期的Class而不等到Full GC,JDK7默认关闭而JDK8打开。看自己情况,比如有没有运行动态语言脚本如Groovy产生大量的临时类。它会增加CMS remark的暂停时间,所以如果新类加载并不频繁,这个参数还是不开的好。
"-XX:+CMSClassUnloadingEnabled"
//与 -XX:+UseCMSInitiatingOccupancyOnly 两个参数需要配合使用,否则第一个参数的75只是一个参考值,JVM会重新计算GC的时间。
"-XX:CMSInitiatingOccupancyFraction=70"
/*
指定HotSpot VM总是使用-XX:CMSInitiatingOccupancyFraction的值作为old的空间使用率限制来启动CMS垃圾回收。
如果没有使用-XX:+UseCMSInitiatingOccupancyOnly,那么HotSpot VM只是利用这个值来启动第一次CMS垃圾回收,
后面都是使用HotSpot VM自动计算出来的值。
*/
"-XX:+UseCMSInitiatingOccupancyOnly",
// GC的线程数
"-XX:ParallelGCThreads=8"
// 并发线程数
"-XX:ConcGCThreads=4"
//启用GC日志文件的自动转储
"-XX:+UseGCLogFileRotation",
//GC日志文件的循环数目
"-XX:NumberOfGCLogFiles=10",
//控制GC日志文件的大小
"-XX:GCLogFileSize=1024M",
//如果在代码里面显式调用System.gc(),那么它还是会执行Full //GC从而导致用户线程被暂停。采用这个选项使得显式触发GC的时候还是使用CMS收集器。
"-XX:+ExplicitGCInvokesConcurrent",
//默认启用。限制GC的运行时间。如果GC耗时过长,就抛OOM。这里是对这个参数进行了关闭。
"-XX:-UseGCOverheadLimit",
//启用CMS低停顿垃圾收集器,减少FGC的暂停时间。
"-XX:+UseConcMarkSweepGC",
/*
指定了CMS垃圾回收时old代的空间占用率该是什么值.
希望old代占用率是65%的时候,启动CMS垃圾回收,你可以设置-XX:CMSInitiatingOccupancyFraction=65.
*/
"-XX:CMSInitiatingOccupancyFraction=65",
//设置多少次full gc后进行内存压缩,由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生"碎片",
//使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理。
"-XX:CMSFullGCsBeforeCompaction=2",
//打印gc详细信息
"-XX:+PrintGCDetails",
//记录的是jvm启动时间为起点的相对时间
"-XX:+PrintGCTimeStamps",
//记录的是系统时间
"-XX:+PrintGCDateStamps",
//在加载java文件之前做拦截做字节码修改
"-javaagent:/usr/local/apm_agent/apm.agent.bootstrap.jar",
//日志文件的输出路径
"-Xloggc:/data/logs/xxx.xxx_gc.log",
//apm的参数
"-Dapm.applicationName=xxx",
"-Dapm.agentId=xxxip-xxxport",
"-Dapm.env=product",
//-D 设置系统参数
"-Djava.endorsed.dirs=/usr/local/tomcat/endorsed",
"-Dcatalina.base=/usr/local/tomcat",
"-Dcatalina.home=/usr/local/tomcat",
"-Djava.io.tmpdir=/usr/local/tomcat/temp"
"-Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties",
"-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager",
"-Duser.timezone=GMT+08",