认识JVM常用命令行参数

  • JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
  • HotSpot参数分类
    1. 标准: - 开头,所有的HotSpot都支持
    2. 非标准:-X 开头,特定版本HotSpot支持特定命令
    3. 不稳定:-XX 开头,下个版本可能取消
    常用的查看参数的命令
    java -version
    java -X
    java -XX:+PrintFlagsInitial 默认参数值
    java -XX:+PrintFlagsFinal 最终参数值
    java -XX:+PrintFlagsFinal -version |grep GC
    java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数

认识GC日志和相关参数

一个小程序观察GC日志

这里就是不断的产生不能回收的对象,观察GC

  1. public class HelloGC {
  2. public static void main(String[] args) {
  3. System.out.println("hello gc~");
  4. List<byte[]> list = new LinkedList<>();
  5. for (; ; ) {
  6. list.add(new byte[1024 * 1024]);
  7. }
  8. }
  9. }

区分概念:内存泄漏memory leak,内存溢出out of memory

内存泄漏就是有一块内存,我们用不到 但是GC也无法回收,
内存溢出是分配内存时,发现内存不够用啦

这俩其实没啥必然的联系:
泄漏不一定导致溢出,内存总空间够大的话,浪费一点也不致命;
内存溢出不一定是由内存泄漏导致,内存总空间比较小,程序的对象又很多时,正常的使用也可能内存溢出

java -XX:+PrintCommandLineFlags -version

打印JAVA版本时打印出JVM的一些参数,也可以把-version换成执行的程序
会有些默认的JVM参数:

  1. -XX:InitialHeapSize=267595520 -XX:MaxHeapSize=4281528320 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC

java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC

  1. -XX:+PrintGC 打印GC信息,<br />关于打印GC的参数还有:<br /> -XX:+PrintGCDetails 打印详细信息,这个下面详细看<br /> -XX:+PrintGCTimeStamps 时间戳<br /> -XX:+PrintGCCause 原因<br /> -Xmn 年轻代空间的最大值,-XX:MaxNewSize<br /> -Xms 堆空间的最小值,-XX:InitialHeapSize,即初始大小<br /> -Xmx 堆空间的最大值,-XX:MaxHeapSize<br /> 一般把XmsXmx设置成一样的大小,避免堆的扩容和缩容所带来的资源消耗<br />执行后输出:<br />GC就是Young GC,年轻代的GC
  1. -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
  2. hello gc~
  3. [GC (Allocation Failure) 7289K->5888K(39936K), 0.0025545 secs]
  4. [GC (Allocation Failure) 13212K->13072K(39936K), 0.0019146 secs]
  5. [GC (Allocation Failure) 20554K->20160K(39936K), 0.0016964 secs]
  6. [GC (Allocation Failure) 27481K->27392K(39936K), 0.0019578 secs]
  7. [Full GC (Ergonomics) 27392K->27275K(54784K), 0.0088717 secs]
  8. [GC (Allocation Failure) 34598K->34507K(54784K), 0.0018232 secs]
  9. [GC (Allocation Failure) 41824K->41739K(53760K), 0.0022524 secs]
  10. [Full GC (Ergonomics) 41739K->41611K(59392K), 0.0021946 secs]
  11. [GC (Allocation Failure) 47889K->47851K(59904K), 0.0014204 secs]
  12. [Full GC (Ergonomics) 47851K->47755K(59904K), 0.0021113 secs]
  13. [Full GC (Ergonomics) 54027K->53900K(59904K), 0.0035498 secs]
  14. [Full GC (Ergonomics) 57096K->56972K(59904K), 0.0024055 secs]
  15. [Full GC (Allocation Failure) 56972K->56953K(59904K), 0.0069355 secs]
  16. Exception in thread main java.lang.OutOfMemoryError: Java heap space
  17. at com.mashibing.jvm.c5_gc.HelloGC.main(HelloGC.java:16)

java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC

-XX:+UseConcMarkSweepGC 使用CMS,观察CMS的GC
日志会详细一些,打出来CMS的一些阶段

PS GC日志详解

每种垃圾回收器的日志格式是不同的!
PS日志格式(-XX:+PrintGCDetails)

  1. -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
  2. hello gc~
  3. [GC (Allocation Failure) [PSYoungGen: 7289K->840K(9216K)] 7289K->5968K(39936K), 0.0015575 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  4. [GC (Allocation Failure) [PSYoungGen: 8164K->792K(9216K)] 13292K->13088K(39936K), 0.0019297 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  5. [GC (Allocation Failure) [PSYoungGen: 8274K->696K(9216K)] 20570K->20160K(39936K), 0.0031077 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  6. [GC (Allocation Failure) [PSYoungGen: 8016K->696K(9216K)] 27481K->27328K(39936K), 0.0032998 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
  7. [Full GC (Ergonomics) [PSYoungGen: 696K->0K(9216K)] [ParOldGen: 26632K->27275K(45056K)] 27328K->27275K(54272K), [Metaspace: 3480K->3480K(1056768K)], 0.0083194 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
  8. [GC (Allocation Failure) [PSYoungGen: 7323K->160K(9216K)] 34598K->34603K(54272K), 0.0016512 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  9. [GC (Allocation Failure) [PSYoungGen: 7477K->128K(8192K)] 41920K->41739K(53248K), 0.0030788 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
  10. [Full GC (Ergonomics) [PSYoungGen: 128K->0K(8192K)] [ParOldGen: 41611K->41611K(51200K)] 41739K->41611K(59392K), [Metaspace: 3480K->3480K(1056768K)], 0.0023069 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  11. [GC (Allocation Failure) [PSYoungGen: 6278K->1152K(8704K)] 47889K->47883K(59904K), 0.0015485 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  12. [Full GC (Ergonomics) [PSYoungGen: 1152K->0K(8704K)] [ParOldGen: 46731K->47755K(51200K)] 47883K->47755K(59904K), [Metaspace: 3480K->3480K(1056768K)], 0.0025861 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  13. [Full GC (Ergonomics) [PSYoungGen: 6272K->3072K(8704K)] [ParOldGen: 47755K->50828K(51200K)] 54027K->53900K(59904K), [Metaspace: 3480K->3480K(1056768K)], 0.0021735 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  14. [Full GC (Ergonomics) [PSYoungGen: 6268K->6144K(8704K)] [ParOldGen: 50828K->50828K(51200K)] 57096K->56972K(59904K), [Metaspace: 3480K->3480K(1056768K)], 0.0015226 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  15. [Full GC (Allocation Failure) [PSYoungGen: 6144K->6144K(8704K)] [ParOldGen: 50828K->50809K(51200K)] 56972K->56953K(59904K), [Metaspace: 3480K->3480K(1056768K)], 0.0094250 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
  16. Heap
  17. PSYoungGen total 8704K, used 6393K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  18. eden space 7168K, 89% used [0x00000000ff600000,0x00000000ffc3e4d0,0x00000000ffd00000)
  19. from space 1536K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x00000000ffe80000)
  20. to space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
  21. ParOldGen total 51200K, used 50809K [0x00000000fc400000, 0x00000000ff600000, 0x00000000ff600000)
  22. object space 51200K, 99% used [0x00000000fc400000,0x00000000ff59e630,0x00000000ff600000)
  23. Metaspace used 3512K, capacity 4502K, committed 4864K, reserved 1056768K
  24. class space used 391K, capacity 394K, committed 512K, reserved 1048576K

image.png
heap dump部分:

  1. PSYoungGen(也有的是def new generation): 年轻代total = eden + 1survivor
  2. eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
  3. 后面的内存地址分别指的是,起始地址,使用空间结束地址,整体空间结束地址

Metaspace就是元数据区了,下面的class space时Metaspace中专门放class的空间
reserved 预留出来的空间大小
committed 内存是分块的,目前占用的块的大小
capacity 目前指定的容量
used 真正使用的大小
image.png

调优前的基础概念

衡量JVM-GC的两个指标

吞吐量throughput:用户代码执行时间 /(用户代码执行时间 + 垃圾回收时间),吞吐量越大,JVM花在GC上的时间越少
响应时间:STW越短,响应时间越好
这俩指标几乎是不可能兼得的,
想要更高的吞吐量,就是要GC总体占用更少的CPU时间,这样的话,一旦需要STW,会STW很久(典型代表:CMS);
想要更快的响应时间,就是要STW很短,需要有线程去不断的标记处理对象,但是这样的话,吞吐量势必会降低,因为GC占用更多的CPU时间了.

所谓调优,首先确定,追求啥?
吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量等等
一般我们做web网站的,肯定是响应时间优先了.
科学计算/数据挖掘,吞吐量。吞吐量优先的一般选择PS + PO
响应时间:网站 GUI API (1.8 G1)

怎么调优?

  1. 根据需求进行JVM规划和预调优
  2. 优化JVM运行环境(慢,卡顿,一般是GC的STW过长了)
  3. 排查定位JVM运行过程中出现的各种问题(CPU 内存使用过高告警,OOM)

第三条其实分的不是特别开,规划的时候要考虑JVM运行环境,
生产中想要去优化JVM运行环境,需要先排查问题
实际中还是要根据业务,配合压测分析日志,逐步调整
其实说白了,就是要根据大量的案例,找到经验,去做一些规划和堆内存的调整,然后线上出问题了定位并解决问题.

根据需求进行JVM规划

调优,从业务场景开始,没有业务场景的调优都是耍流氓
无监控(压力测试,能看到结果),不调优
步骤:

  1. 熟悉业务场景,选择优化的维度

    (没有最好的垃圾回收器,只有最合适的垃圾回收器)
    响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
    吞吐量 = 用户时间 /( 用户时间 + 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<br />最多保留5GC日志文件,每个文件最大20M,超过5个时最新的文件会把最老的覆盖掉<br />或者每天产生一个日志文件
  7. 观察日志情况

案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

  1. 其实很多不同的服务器配置都能支撑(1.5G 16G),只是GC的次数影响吞吐量和响应时间
  2. 这里我们要找到最高峰的TPS,在最高峰TPS时,能够满足多少的响应时间
  3. 假设每天的高峰期在的1小时内产生36W订单,平均100个订单/秒
  4. 或者通过日志或者数据库发现,最顶峰是 1000个订单/秒
  5. 找到高峰期后,其实还是根据经验值,然后压测,因为还跟CPU算力有关
  6. 非要计算:一个订单产生需要多少内存?512K * 1000 500M内存
  7. 重要的还是要靠压测!

案例2:12306遭遇春节大规模抢票应该如何支撑?(架构问题)

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

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

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

优化JVM运行环境

说白了就是选用合适的GC,这个现代的内存越来越便宜了,建议扩内存,上G1
调整各个分代区域(年轻代,老年代)的空间,这个要根据实际业务,压测结果分析日志了.

  1. 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了

    1. 1.1 为什么原网站慢?<br /> 很多用户浏览数据,很多数据load到内存,内存不足,频繁GCSTW长,响应时间变慢<br /> 1.2 为什么会更卡顿?<br /> 内存越大,FGC时间越长<br /> 1.3 咋办?<br /> 换个垃圾回收器<br /> PS -> PN + CMS 或者 G1
  2. 系统CPU经常100%,如何定位问题?(面试高频)

    CPU100%那么一定有线程在占用系统资源,
    2.1 找出哪个进程cpu高(top)
    2.2 该进程中的哪个线程cpu高(top -Hp)
    看看是 工作线程占比高 还是 垃圾回收线程占比高
    2.3 导出该线程的堆栈 (jstack)
    2.4 查找哪个方法(栈帧)消耗时间 (jstack)

  3. 系统内存飙高,如何查找问题?(面试高频)

    1. 3.1 导出堆内存 (jmap)<br /> 3.2 分析 (jhat jvisualvm mat jprofiler )
  4. 如何监控JVM

    1. jstat jvisualvm jprofiler arthas top

    通过一个简单案例,掌握如何定位JVM运行中的问题

    通过一个案例理解常用工具

测试代码:
启动加JVM参数: -Xms200M -Xmx200M -XX:+PrintGC
java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01

  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,
  21. new ThreadPoolExecutor.DiscardOldestPolicy());
  22. public static void main(String[] args) throws Exception {
  23. executor.setMaximumPoolSize(50);
  24. for (;;){
  25. modelFit();
  26. Thread.sleep(100);
  27. }
  28. }
  29. private static void modelFit(){
  30. List<CardInfo> taskList = getAllCardInfo();
  31. taskList.forEach(info -> {
  32. // do something
  33. executor.scheduleWithFixedDelay(() -> {
  34. //do sth with info
  35. info.m();
  36. }, 2, 3, TimeUnit.SECONDS);
  37. });
  38. }
  39. private static List<CardInfo> getAllCardInfo(){
  40. List<CardInfo> taskList = new ArrayList<>();
  41. for (int i = 0; i < 100; i++) {
  42. CardInfo ci = new CardInfo();
  43. taskList.add(ci);
  44. }
  45. return taskList;
  46. }
  47. }

这段代码是有问题的,生产中我们也很可能遇到各种问题,大概流程是这样:

一般是运维团队首先受到报警信息(CPU Memory)

top命令找到问题进程

发现有个进程 内存不断增长 CPU占用率居高不下
记住这个进程ID
这个问题进程其实就是我们的JAVA进程

观察进程中的线程(top -Hp)(jstack)

  1. 代码中的线程名称一定要写的有意义,不然出了问题很难定位
  2. 这也是为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称,通过自定义ThreadFactory实现

top -Hp 进程ID,观察进程中的线程,哪个线程CPU和内存占比高
jstack JAVA进程ID,定位线程状况,重点关注:WAITING BLOCKED(死锁)
eg.
waiting on <0x0000000088ca3310> (a java.lang.Object)
假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁
怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE

  1. 1:写一个死锁程序,用jstack观察
  2. 2:写一个程序,一个线程持有锁不释放,其他线程等待,用jstack观察

观察JAVA进程的JVM信息

jinfo pid ,查看version,classpath和启动参数等

观察gc情况

jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用,但是收费)
jstat -gc 4655 500 : 每个500个毫秒打印GC的情况(4655是进程id)
怎么定位OOM问题的?生产中不能直接用图形界面,影响性能
1:已经上线的系统不用图形界面用什么?(cmdline,arthas)
2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)

查找最多的前xx个实例对象的类

jmap - histo 4655 | head -20
jmap -dump:format=b,file=xxx pid (这个要注意!!)
线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到)

使用MAT / jhat /jvisualvm 进行dump文件分析

https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
jhat -J-mx512M xxx.dump
浏览器直接访问 http://ip:7000 操作
拉到最后:找到对应链接
可以使用OQL查找特定问题对象

当然最终还是要找到代码的问题

图形界面观察gc情况

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

    jvisualvm远程连接

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

jprofiler (收费)

arthas在线排查工具

为什么需要在线排查?
在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。

java -jar arthas.jar 启动arthas,然后找到对应的Java进程,进入后可输入help查看帮助命令

  1. jvm观察jvm信息
  2. thread定位线程问题
  3. dashboard 观察系统情况
  4. heapdump + jhat分析
  5. jad反编译

    动态代理生成类的问题定位
    第三方的类(观察代码)
    版本问题(确定自己最新提交的版本是不是被使用)

  6. redefine 热替换

目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性
m() -> mm()
sc - search class
watch - watch method
没有包含的功能:jmap (jmap - histo 4655 | head -20)