开发问题解决方案
时间和时间戳的应用场景
Netty的ByteBuf基础知识
JVM生产环境调优工具
i18n注意事项
导致缓存击穿的代码示例
CPU高的问题排查方法
服务器性能监控
复费率读写接口标准
本文档使用 MrDoc 发布
-
+
首页
JVM生产环境调优工具
# JVM生产环境调优工具 # 0. 概述 - jps:虚拟机进程状况 - jmap:监控java堆内存和生成dump文件。 - jstack:Java堆栈跟踪工具,当CPU高时,可以用来排查问题。 - jinfo:查看正在运行的jvm参数和java系统参数。 - jstat:查看jvm的GC情况,堆内存各部分的使用量,以及加载类的数量。 ## docker 生产环境通常用docker部署,所有操作都需要在docker容器内进行。 ```bash # 如果linux发行版有bash,/bin/sh可以换成bash docker exec -it iot-exchange /bin/sh ``` 其中有一点需要特别注意,类似于 jmap 这些 JDK 工具依赖于 Linux 的 PTRACE_ATTACH,而是 Docker 自 1.10 在默认的 seccomp 配置文件中禁用了 ptrace,这是Docker 自 1.10 版本开始加入的安全特性。 主要有三种解决办法: - **–security-opt seccomp=unconfined** 简单暴力(不推荐),直接关闭 seccomp 配置。用法: ```bash docker run --security-opt seccomp:unconfined ... ``` - **–cap-add=SYS_PTRACE** 使用 –cap-add 明确添加指定功能: ```bash docker run --cap-add=SYS_PTRACE ... ``` - **Docker Compose 的支持** ```yaml version: '2' services: iot-exchange: cap_add: - SYS_PTRACE ``` 查看进程号: ``` # 查看进程号 jps -l # 可以看到进程号为1 1 /home/exchange/service-exchange-eiot.jar 19215 sun.tools.jps.Jps ``` # 1. jps  # 2. jmap jmap可以用来看内存信息,实例个数以及占用内存大小。不仅可以获取 dump 文件,还可以查询永久代、堆空间使用率、GC 收集器等信息。 下面以进程号1为例进行操作,jmap必须在docker内: ```bash # 查看堆信息 jmap -heap 1 # dump 堆到文件,format 指定输出格式,live 指明是活着的对象,file 指定文件名 jmap -dump:live,format=b,file=java.hprof 1 # 查看历史生成的实例 jmap -histo 1 # 查看当前存活的实例,执行过程中可能会触发一次full gc jmap -histo:live 1 # 可将日志输出到文件 jmap -histo 1 > /log.txt ``` > 注:如果网络和安全策略允许把log文件从服务器拷出来,可以先退出容器,然后把文件从容器中拷出来,方便阅读: `docker cp iot-exchange:/log.txt /log.txt` > log.txt文件如下:  - num:序号 - instances:实例数量 - bytes:占用空间大小 - class name:类名称,其中[C表示char[],[S表示short[],[I表示int[],[B表示byte[],[[I表示int[][]; ## 2.1 堆信息 ```bash jmap -heap 1 ``` 可以看到堆内存各区域的使用情况。  **异常信息** 如果报了如下错误,可以往上翻到第0节的docker启动命令 ``` Attaching to process ID 1, please wait... Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 1: Operation not permitted sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 1: Operation not permitted at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$LinuxDebuggerLocalWorkerThread.execute(LinuxDebuggerLocal.java:163) ... ``` ## 2.2 堆内存dump ```bash jmap -dump:format=b,file=eureka.dump 1 ``` 其中,eureka.dump是文件名,可以用jvisualvm、Intellij IDEA打开;如果习惯在MACOS用JProfiler分析,可以将 `.dump` 后缀改为 `.hprof`。 如下提示表示成功:  用jvisualvm打开,可以看到堆内存详情。  ### 2.2.1 示例 示例代码: ```java public class OOMTest { public static List<Object> list = new ArrayList<>(); // JVM设置 // -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/tyrival/jvm.dump public static void main(String[] args) { List<Object> list = new ArrayList<>(); int i = 0; int j = 0; while (true) { list.add(new User(i++, UUID.randomUUID().toString())); new User(j--, UUID.randomUUID().toString()); } } } ``` 打开文件进行分析,此处是JProfiler:  **异常信息** 有时候会报如下错误,通常是因为当前登录的与启动java程序的不是同一个用户。 ``` 1: Unable to open socket file: target process not responding or HotSpot VM not loaded The -F option can be used when the target process is not responding ``` 可以通过top命令查看该进程的用户信息。  比如这个java程序是用户acrel启动的,那么就要切换用户来执行jmap命令。 ```bash su acrel ``` ## 2.3 jhat jmap 有一个搭档叫 jhat,jhat 可以分析 jmap 生成的堆转储快照。jhat 生成的 dump 文件结果报告可以直接在浏览器中打开。 # 3. jstack jstack 用于生成虚拟机当前时刻的线程快照,也就是“线程 dump”文件。线程快照的意思就是线程当前的堆栈信息,生成线程快照的主要目的通常是为了定位线程阻塞的原因,如死循环、死锁、IO 资源问题等。如果发生线程阻塞,我们可以使用 jstack 来查看线程堆栈,就可以清晰地看到这些线程在后台执行什么任务、wait 什么 IO 资源。 | 参数 | 说明 | | --- | --- | | -F | 当正常输出的请求不被响应时,强制输出线程堆栈 | | -l | 除堆栈外,显示关于锁的信息 | 用jstack加进程id,可以用来排查CPU高的问题,比如出现死锁、内存不足导致GC。当我们发现程序的CPU高时:  可以进一步排查占用CPU高的线程: ```bash top -p [PID] -H ``` 由于程序里会有很多线程,有些线程是常驻并且占用高的,有些是非常驻并且占用高的,可以多观察一会,选出这两类线程,然后分别排查,此处观察后,我们选定的线程是7~10,以及183、184。  使用jstack检测: ```bash jstack 1 | grep -A 100 nid=0x7 ``` - 1表示进程号PID - 100表示打印线程堆栈信息中,这个线程所在行的后面100行 - nid=0x7表示线程号7的16进制,线程号如果是16,这边就是0x10,183则是0xb7 此处可以看到,线程7大概率是个MQTT连接线程,8~10也是类似的情况,而且这几个线程属于常驻的高占用,基本排除是这几个线程的问题。  - "nioEventLoopGroup-18-8":线程名 - prio=10:优先级=5 - tid=0x00007f22f8083800:线程id - nid=0x7ef2:线程对应的本地线程标识nid - java.lang.Thread.State: RUNNABLE:线程状态 接下来检查183和184线程: ```bash jstack 1 | grep -A 100 nid=0xb7 ``` 查看日志后,发现其中出现如下内容,可以定位到是AlarmTriggerDatagramBatchTask中,判断平台报警时需要获取设备投影,然后转为JSONObject。因为平台上有6000多个仪表,每条记录都要与redis的交互和数据解析,会消耗很高的带宽(redis集群在其他服务器)和CPU(反序列化JSON)。 > 注意: 打印日志时,如果一次看不到问题,可以多打印几次,并且尽量不要把打印行数设置得太低,因为springboot中的封装层级很多,比如只打印10行的话,很可能打出来的都是框架层面的调用,看不到业务类的调用。 >  184线程也是类似的情况,还有几个其他线程也差不多,有些是调用DeviceService查询设备信息等。 通过查看代码,主要逻辑是在AlarmTriggerRuleServiceImpl.handlerMessage()中,可以通过优化这里面的代码,减少与数据库/缓存不必要的交互,来降低资源消耗。 # 4. jinfo | 参数 | 说明 | | --- | --- | | 无参数 | 输出全部的参数和系统属性 | | -flag name | 输出对应名称的参数 | | -flag [+-]name | 开启或者关闭对应名称的参数 只有被标记为 manageable 的参数才可以被动态修改 | | -flag name=value | 设定对应名称的参数 | | -flags | 输出全部的参数 | | -sysprops | 输出系统属性 | ```bash # 查看某个 java 进程的具体参数的值 jinfo -flag name PID # 查看曾经赋过值的一些参数 jinfo -flags PID # 可以查得由System.getProperties()取得的参数 jinfo -sysprops PID ```
admin
2024年2月22日 09:13
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码