JVM命令行参数参考: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

1. 基本概念

  1. 吞吐量:

7. JVM调优实战 - 图1

  1. 响应时间快:

用户线程停顿时间短

所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量?

问题:
科学计算,吞吐量优先。数据挖掘,吞吐量优先优先。吞吐量优先的一般使用PS + PO。
网站或者图形界面程序,肯定是响应时间优先, 一般是使用G1。

1. HotSpot参数分类:

参数类型 表示 说明
标准参数 **-** 开头 所有HotSpot版本都支持
非标准参数 **-X**开头 特定版本HotSpot支持特定命令
不稳定参数 **-XX**开头 下个版本可能取消

2. 常用JVM参数

  1. java -X:打印非标类参数

image.png
实验小程序:

  1. import java.util.LinkedList;
  2. import java.util.List;
  3. public class TestGC {
  4. public static void main(String[] args) {
  5. System.out.println("HelloGC!");
  6. List list = new LinkedList();
  7. for(;;) {
  8. byte[] b = new byte[1024*1024];
  9. list.add(b);
  10. }
  11. }
  12. }

java -XX:+PrintCommandLineFlags:打印命令行参数选项
image.png

-XX:InitialHeapSize=267117376 指定初始堆大小
-XX:MaxHeapSize=4273878016 指定最大堆大小
XX:+UseCompressedClassPointers 使用压缩指针
-XX:+UseCompressedOops 使用压缩Oops
-XX:+UseParallelGC 使用ParallelGC
-Xmn10M 新生代的大小
-Xms40M 指定最小堆大小为40M
-Xmx60M 指定最小堆大小为60M 一般情况下要指定最小堆大小和最大堆大小要一样的, 因为如果不一样的话JAVA会自动弹性扩容和收缩堆的空间, 浪费系统性能。
-XX:+PrintGC 打印GC基本信息
-XX:+PrintGCDetails 打印GC详细信息
-XX:+PrintGCTimeStamps 打印GC的时间信息
-XX:+PrintGCCause 打印GC产生的原因

在windows下面打印GC日志,并解析GC日志
image.png
指定-classpath D:\code\IDEA\idea_workspace\learn\JVM\out\production\JVM 是因为java搜索class文件是在classpath里面搜索的。
image.png
image.png

image.png
java -XX:+PrintCommandLineFlags TestGC

3. GC常用参数

  • -Xmn -Xms -Xmx -Xss: 年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB: 使用TLAB,默认打开
  • -XX:+PrintTLAB: 打印TLAB的使用情况
  • -XX:TLABSize: 设置TLAB大小
  • -XX:+DisableExplictGC:System.gc()不管用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低)打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime (低)打印暂停时长
  • -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
  • -verbose:class 类加载详细过程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
  • -Xloggc:opt/log/gc.log
  • -XX:MaxTenuringThreshold 升代年龄,最大值15
  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 …
    这些不建议设置

    4. Parallel常用参数

  • -XX:+UseConcMarkSweepGC

  • -XX:ParallelCMSThreads
    CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction
    使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection
    在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction
    多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction
    达到什么比例时进行Perm回收
  • GCTimeRatio
    设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis
    停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

5. G1常用参数

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis
    建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis
    ?GC的间隔时间
  • -XX:+G1HeapRegionSize
    分区大小,建议逐渐增大该值,1 2 4 8 16 32。
    随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
    ZGC做了改进(动态区块大小)
  • G1NewSizePercent
    新生代最小比例,默认为5%
  • G1MaxNewSizePercent
    新生代最大比例,默认为60%
  • GCTimeRatio
    GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads
    线程数量
  • InitiatingHeapOccupancyPercent
    启动G1的堆空间占用比例

6. 预调优

  1. 调优,从业务场景开始,没有业务场景的调优都是耍流氓
  2. 无监控(压力测试,能看到结果),不调优
  3. 步骤:
    1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
      1. 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
      2. 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
    2. 选择回收器组合
    3. 计算内存需求(经验值 1.5G 16G)
    4. 选定CPU(越高越好)
    5. 设定年代大小、升级年龄
    6. 设定日志参数
      1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause

-Xloggc: 指定文件存放路径
-XX:+UseGCLogFileRotation:使用循环文件
-XX:NumberOfGCLogFiles=5:GC日志文件最多为5个和-XX:+UseGCLogFileRotation配合会滚动删除最早的日志文件
-XX:GCLogFileSize=20M: 每个文件大小最大20M
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause : 打印日志详细,时间,产生原因

  1. 1. 或者每天产生一个日志文件
  1. 观察日志情况
  • 案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

    这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G) 1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒) 经验值, 非要计算:一个订单产生需要多少内存?512K * 1000 500M内存 专业一点儿问法:要求响应时间100ms 压测!

  • 案例2:12306遭遇春节大规模抢票应该如何支撑?

    12306应该是中国并发量最大的秒杀网站: 号称并发量100W最高 CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器 普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款 12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款 减库存最后还会把压力压到一台服务器 可以做分布式本地库存 + 单独服务器做库存均衡 大流量的处理方法:分而治之

  • 怎么得到一个事务会消耗多少内存?

    1. 弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到
    2. 用压测来确定

4. 调优实战

一个小程序,这个小程序最终会造成OOM。

  1. package com.mashibing.jvm.gc;
  2. import java.math.BigDecimal;
  3. import java.util.ArrayList;
  4. import java.util.Date;
  5. import java.util.List;
  6. import java.util.concurrent.ScheduledThreadPoolExecutor;
  7. import java.util.concurrent.ThreadPoolExecutor;
  8. import java.util.concurrent.TimeUnit;
  9. /**
  10. * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
  11. */
  12. public class T15_FullGC_Problem01 {
  13. private static class CardInfo {
  14. BigDecimal price = new BigDecimal(0.0);
  15. String name = "张三";
  16. int age = 5;
  17. Date birthdate = new Date();
  18. public void m() {}
  19. }
  20. private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());
  21. public static void main(String[] args) throws Exception {
  22. executor.setMaximumPoolSize(50);
  23. for (;;){
  24. modelFit();
  25. Thread.sleep(100);
  26. }
  27. }
  28. private static void modelFit(){
  29. List<CardInfo> taskList = getAllCardInfo();
  30. taskList.forEach(info -> {
  31. // do something
  32. executor.scheduleWithFixedDelay(() -> {
  33. //do sth with info
  34. info.m();
  35. }, 2, 3, TimeUnit.SECONDS);
  36. });
  37. }
  38. private static List<CardInfo> getAllCardInfo(){
  39. List<CardInfo> taskList = new ArrayList<>();
  40. for (int i = 0; i < 100; i++) {
  41. CardInfo ci = new CardInfo();
  42. taskList.add(ci);
  43. }
  44. return taskList;
  45. }
  46. }
  1. 启动环境,减少java内存达到尽早OOM的目的。

    java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01

  2. 一般是运维团队首先受到报警信息

    CPU居高不下、内存不断增长

  3. top命令观察到问题

    找出CPU居高不下或者内存不断增长的进程, 记住PID

  4. top -Hp [``PID``] 观察进程中的线程

    观察哪个进程CPU居高不下或者内存不断增长。

  5. jps + jstack 进一步分析

    jps定位具体java进程
    jstack 定位线程状况,重点关注:WAITING BLOCKED
    eg.
    waiting on <0x0000000088ca3310> (a java.lang.Object)

  6. 找到阻塞线程

    假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁。

    怎么找?搜索jstack dump的信息,看哪个线程持有这把锁RUNNABLE

    PS:为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称? 怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

  1. jinfo pid
  2. jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
    jstat -gc 4655 500 : 每个500个毫秒打印GC的情况
    如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误)
    1:已经上线的系统不用图形界面用什么?(cmdline arthas)
    2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)
  3. jmap - histo 4655 | head -20,查找有多少对象产生
  4. jmap -dump:format=b,file=xxx pid :
    线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
    1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
    2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
    3:在线定位(一般小点儿公司用不到)
  5. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
  6. 使用MAT / jhat /jvisualvm 进行dump文件分析
    https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
    jhat -J-mx512M xxx.dump
    http://192.168.17.11:7000
    拉到最后:找到对应链接
    可以使用OQL查找特定问题对象
  7. 找到代码的问题

5. 在线排查工具

5.1 jconsole 远程连接

  1. 程序启动加入参数:

    1. java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
  2. 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去

    1. 192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
    2. ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
  3. 关闭linux防火墙(实战中应该打开对应端口)

    1. service iptables stop
    2. chkconfig iptables off #永久关闭
  4. windows上打开 jconsole远程连接 192.168.17.11:11111

5.2 jvisualvm远程连接

https://www.cnblogs.com/liugh/p/7620336.html (简单做法)

5.3 jprofiler (收费)

5.4 arthas

  • 为什么需要在线排查?
    在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。
  • jvm观察jvm信息
  • thread定位线程问题
  • dashboard 观察系统情况
  • heapdump + jhat分析
  • jad反编译
    动态代理生成类的问题定位
    第三方的类(观察代码)
    版本问题(确定自己最新提交的版本是不是被使用)
  • redefine 热替换
    目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性
    m() -> mm()
  • sc - search class
  • watch - watch method
  • 没有包含的功能:jmap