一、场景介绍
-
在线上运行的应用程序,如果出现
OOM
等等JVM
的异常,我们需要通过灾难现场来判断问题代码的所在 -
如果是传统的
Tomcat
等服务器部署,则可以直接使用服务器的JDK
环境变量自带的例如:jmap
jstack
这些内存分析工具进行问题的分析 -
如果是通过
Docker
容器部署,想去查看容器内应用的堆栈信息,则需要根据不同容器化启动方式来具体分析
二、基于 JRE 环境运行 Docker 容器的 JVM 调优
-
场景一:基于
Alpine JRE
基础镜像运行的Docker
容器-
基于该场景的容器,由于采用的是精简版的
JRE
,容器内部没有类似jmap
jstack
这些内存分析工具 -
如果想通过
jmap
jstack
这些内存分析工具进行JVM
调优,我们必须在容器内部添加JDK
的环境 -
添加方式可以是在线安装方式或者将
JDK
环境复制到容器内部的方式,根据自己的实际情况来抉择,具体操作步骤如下:-
进入容器内部,查看该容器内部是否拥有类似
jmap
jstack
这些内存分析工具# 进入容器内部 [root@node42 ~]# docker exec -it example-service /bin/sh # 查看是否拥有调优命令 /opt/java/example-service # jmap /bin/sh: jmap: not found /opt/java/example-service # jstack /bin/sh: jstack: not found /opt/java/example-service #
-
我们可以尝试在容器内部安装
OpenJDK
来解决# 进入容器内部 docker exec -it container-name /bin/sh # 更新 apk 源 /opt/java/example-service # echo https://mirrors.aliyun.com/alpine/v3.14/main > /etc/apk/repositories && echo https://mirrors.aliyun.com/alpine/v3.14/community >> /etc/apk/repositories # 安装 OpenJDK /opt/java/example-service # apk update && apk upgrade && apk add openjdk8 # 查看 OpenJDK 是否安装成功 /opt/java/example-service # ls -l /usr/lib/jvm/java-1.8-openjdk total 184 -r--r--r-- 1 root root 1522 Apr 20 15:03 ASSEMBLY_EXCEPTION -r--r--r-- 1 root root 19274 Apr 20 15:03 LICENSE -r--r--r-- 1 root root 155003 Apr 20 15:03 THIRD_PARTY_README drwxr-xr-x 1 root root 4096 Jul 19 16:05 bin drwxr-xr-x 3 root root 132 Jul 19 16:05 include drwxr-xr-x 1 root root 95 Jul 19 16:04 jre drwxr-xr-x 1 root root 126 Jul 19 16:05 lib -rw-r--r-- 1 root root 84 Apr 20 15:03 release # 进入 OpenJDK 二进制目录(由于在线安装的没有配置环境变量,这里就直接在二进制目录进行 JVM 参数排查操作) /opt/java/example-service # cd /usr/lib/jvm/java-1.8-openjdk/bin
-
通过新增的
OpenJDK
来分析内存信息/usr/lib/jvm/java-1.8-openjdk/bin # ps -ef PID USER TIME COMMAND 1 root 2h16 java -jar ./example-service.jar 891 root 0:00 sh 1106 root 0:00 ps -ef /opt/java/example-service # /usr/lib/jvm/java-1.8-openjdk/bin/./jstack 6 2021-07-19 16:28:09 Full thread dump OpenJDK 64-Bit Server VM (25.212-b04 mixed mode): "Keep-Alive-Timer" #49 daemon prio=8 os_prio=0 tid=0x00007f76b08ff000 nid=0x62 waiting on condition [0x00007f768925d000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at sun.net.www.http.KeepAliveCache.run(KeepAliveCache.java:172) at java.lang.Thread.run(Thread.java:748)
-
其它问题分析和解决方案
-
考虑到以后可能需要多次使用本次下载下来的
OpenJDK
,我们可以将容器中的OpenJDK
拷贝出来,方便后续再次使用时,再次拷贝到容器内部使用# 将 OpenJDK 拷贝到宿主机 docker cp example-service:/usr/lib/jvm/java-1.8-openjdk . # 将宿主机的 OpenJDK 拷贝到容器内部 docker cp java-1.8-openjdk example-service:/usr/lib/jvm/
-
如果分析过程中出现如下问题:
/usr/lib/jvm/java-1.8-openjdk/bin # ps -ef PID USER TIME COMMAND 1 root 2h16 java -jar ./example-service.jar 891 root 0:00 sh 1106 root 0:00 ps -ef /usr/lib/jvm/java-1.8-openjdk/bin # ./jstack 1 1: Unable to get pid of LinuxThreads manager thread /usr/lib/jvm/java-1.8-openjdk/bin # ./jmap -histo 1 1: Unable to get pid of LinuxThreads manager thread /usr/lib/jvm/java-1.8-openjdk/bin #
请采用
docker --init
的方式启动容器后,再次尝试即可
-
-
-
-
场景二: 基于宿主机的
JRE
环境运行的Docker
容器-
基宿主机的
JRE
环境运行的Docker
容器,同样也没有jmap
jstack
这些内存分析工具[root@node42 ~]# docker exec -it example-service /bin/sh /opt/java/example-service # jmap /bin/sh: jmap: not found /opt/java/example-service # jstack /bin/sh: jstack: not found /opt/java/example-service #
-
我们可以通过更换宿主机的
JRE
为JDK
环境,或者进入容器内部安装OpenJDK
来进行JVM
调优 -
以上方案一可以参考网上资料。搜索关键字
Linux 安装配置 JDK 环境
,方案二可以参考本段场景一
-
三、基于 JDK 环境运行 Docker 容器的 JVM 调优
-
场景一:基于
Alpine OpenJDK
基础镜像运行的Docker
容器-
基于
OpenJDK
基础镜像运行的Docker
容器,本身拥有jmap
jstack
这些内存分析工具 -
我们可以直接采用
jmap
jstack
这些内存分析工具来进行应用的JVM
调优 -
我们还可以通过阿里开源的 Arthas 来进行
JVM
调优
P.S
-
Arthas
也是通过JDK
中的jps
去查找JAVA
进程的,所以运行时JAVA
环境变量必须要有jps
这些命令 -
很多时候,应用在
Docker
里出现Arthas
无法工作的问题,是因为应用没有安装JDK
,而是安装了JRE
。如果只安装了JRE
,则会缺少很多JAVA
的命令行工具和类库,Arthas
也没办法正常工作。下面介绍两种常见的在Docker
里使用JDK
的方式FROM openjdk:8-jdk # 或者 FROM openjdk:8-jdk-alpine
-
Docker
容器内部的应用的PID=1
可能会出现无法Attach
,请尽量采用非PID=1
的方式运行应用,应用PID=1
为系统默认进程docker run --init .........
-
-
场景二:基于宿主机的
JDK
环境运行的Docker
容器-
基于宿主机的
JDK
环境变量运行的Docker
容器,本身拥有jmap
jstack
这些内存分析工具 -
我们可以直接采用
jmap
jstack
这些内存分析工具来进行应用的JVM
调优 -
我们还可以通过阿里开源的 Arthas 来进行
JVM
调优 -
切记在此场景下通过
Arthas
进行调优,请采用和宿主机拥有相同root
权限的命令启动容器,不然会出现无法Attach
的情况,不管内部应用的PID
是否为1
[INFO] Try to attach process 1 Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.io.IOException: Bad pathname at java.lang.ClassLoader.findBootstrapClass(Native Method) at java.lang.ClassLoader.findBootstrapClassOrNull(ClassLoader.java:1008) at java.lang.ClassLoader.loadClass(ClassLoader.java:407) at java.lang.ClassLoader.loadClass(ClassLoader.java:405) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) at java.lang.ClassLoader.loadClass(ClassLoader.java:351) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:601) [ERROR] attach fail, targetPid: 1
docker run --privileged=true .........
P.S
-
Docker
容器内部的应用请尽量采用非PID=1
的方式运行应用,应用PID=1
为系统默认进程docker run --init --privileged=true .........
-