文章目录
- 一、场景介绍
- 二、基于 JRE 环境运行 Docker 容器的 JVM 调优
- 三、基于 JDK 环境运行 Docker 容器的 JVM 调优
一、场景介绍
- 在线上运行的应用程序,如果出现
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 .........