jdk1.8和jdk1.7 默认垃圾收集器Parallel(分代回收)默认堆最大内存占总内存四分之一(对于开发环境,通常情况是最好的垃圾收集器)
jdk1.9 默认垃圾收集器G1(关注停顿,大于6G堆内存建议用G1更优,小于6G;关注吞吐量,或小于6G用CMS);
G1的设计原则就是简单可行的性能调优
如果我们需要调优,在内存大小一定的情况下,我们只需要修改MaxGCPauseMillis最大暂停时间即可。
如果MaxGCPauseMillis设置的过小,那么GC就会频繁,吞吐量就会下降。如果MaxGCPauseMillis设置的过大,
应用程序暂停时间就会变长。G1的默认暂停时间是200毫秒;垃圾收集器G1详细可看:https://mp.weixin.qq.com/s/7qqrLuJiorSBz77hktmrBA
8G内存服务器
java -Xms4g -Xmx4g -XX:+UseG1GC -Xss256k -XX:MaxGCPauseMillis=200
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
-XX:+DisableExplicitGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gc.log -jar springboot-start-0.0.1.jar &
G1收集器介绍https://mp.weixin.qq.com/s/7qqrLuJiorSBz77hktmrBA
Young GC:选定所有新生代里的region。通过控制新生代的region个数来控制young GC的开销。
Mixed GC:选定所有新生代里的region,外加根据global concurrent marking统计得出收集收益高的若干老年代region。在用户指定的开销目标范围内尽可能选择收益高的老年代region。
#G1最佳实践-针对具有大内存、多处理器的机器,低GC延迟,堆大小约6GB或更大时用UseG1GC
在使用G1垃圾收集器的时候遵循以下实践可以少走不少弯路:
1不断调优暂停时间指标
通过XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。
2不要设置新生代和老年代的大小
G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小。
【注】:使用G1回收器时,G1打破了以往将收集范围固定在新生代或老年代的模式,不需要为各个空间进行单独设置了,G1算法将堆整体划分为若干个区域(Region)。
8G内存服务器
-Xms4g -Xmx4g -XX:+UseG1GC -Xss256k -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
-XX:+DisableExplicitGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gc.log
16G内存服务器
-Xms8g -Xmx8g -XX:+UseG1GC -Xss256k -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
-XX:+DisableExplicitGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gc.log
#-XX:+UseG1GC
#-Xmx4g 堆大小
#-Xss256k 每个线程栈的大小;默认值1M,一般不会有1M
#-XX:MetaspaceSize=512m 元空间大小(代替了永久代),对于大部分项目 256m 即可。
具体设置多大,建议稳定运行一段时间后通过jstat -gc pid确认且这个值大一些,对于大部分项目 256m 即可。
无论-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数如何设置,都会从 20.8M 开始,随着类加载越来越多不断扩容调整,上限是-XX:MaxMetaspaceSize,默认是无穷大,占满操作系统内存后发生OOM
和jdk7的永久代对比(只是内存限制和垃圾回收机制这个两个地方改变了)几乎存储类和类加载的元数据信息(符号,常量,字符串常量)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap_dump.bin 内存溢出详细堆栈信息:
-XX:+DisableExplicitGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gc.log gc日志
#参数/默认值 含义
-XX:+UseG1GC 使用 G1 垃圾收集器
-XX:MaxGCPauseMillis=200 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)
-XX:InitiatingHeapOccupancyPercent=45 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45.
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.最多为8。如果处理器多于8个,则将n的值设置为处理器的大约5/8。
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap_dump.bin 内存溢出详细堆栈信息:
-verbose:gc //在控制台输出GC情况
-XX:+PrintGCDetails //在控制台输出详细的GC情况
-Xloggc: filepath //将GC日志输出到指定文件中
XX:NewSize 新生代
#调优实践MaxGCPauseMillis调优
-XX:+UseG1GC -Xmx32g-XX:MaxGCPauseMillis=200
前面2个参数都好理解,后面这个MaxGCPauseMillis参数该怎么配置呢?这个参数从字面的意思上看,就是允许的GC最大的暂停时间。G1尽量确保每次GC暂停的时间都在设置的MaxGCPauseMillis范围内。 那G1是如何做到最大暂停时间的呢?这涉及到另一个概念,CSet(collection set)。它的意思是在一次垃圾收集器中被收集的区域集合。
Young GC:选定所有新生代里的region。通过控制新生代的region个数来控制young GC的开销。
Mixed GC:选定所有新生代里的region,外加根据global concurrent marking统计得出收集收益高的若干老年代region。在用户指定的开销目标范围内尽可能选择收益高的老年代region。
在理解了这些后,我们再设置最大暂停时间就好办了。首先,我们能容忍的最大暂停时间是有一个限度的,我们需要在这个限度范围内设置。但是应该设置的值是多少呢?我们需要在吞吐量跟MaxGCPauseMillis之间做一个平衡。如果MaxGCPauseMillis设置的过小,那么GC就会频繁,吞吐量就会下降。如果MaxGCPauseMillis设置的过大,应用程序暂停时间就会变长。G1的默认暂停时间是200毫秒,我们可以从这里入手,调整合适的时间
#JVM的一般参数设置
内存区域大小
XX:+UseG1GC:用于指定 JVM 使用的垃圾回收器为 G1,尽量不要靠默认值去保证,要显式的指定一个。
-Xmx:设置堆的最大值,一般为操作系统的 2/3 大小。
-Xms:设置堆的初始值,一般设置成和 Xmx 一样的大小来避免动态扩容。
-Xmn:表示年轻代的大小,默认新生代占堆大小的 1/3。高并发、对象快消亡场景可适当加大这个区域,对半,或者更多,都是可以的。但是在 G1 下,就不用再设置这个值了,它会自动调整。
-XX:MaxMetaspaceSize:用于限制元空间的大小,一般 256M 足够了,这一般和初始大小 -XX:MetaspaceSize 设置成一样的。
-XX:MaxDirectMemorySize:用于设置直接内存的最大值,限制通过 DirectByteBuffer 申请的内存。
堆外内存适用于生命周期中等或较长的对象:直接的文件拷贝操作,或者I/O操作。直接使用堆外内存就能少去内存从用户内存拷贝到系统内存的操作;同时,还可以使用 池+堆外内存 的组合方式,来对生命周期较短,但涉及到I/O操作的对象进行堆外内存的再使用。( Netty中就使用了该方式 )
-XX:ReservedCodeCacheSize:用于设置 JIT 编译后的代码存放区大小,如果观察到这个值有限制,可以适当调大,一般够用即可。
-Xss:用于设置栈的大小,默认为 1M,已经足够用了。
内存调优
-XX:+AlwaysPreTouch:表示在启动时就把参数里指定的内存全部初始化,启动时间会慢一些,但运行速度会增加。
-XX:SurvivorRatio:默认值为 8,表示伊甸区和幸存区的比例。
-XX:MaxTenuringThreshold:这个值在 CMS 下默认为 6,G1 下默认为 15,这个值对象提升有关,改动效果会比较明显。对象的年龄分布可以使用 -XX:+PrintTenuringDistribution 打印,如果后面几代的大小总是差不多,证明过了某个年龄后的对象总能晋升到老生代,就可以把晋升阈值设小。
PretenureSizeThreshold:表示超过一定大小的对象,将直接在老年代分配,不过这个参数用的不是很多。
CMS 垃圾回收器
-XX:+UseCMSInitiatingOccupancyOnly:这个参数需要加上 -XX:CMSInitiatingOccupancyFraction,注意后者需要和前者一块配合才能完成工作,它们指定了 MajorGC 的发生时机。
-XX:ExplicitGCInvokesConcurrent:当代码里显示调用了 System.gc(),实际上是想让回收器进行 FullGC,如果发生这种情况,则使用这个参数开始并行 FullGC,建议加上这个参数。
-XX:CMSFullGCsBeforeCompaction:这个参数的默认值为 0,代表每次 FullGC 都对老生代进行碎片整理压缩,建议保持默认。
-XX:CMSScavengeBeforeRemark:表示开启或关闭在 CMS 重新标记阶段之前的清除(YGC)尝试,它可以降低 remark 时间,建议加上。
-XX:+ParallelRefProcEnabled:可以用来并行处理 Reference,以加快处理速度,缩短耗时
G1 垃圾回收器
-XX:MaxGCPauseMillis:用于设置目标停顿时间,G1 会尽力达成。
-XX:G1HeapRegionSize:用于设置小堆区大小,这个值为 2 的次幂,不要太大,也不要太小,如果实在不知道如何设置,建议保持默认。
-XX:InitiatingHeapOccupancyPercent:表示当整个堆内存使用达到一定比例(默认是 45%),并发标记阶段 就会被启动。
-XX:ConcGCThreads:表示并发垃圾收集器使用的线程数量,默认值随 JVM 运行的平台不同而变动,不建议修改。
其他参数优化
*-XX:AutoBoxCacheMax:用于加大 IntegerCache,具体原因可参考第 20 课时。
-Djava.security.egd=file:/dev/./urandom:这个参数使用 urandom 随机生成器,在进行随机数获取时,速度会更快。
-XX:-OmitStackTraceInFastThrow:用于减少异常栈的输出,并进行合并。虽然会对调试有一定的困扰,但能在发生异常时显著增加性能。
存疑优化
-XX:-UseBiasedLocking:用于取消偏向锁(第 19 课时),理论上在高并发下会增加效率,这个需要实际进行观察,在无法判断的情况下,不需要配置。
GC 日志
G1 垃圾回收器运行的 JVM 启动命令
java \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1HeapRegionSize=16m \
-XX:+ParallelRefProcEnabled \
-XX:MaxTenuringThreshold=3 \
-XX:+AlwaysPreTouch \
-Xmx5440M \
-Xms5440M \
-XX:MaxMetaspaceSize=256M \
-XX:MetaspaceSize=256M \
-XX:MaxDirectMemorySize=100M \
-XX:ReservedCodeCacheSize=268435456 \
-XX:-OmitStackTraceInFastThrow \
-Djava.security.egd=file:/dev/./urandom \
-verbose:gc \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCApplicationStoppedTime \
-XX:+PrintGCApplicationConcurrentTime \
-XX:+PrintTenuringDistribution \
-XX:+PrintClassHistogramBeforeFullGC \
-XX:+PrintClassHistogramAfterFullGC \
-Xloggc:/tmp/logs/gc_%p.log \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/logs \
-XX:ErrorFile=/tmp/logs/hs_error_pid%p.log \
-Djava.rmi.server.hostname=127.0.0.1 \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=14000 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-javaagent:/opt/test.jar \
MainRun
#G1的设计原则就是简单可行的性能调优
-XX:+UseG1GC -Xmx32g-XX:MaxGCPauseMillis=200
如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。
#G1算法将堆划分为若干个区域(Region),
它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了
#分成4个色块的代
Eden
Survivor
Old
Humongous G1划分了一个Humongous区,它用来专门存放短期存在巨型对象,
那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
黑色:根对象,或者该对象与它的子对象都被扫描
灰色:对象本身被扫描,但还没扫描完该对象中的子对象
白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象
1根对象被置为黑色,子对象被置为灰色。
2继续由灰色遍历,将已扫描了子对象的对象置为黑色。
3遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理。
#apollo
## Adjust memory settings if necessary
export JAVA_OPTS="-Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=1536m -XX:MaxNewSize=1536m -XX:SurvivorRatio=8"
## Only uncomment the following when you are using server jvm
export JAVA_OPTS="$JAVA_OPTS -server -XX:-ReduceInitialCardMarks"
########### The following is the same for configservice, adminservice, portal ###########
export JAVA_OPTS="$JAVA_OPTS -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+DisableExplicitGC -XX:+ScavengeBeforeFullGC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+ExplicitGCInvokesConcurrent -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Duser.timezone=Asia/Shanghai -Dclient.encoding.override=UTF-8 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom"
jdk1.8和jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1
#16G内存默认jdk8 垃圾回收器Parallel配置-堆大大值4G
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266390080 -XX:MaxHeapSize=4262241280 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
#UseParallelGC 即 Parallel Scavenge + Parallel Old,再查看详细信息
java -XX:+PrintGCDetails -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
Heap
PSYoungGen total 76288K, used 2621K [0x000000076b500000, 0x0000000770a00000, 0x00000007c0000000)
eden space 65536K, 4% used [0x000000076b500000,0x000000076b78f748,0x000000076f500000)
from space 10752K, 0% used [0x000000076ff80000,0x000000076ff80000,0x0000000770a00000)
to space 10752K, 0% used [0x000000076f500000,0x000000076f500000,0x000000076ff80000)
ParOldGen total 175104K, used 0K [0x00000006c1e00000, 0x00000006cc900000, 0x000000076b500000)
object space 175104K, 0% used [0x00000006c1e00000,0x00000006c1e00000,0x00000006cc900000)
Metaspace used 2291K, capacity 4480K, committed 4480K, reserved 1056768K
class space used 254K, capacity 384K, committed 384K, reserved 1048576K
#G1日志分析
//新生代GC
2018-05-03T10:21:43.209-0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0035356 secs] //初始标记,耗时0.0035秒
[Parallel Time: 2.4 ms, GC Workers: 8] //并行8个线程,耗时2.4ms
[GC Worker Start (ms): Min: 813.1, Avg: 813.7, Max: 813.9, Diff: 0.7]
[Ext Root Scanning (ms): Min: 0.0, Avg: 1.1, Max: 1.5, Diff: 1.5, Sum: 9.1] //每个扫描root的线程耗时
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //更新RS的耗时,G1中每块区域都有一个RS与之对应,RS记录了该区域被其他区域引用的对象。回收时,就把RS作为根集的一部分,从而加快回收
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0] //Processed Buffers就是记录引用变化的缓存空间
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //扫描RS
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //根扫描耗时
[Object Copy (ms): Min: 0.0, Avg: 0.5, Max: 1.3, Diff: 1.3, Sum: 3.6] //对象拷贝
[Termination (ms): Min: 0.0, Avg: 0.2, Max: 0.2, Diff: 0.2, Sum: 1.2]
[Termination Attempts: Min: 1, Avg: 1.8, Max: 4, Diff: 3, Sum: 14]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 1.6, Avg: 1.8, Max: 2.3, Diff: 0.8, Sum: 14.1] //GC线程耗时
[GC Worker End (ms): Min: 815.4, Avg: 815.4, Max: 815.4, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms] //清空CardTable耗时,RS是依赖CardTable记录区域存活对象的
[Other: 1.1 ms]
[Choose CSet: 0.0 ms] //选取CSet
[Ref Proc: 0.9 ms] //弱引用、软引用的处理耗时
[Ref Enq: 0.0 ms] //弱引用、软引用的入队耗时
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms] //释放被回收区域的耗时(包含他们的RS)
[Eden: 5120.0K(24.0M)->0.0B(12.0M) Survivors: 0.0B->2048.0K Heap: 16.0M(50.0M)->12.4M(50.0M)]
[Times: user=0.01 sys=0.00, real=0.01 secs]
//根区域扫描
2018-05-03T10:21:43.213-0800: [GC concurrent-root-region-scan-start]
2018-05-03T10:21:43.214-0800: [GC concurrent-root-region-scan-end, 0.0012422 secs]
// 并发标记
2018-05-03T10:21:43.214-0800: [GC concurrent-mark-start]
2018-05-03T10:21:43.214-0800: [GC concurrent-mark-end, 0.0004063 secs]
//重新标记又叫最终标记
2018-05-03T10:21:43.214-0800: [GC remark 2018-05-03T10:21:43.215-0800: [Finalize Marking, 0.0003736 secs] 2018-05-03T10:21:43.215-0800: [GC ref-proc, 0.0000533 secs] 2018-05-03T10:21:43.215-0800: [Unloading, 0.0007439 secs], 0.0013442 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
//独占清理
2018-05-03T10:21:43.216-0800: [GC cleanup 13M->13M(50M), 0.0004002 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]