Docker 容器中的 JAVA 应用如何进行 JVM 调优

本文详细介绍了在Docker环境下基于JRE和JDK运行的应用如何进行JVM调优。针对JRE环境,通过在容器内部安装OpenJDK以使用jmap和jstack等工具进行内存分析;对于JDK环境,直接利用内置工具进行调优。同时提到了Arthas在Docker中的使用,并强调了避免应用以PID 1运行以确保工具的正常使用。
摘要由CSDN通过智能技术生成

一、场景介绍
  • 在线上运行的应用程序,如果出现 OOM 等等 JVM 的异常,我们需要通过灾难现场来判断问题代码的所在

  • 如果是传统的 Tomcat 等服务器部署,则可以直接使用服务器的 JDK 环境变量自带的例如:jmap jstack 这些内存分析工具进行问题的分析

  • 如果是通过 Docker 容器部署,想去查看容器内应用的堆栈信息,则需要根据不同容器化启动方式来具体分析

二、基于 JRE 环境运行 Docker 容器的 JVM 调优
  • 场景一:基于 Alpine JRE 基础镜像运行的 Docker 容器

    • 基于该场景的容器,由于采用的是精简版的 JRE,容器内部没有类似 jmap jstack 这些内存分析工具

    • 如果想通过 jmap jstack 这些内存分析工具进行 JVM 调优,我们必须在容器内部添加 JDK 的环境

    • 添加方式可以是在线安装方式或者将 JDK 环境复制到容器内部的方式,根据自己的实际情况来抉择,具体操作步骤如下:

      1. 进入容器内部,查看该容器内部是否拥有类似 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 #
        
      2. 我们可以尝试在容器内部安装 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
        
      3. 通过新增的 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)    
        
      4. 其它问题分析和解决方案

        • 考虑到以后可能需要多次使用本次下载下来的 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 容器

    1. 基宿主机的 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 #
      
    2. 我们可以通过更换宿主机的 JREJDK 环境,或者进入容器内部安装 OpenJDK 来进行 JVM 调优

    3. 以上方案一可以参考网上资料。搜索关键字 Linux 安装配置 JDK 环境,方案二可以参考本段场景一

三、基于 JDK 环境运行 Docker 容器的 JVM 调优
  • 场景一:基于 Alpine OpenJDK 基础镜像运行的 Docker 容器

    1. 基于 OpenJDK 基础镜像运行的 Docker 容器,本身拥有 jmap jstack 这些内存分析工具

    2. 我们可以直接采用 jmap jstack 这些内存分析工具来进行应用的 JVM 调优

    3. 我们还可以通过阿里开源的 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 容器

    1. 基于宿主机的 JDK 环境变量运行的 Docker 容器,本身拥有 jmap jstack 这些内存分析工具

    2. 我们可以直接采用 jmap jstack 这些内存分析工具来进行应用的 JVM 调优

    3. 我们还可以通过阿里开源的 Arthas 来进行 JVM 调优

    4. 切记在此场景下通过 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 .........
      
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值