开发者学堂课程【JVM 整体架构及内存调优 :JVM 整体架构及内存调优(三)】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/1207/detail/18174
JVM 整体架构及内存调优
五、JVM 调优
1.参数
(1)标准参数(-):带一个杠的参数都叫标准参数。
如果是 windows 系统,在 JDK 的 command 里可以看到所有的参数。最常用的有 -verbose 参数,可以根据 java -verbose 引用 class,class 会输出 JVM 的一些相关信息。如果遇到代码类找不到的问题,则可以通过 -verbose:class 打印去观察类是否存在。另外也可以用 -verbose:gc 去打印每次 gc 的情况;同时也还有 -verbose:jni 可以输出 native 方法,调用相关的信息。
以上是 JVM 一些本地方法接口。
(2)非标准参数:(-X)
常见有:-Xloggc,将GC状态记录在文件中(带时间截);
-Xms,设置初始 java 堆大小;
-Xmx,设置最大 java 堆大小;
-Xss ,设置 java 线程堆栈大小;
(3)非 stable 参数( -XX )参数可以被松散的聚合成三类:
①行为参数( Behavioral Options ):用于改变JVM的一些基础行为 ;
内含垃圾回收器,表格中用黑体标识的参数代表JVM中 GC 执行的三种方式:串行、并行、并发。
②性能调优( Performance Tuning ):用于JVM的性能调优;
新生代的大小,新生代对象能占用的最大值,新生代对象生成时占用内存的默认值极易新生代和老年代的比例,可以通过性能调优参数进行设置。
③调试参数( Debugging Options):般用于打开跟踪、打印、输出等
JVM 参数,用于显示JVM更加详细的信息;
出现问题时可以使用,例如指定导出堆信息时的路径或文件名、当首次遭遇 OOM 时导出此时堆中相关信息、遇到 Ctrl-Break 后打印井发锁的相关信息,与jstack -l功能相同。
2.案例
(1)JVM 调优 -jstack 命令
某个系统会有一些大量的消息堆积,需要分析有哪些堆栈信息。
分析过程:
①登陆订阅方所在的服务器,使用 ps -ef lgrep java 或 jps -lm 来查询找到订阅方堆积的应用的 pid。
②使用 jstack -l pid > /tmp/pid.jstack 打印订阅方的线程堆栈信息进行查看
如下图:
在导出的 jstack 文件中,可以得到一些对账信息,waiting 的数量达到了600+的线程,会存在资源消耗问题,在一直阻塞的过程中会影响到整个程序的后续进行。
总结:
大量线程( 600+ )线程处于 WAITING 状态,阻塞在试图从任务队列中取任务(LinkedBlockingQueue.take),这个任务队列指的是 ThreadPoolExecutor 的线程池启动的线程任务队列;也就是说,这些线程都是空闲状态,因为使用完成之后一直没有关闭,导致一直在等着任务的到来,导致订阅端消费者无法消费消息。
建议:
①使用 ExecutorService 提交的线程任务,也要记得关闭,不关闭会有大量的线程,则会出现消息堆积的问题;
②启动新线程的时候,最好给线程起个名字,这样线程堆栈的问题排查更加容易;
③MQ 消息消费端打印详细日志,消费时间、内容等(消费异常的消息也要打印)。
如果只使用 jstack 信息,没有业务日志,则还需要返回重新查看业务代码来判断问题;如果上述两步完成的较好则解决问题更加迅速。
3.Arthas (阿尔萨斯)能做什么?
作用:得知类从哪个 jar 包加载的、为什么会报各种类相关的 Exception、改的代码为什么没有执行到、程序的执行时间,监控 JVM 实时运行的状态。
官网: https://arthas aliyun.com/doc/
用户案例:https://github.com/alibaba/arthas/issues?q=label%3Auser-case
(1)快速入门:启动 math-game、启动 arthas、查看 dashboard、通过 thread 命令来获取到 math-game 进程的Main Class、通过 jad 来反编译 Main Class。
Dashboard 可以看到 JVM 的一些相关信息
(2)命令
例如 sc,可以看 class 的信息,后面可以添加参数
watch 命令:可以观察到函数的调用情况,可以观察到函数入参、出参的信息。
(3)案例分析:
具有调用链路为三百多毫秒,实际上数据库的执行在几十毫秒左右,剩下的均在程序的运行过程中消耗,这显然是不合理的,所以进行一些优化。通过 Trace 命令优化之后,每个环节具体消耗的时间都清晰可见。Trace 中可以看到消耗最多的是封装的一个SimpleDateFormat. foreatDate() .
原先的代码是 String 类型,每一次编译时都要做 foreatDate,所以将时间点集合Set dateSet 改为Set ,这样会有明显的提升,优化了50毫秒。