原文:https://www.howardliu.cn/java/jvm-tuning-basic/

这几天压测预生产环境,发现TPS各种不稳。因为是重构的系统,据说原来的系统在高并发的时候一点问题没有,结果重构的系统被几十个并发压一下就各种不稳定。虽然测试的同事没有说啥,但自己感觉被啪啪的打脸。。。

于是各种排查,最先想到的就是JVM参数,于是优化一番,希望能够出一个好的结果。尽管后来证明不稳定的原因是安装LoadRunner的压测服务器不稳定,不关我的系统的事,不过也是记录一下,一是做个备份,二是可以给别人做个参考。

写在前面的话

因为Hotspot JDK提供的参数默认值,在小版本之间不断变化,参数之间也会互相影响。而且,服务器配置不同,都可能影响最后的效果。所以千万不要迷信网上的某篇文章(包括这篇)里面的参数配置,一切的配置都需要自己亲身测试一番才能用。针对于JVM参数默认值不断变化,可以使用-XX:+PrintFlagsFinal打印当前环境JVM参数默认值,比如:java -XX:PrintFlagsFinal -version,也可以用java [生产环境参数] -XX:+PrintFlagsFinal –version | grep [待查证的参数]查看具体的参数数据。

这里是一个8G服务器的参数,JDK版本信息如下:

java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

堆设置

堆内存设置应该算是一个Java程序猿的基本素养,最少也得修改过Xms、Xmx、Xmn这三个参数了。但是一个2G堆大小的JVM,可能总共占用多少内存的?

堆内存 + 线程数 * 线程栈 + 永久代 + 二进制代码 + 堆外内存

2G + 1000 * 1M + 256M + 48/240M + (~2G) = 5.5G (3.5G)

  • 堆内存: 存储Java对象,默认为物理内存的1/64
  • 线程栈: 存储局部变量(原子类型,引用)及其他,默认为1M
  • 永久代: 存储类定义及常量池,注意JDK7/8的区别
  • 二进制代码:JDK7与8,打开多层编译时的默认值不一样,从48到240M
  • 堆外内存: 被Netty,堆外缓存等使用,默认最大值约为堆内存大小

也就是说,堆内存设置为2G,那一个有1000个线程的JVM可能需要占5.5G,在考虑系统占用、IO占用等等各种情况,一台8G的服务器,也就启动一个服务了。当然,如果线程数少、并发不高、压力不大,还是可以启动多个,而且也可以把堆内存降低。

  1. -Xms2g 与 -Xmx2g:堆内存大小,第一个是最小堆内存,第二个是最大堆内存,比较合适的数值是2-4g,再大就得考虑GC时间
  2. -Xmn1g 或 (-XX:NewSize=1g 和 -XX:MaxNewSize=1g) 或 -XX:NewRatio=1:设置新生代大小,JDK默认新生代占堆内存大小的1/3,也就是-XX:NewRatio=2。这里是设置的1g,也就是-XX:NewRatio=1。可以根据自己的需要设置。
  3. -XX:MetaspaceSize=128m 和 -XX:MaxMetaspaceSize=512m,JDK8的永生代几乎可用完机器的所有内存,为了保护服务器不会因为内存占用过大无法连接,需要设置一个128M的初始值,512M的最大值保护一下。
  4. -XX:SurvivorRatio:新生代中每个存活区的大小,默认为8,即1/10的新生代, 1/(SurvivorRatio+2),有人喜欢设小点省点给新生代,但要避免太小使得存活区放不下临时对象而要晋升到老生代,还是从GC Log里看实际情况了。
  5. -Xss256k:在堆之外,线程占用栈内存,默认每条线程为1M。存放方法调用出参入参的栈、局部变量、标量替换后的局部变量等,有人喜欢设小点节约内存开更多线程。但反正内存够也就不必要设小,有人喜欢再设大点,特别是有JSON解析之类的递归调用时不能设太小。
  6. -XX:MaxDirectMemorySize:堆外内存/直接内存的大小,默认为堆内存减去一个Survivor区的大小。
  7. -XX:ReservedCodeCacheSize:JIT编译后二进制代码的存放区,满了之后就不再编译。默认开多层编译240M,可以在JMX里看看CodeCache的大小。

GC设置

目前比较主流的GC是CMS和G1,有大神建议以8G为界。(据说JDK 9默认的是G1)。因为应用设置的内存都比较小,所以选择CMS收集器。下面的参数也是针对CMS收集器的,等之后如果有需要,再补充G1收集器的参数。

CMS设置

  1. -XX:+UseConcMarkSweepGC:启用CMS垃圾收集器
  2. -XX:CMSInitiatingOccupancyFraction=80 与 -XX:+UseCMSInitiatingOccupancyOnly:两个参数需要配合使用,否则第一个参数的75只是一个参考值,JVM会重新计算GC的时间。
  3. -XX:MaxTenuringThreshold=15:对象在Survivor区熬过多少次Young GC后晋升到年老代,默认是15。Young GC是最大的应用停顿来源,而新生代里GC后存活对象的多少又直接影响停顿的时间,所以如果清楚Young GC的执行频率和应用里大部分临时对象的最长生命周期,可以把它设的更短一点,让其实不是临时对象的新生代长期对象赶紧晋升到年老代。
  4. -XX:-DisableExplicitGC:允许使用System.gc()主动调用GC。这里需要说明下,有的JVM优化建议是设置-XX:-DisableExplicitGC,关闭手动调用System.gc()。这是应为System.gc()是触发Full GC,频繁的Full GC会严重影响性能。但是很多NIO框架,比如Netty,会使用堆外内存,如果没有Full GC的话,堆外内存就无法回收。如果不主动调用System.gc(),就需要等到JVM自己触发Full GC,这个时候,就可能引起长时间的停顿(STW),而且机器负载也会升高。所以不能够完全禁止System.gc(),又得缩短Full GC的时间,那就使用-XX:+ExplicitGCInvokesConcurrent-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses选项,使用CMS收集器来触发Full GC。这两个选项需要配合-XX:+UseConcMarkSweepGC使用。
  5. -XX:+ExplicitGCInvokesConcurrent:使用System.gc()时触发CMS GC,而不是Full GC。默认是不开启的,只有使用-XX:+UseConcMarkSweepGC选项的时候才能开启这个选项。
  6. -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses:使用System.gc()时,永久代也被包括进CMS范围内。只有使用-XX:+UseConcMarkSweepGC选项的时候才能开启这个选项。
  7. -XX:+ParallelRefProcEnabled:默认为false,并行的处理Reference对象,如WeakReference,除非在GC log里出现Reference处理时间较长的日志,否则效果不会很明显。
  8. -XX:+ScavengeBeforeFullGC:在Full GC执行前先执行一次Young GC。
  9. -XX:+UseGCOverheadLimit: 限制GC的运行时间。如果GC耗时过长,就抛OOM。
  10. -XX:+UseParallelGC:设置并行垃圾收集器
  11. -XX:+UseParallelOldGC:设置老年代使用并行垃圾收集器
  12. -XX:-UseSerialGC:关闭串行垃圾收集器
  13. -XX:+CMSParallelInitialMarkEnabled 和 -XX:+CMSParallelRemarkEnabled:降低标记停顿
  14. -XX:+CMSScavengeBeforeRemark:默认为关闭,在CMS remark前,先执行一次minor GC将新生代清掉,这样从老生代的对象引用到的新生代对象的个数就少了,停止全世界的CMS remark阶段就短一些。如果看到GC日志里remark阶段的时间超长,可以打开此项看看有没有效果,否则还是不要打开了,白白多了次YGC。
  15. -XX:CMSWaitDuration=10000:设置垃圾收集的最大时间间隔,默认是2000。
  16. -XX:+CMSClassUnloadingEnabled:在CMS中清理永久代中的过期的Class而不等到Full GC,JDK7默认关闭而JDK8打开。看自己情况,比如有没有运行动态语言脚本如Groovy产生大量的临时类。它会增加CMS remark的暂停时间,所以如果新类加载并不频繁,这个参数还是不开的好。

GC日志

GC过程可以通过GC日志来提供优化依据。

  1. -XX:+PrintGCDetails:启用gc日志打印功能
  2. -Xloggc:/path/to/gc.log:指定gc日志位置
  3. -XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
  4. -XX:+PrintGCDateStamps:打印可读的日期而不是时间戳
  5. -XX:+PrintGCApplicationStoppedTime:打印所有引起JVM停顿时间,如果真的发现了一些不知什么的停顿,再临时加上-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1找原因。
  6. -XX:+PrintGCApplicationConcurrentTime:打印JVM在两次停顿之间正常运行时间,与-XX:+PrintGCApplicationStoppedTime一起使用效果更佳。
  7. -XX:+PrintTenuringDistribution:查看每次minor GC后新的存活周期的阈值
  8. -XX:+UseGCLogFileRotation 与 -XX:NumberOfGCLogFiles=10 与 -XX:GCLogFileSize=10M:GC日志在重启之后会清空,但是如果一个应用长时间不重启,那GC日志会增加,所以添加这3个参数,是GC日志滚动写入文件,但是如果重启,可能名字会出现混乱。
  9. -XX:PrintFLSStatistics=1:打印每次GC前后内存碎片的统计信息

其他参数设置

  1. -ea:启用断言,这个没有什么好说的,可以选择启用,或这选择不启用,没有什么大的差异。完全根据自己的系统进行处理。
  2. -XX:+UseThreadPriorities:启用线程优先级,主要是因为我们可以给予周期性任务更低的优先级,以避免干扰客户端工作。在我当前的环境中,是默认启用的。
  3. -XX:ThreadPriorityPolicy=42:允许降低线程优先级
  4. -XX:+HeapDumpOnOutOfMemoryError:发生内存溢出是进行heap-dump
  5. -XX:HeapDumpPath=/path/to/java_pid.hprof:这个参数与-XX:+HeapDumpOnOutOfMemoryError共同作用,设置heap-dump时内容输出文件。
  6. -XX:ErrorFile=/path/to/hs_err_pid.log:指定致命错误日志位置。一般在JVM发生致命错误时会输出类似hs_err_pid.log的文件,默认是在工作目录中(如果没有权限,会尝试在/tmp中创建),不过还是自己指定位置更好一些,便于收集和查找,避免丢失。
  7. -XX:StringTableSize=1000003:指定字符串常量池大小,默认值是60013。对Java稍微有点常识的应该知道,字符串是常量,创建之后就不可修改了,这些常量所在的地方叫做字符串常量池。如果自己系统中有很多字符串的操作,且这些字符串值比较固定,在允许的情况下,可以适当调大一些池子大小。
  8. -XX:+AlwaysPreTouch:在启动时把所有参数定义的内存全部捋一遍。使用这个参数可能会使启动变慢,但是在后面内存使用过程中会更快。可以保证内存页面连续分配,新生代晋升时不会因为申请内存页面使GC停顿加长。通常只有在内存大于32G的时候才会有感觉。
  9. -XX:-UseBiasedLocking:禁用偏向锁(在存在大量锁对象的创建且高度并发的环境下(即非多线程高并发应用)禁用偏向锁能够带来一定的性能优化)
  10. -XX:AutoBoxCacheMax=20000:增加数字对象自动装箱的范围,JDK默认-128~127的int和long,超出范围就会即时创建对象,所以,增加范围可以提高性能,但是也是需要测试。
  11. -XX:-OmitStackTraceInFastThrow:不忽略重复异常的栈,这是JDK的优化,大量重复的JDK异常不再打印其StackTrace。但是如果系统是长时间不重启的系统,在同一个地方跑了N多次异常,结果就被JDK忽略了,那岂不是查看日志的时候就看不到具体的StackTrace,那还怎么调试,所以还是关了的好。
  12. -XX:+PerfDisableSharedMem:启用标准内存使用。JVM控制分为标准或共享内存,区别在于一个是在JVM内存中,一个是生成/tmp/hsperfdata_{userid}/{pid}文件,存储统计数据,通过mmap映射到内存中,别的进程可以通过文件访问内容。通过这个参数,可以禁止JVM写在文件中写统计数据,代价就是jps、jstat这些命令用不了了,只能通过jmx获取数据。但是在问题排查是,jps、jstat这些小工具是很好用的,比jmx这种很重的东西好用很多,所以需要自己取舍。这里有个GC停顿的例子。
  13. -Djava.net.preferIPv4Stack=true:这个参数是属于网络问题的一个参数,可以根据需要设置。在某些开启ipv6的机器中,通过InetAddress.getLocalHost().getHostName()可以获取完整的机器名,但是在ipv4的机器中,可能通过这个方法获取的机器名不完整,可以通过这个参数来获取完整机器名。

大神给出的例子

下面贴上大神给出的例子,可以参考使用,不过还是建议在自己的环境中有针对的验证之后再使用,毕竟大神的环境和自己的环境还是不同。

性能相关

-XX:-UseBiasedLocking -XX:-UseCounterDecay -XX:AutoBoxCacheMax=20000
-XX:+PerfDisableSharedMem(可选) -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom

内存大小相关(JDK7)

-Xms4096m -Xmx4096m -Xmn2048m -XX:MaxDirectMemorySize=4096m
-XX:PermSize=128m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=240M

如果使用jdk8,就把-XX:PermSize=128m -XX:MaxPermSize=512m换成-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m,正如前面所说的,这两套参数是为了保证安全的,建议还是加上。

CMS GC相关

-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=6
-XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled

GC日志相关

-Xloggc:/dev/shm/app-gc.log -XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCDateStamps -XX:+PrintGCDetails

异常日志相关

-XX:-OmitStackTraceInFastThrow -XX:ErrorFile=${LOGDIR}/hserr%p.log
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/

JMX相关

-Dcom.sun.management.jmxremote.port=${JMX_PORT} -Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

JVM参数优化(基础篇)的更多相关文章

  1. Tomcat 调优及 JVM 参数优化

    Tomcat 本身与 JVM 优化 Tomcat:调整Server.xml JVM:bat启动服务方式的话修改catalina.bat 服务式启动的话参考:http://www.cnblogs.com ...

  2. Tomcat7 调优及 JVM 参数优化

      Tomcat 的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你.对于操作系统优化来说,是尽可能的增大可使用的内存容量.提高CPU 的频率,保证 ...

  3. jvm参数优化

    一.HotSpot JVM 提供了三类参数 现在的JVM运行Java程序(和其它的兼容性语言)时在高效性和稳定性方面做的非常出色.例如:自适应内存管理.垃圾收集.及时编译.动态类加载.锁优化等.虽然有 ...

  4. tomcat jvm参数优化

    根据gc(垃圾回收器)的选择,进行参数优化 JVM给了三种选择:串行收集器.并行收集器.并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器. -XX:+Us ...

  5. sql优化基础篇

    优化的步骤: 0.先sql运行看看是否真的很慢,注意设置SQL_NO_CACHE 1.where条件单表查,锁定最小返回记录表.这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始 ...

  6. tomcat jvm 参数优化

    1. 内存设置 常见配置汇总 堆设置 -Xms:初始堆大小 -Xmx:最大堆大小 -XX:NewSize=n:设置年轻代大小 -XX:NewRatio=n:设置年轻代和年老代的比值.如:为3,表示年轻 ...

  7. 性能测试十九:jmeter参数优化+排错

    一:参数优化 1,控制台取样间隔的设置,在jmeter/bin/jmeter.properties文件中修改 summariser.interval=10,默认为30s,最低可修改为6s 2,Jvm参 ...

  8. linux下实现压测-html报表生成-控制台参数优化【jmeter】

    jmeter - 单机压测 - 命令行模式-html报表生成-控制台参数优化 一/ 准备工作 1.压力机安装并配置好 jdk 2.调试好程序脚本 再上传到 linux下 3.进入jmeter  bin ...

  9. TomcatJVM参数优化降低内存使用率(重点)!

    JVM是jdk最底层的支柱 做JVM参数优化主要是为了改善服务器性能以及内存使用率 JAVA堆分为三大部分:(新生代.老年代.永久代) ================================ ...

随机推荐

  1. 创建slackapp prometheus告警发到slack

    创建slackapp: https://blog.walterlv.com/post/slack-api-starter-incoming-webhooks.html#%E5%88%9B%E5%BB% ...

  2. Java之整数运算

    Java的整数运算遵循四则运算规则,可以使用任意嵌套的小括号.四则运算规则和初等数学一致.例如: public class Main { public static void main(String[ ...

  3. poj 2775 文件结构“图"

    总时间限制: 1000ms 内存限制: 65536kB 描述 在计算机上看到文件系统的结构通常很有用.Microsoft Windows上面的"explorer"程序就是这样的一个 ...

  4. Android Capabilities讲解

    1.Capabilities介绍 可以看下之前代码里面设置的capabilities DesiredCapabilities capabilities =newDesiredCapabilities( ...

  5. String初解

    String 类型是不可变的对象,因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后有一次看到一个代码段,如下: 这段代码返回的是ture,当时以为是f ...

  6. ubuntu18.04LTS服务器用vituralenv安装和配置pytorch和tensorflow

    ============tensorflow================= $ python3 -m venv tf14====输入例子====# $ vim ~/.bashrc #(添加如下行, ...

  7. Office系列常用快捷键

    office三件套,常用的快捷键. Word常用快捷键 查找文字.格式和特殊项 Ctrl+G 使字符变为粗体 Ctrl+B 为字符添加下划线 Ctrl+U 删除段落格式 Ctrl+Q 复制所选文本或对 ...

  8. mongdb基本使用

    mongodb创建用户,设置密码 参考:https://www.jianshu.com/p/237a0c5ad9fa MongoDB内置的数据库角色有: 1. 数据库用户角色:read.readWri ...

  9. java.lang.IllegalArgumentException: Expected authority at index 7: http:// 异常的原因

    今天遇到个错误,异常信息  java.lang.IllegalArgumentException: Expected authority at index 7: http://  ,中文意思就是说参数 ...

  10. Spring Cloud Zuul源码

    一.Zuul源码分析(初始化流程.请求处理流程)