线上问题处理干货(系列二)--如何分析docker Java项目内存泄露和溢出等问题?

01、线上JVM调优

1.主要参数

#JVM x参数
#非标准化参数
-Xint: 解释执行
-Xcomp:第一次使用就编译成本地代码
-Xmixed:混合模式,JVM自己来决定是否编译成本代码
#XX参数分类
格式:-XX:[+-]<name>表示启用或者禁用name属性比如:  -XX:+UseConcMarkSweepGC  -XX:UseG1GC  非Boolean类型格式:-XX:<name> = <value>表示name属性的值是value比如:  -XX:MaxGCPauseMillis=500  XX:GCTimeRatio=19  不是X参数,而是XX参数
-Xms等价于-XX:InitialHeapSize-Xmx等价于-XX:MaxHeapSize
查看JVM运行时参数
-XX:+PrintFlagsInitial-XX:+PrintFlagsFinal-XX:+UnlockExperimentalIVMOptions解锁实验参数

2.怎么查看项目呢?

    这里主要讲Java项目,其它项目不在考虑范围之内。

jps --查看有哪些线程

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

jps -l --查看详细的线程名称

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

    启动项目:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

jinfo -flag MaxHeapSize 22244 --查看jvm参数

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

jinfo -flags 22244 --查看jvm所有的参数

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

jstat 查看JVM统计信息--JIT编译jstat -compiler 22244
--类装载jstat -class 22244 1000 10
--垃圾回收jstat -gc 22244 1000 3

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

#如何导出内存映象文件#内存溢出字段导出-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=./
#使用jmap命令手动导出:jmap -dump:format=b,file=heap.hprof 22244
#查看死锁或者线程的状态jstack 22244 > 22244.txt
#使用jdk里面的jvisualvm 监控Java程序:
--可以监控本地tomcat,也可以远程,但是必须开发IP地址和端口。
#生成字节码javap -verbose Test.class > test.txt

 

    docker环境下怎么查看项目JVM信息,通过上面的命令?

 

    首先对spring项目进行打包:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

    同时创建一个Dockerfile文件和把Jar上传到服务上:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

# Dockerfile文件内容FROM openjdk:alpine 
RUN echo "Asia/Shanghai" > /etc/timezone#RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeENV TZ=Asia/Shanghai# 添加项目构建出来的 Jar 包ADD spring-0.0.1-SNAPSHOT.jar /root/bin/starter.jar
WORKDIR /root/bin
EXPOSE 8080
# 设定镜像的主命令CMD ["java", "-jar", "starter.jar", "--spring.profiles.active=dev", "-Djava.security.egd=file:/dev/./urandom"]

    构建镜像,命令如下:

# 注意后面有个“.”号docker build -t appliaction .

    如果出现如下错误,表明是镜像地址有问题:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    构建成功的镜像,如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    启动容器,命令如下:

docker run  -d -p 8080:8080 appliaction

    执行上面的命令,结果如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    一直报这个错误:

1: Unable to get pid of LinuxThreads manager thread

    很让人无语,pid线程为1明明存在,就是说get不到,然后查询资料,百度说是openjdk不支持上面哪些命令,这怎么可能。然后去官网找了资料,说是要把Dockfile的启动方式改为下面的:

ENTRYPOINT ["/bin/bash", "-c", "set -e && java -Xmx100m -jar /demo.jar"]

    这个方式我没有试过,因为不想动Dockerfile。所以就继续查找,官网说,这其实不是什么 Bug,而是 Docker 自 1.10 版本开始加入的安全特性。类似于 jmap 这些 JDK 工具依赖于 Linux 的 PTRACE_ATTACH,而是 Docker 自 1.10 在默认的 seccomp 配置文件中禁用了 ptrace。

    这篇文章介绍了整个的缘由以及应对方法:

JVM in Docker and PTRACE_ATTACH(

https://jarekprzygodzki.wordpress.com/2016/12/19/jvm-in-docker-and-ptrace_attach/)

 

    解决方案如下,主要提及三种:

 

    1.1 –security-opt seccomp=unconfined

    简单暴力(不推荐),直接关闭 seccomp 配置。用法:

docker run --security-opt seccomp:unconfined ...

    1.2 –cap-add=SYS_PTRACE

    使用 --cap-add 明确添加指定功能:

docker run --cap-add=SYS_PTRACE ...

    1.3 Docker Compose 的支持

    Docker Compose 自 version 1.1.0 (2015-02-25) 起支持 cap_add。官方文档:cap_add, cap_drop。用法:

     docker-compose.yml 改写后文件内容如下(内容部分省略):

version: '2'services:  mysql:    ...  api:    ...    cap_add:      - SYS_PTRACE

    所以,我打算采用下面的方式:

docker run --cap-add=SYS_PTRACE -d -p 8080:8080 appliaction

    得出的结论还是,同样的结果:

    1: Unable to get pid of LinuxThreads manager thread

    最后思考了一下,能不能把pid为1,改成不是1,改成项目的那样,22244。然后怎么设置docker 程序的pid呢?命令如下:

docker run --security-opt seccomp:unconfined -d -p 8080:8080 --pid=host appliaction

    生成内存文件,OK。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

02、怎么分析内存泄露?

1.调用spring项目中的接口:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    然后,使用top命令进行查看,内存CPU已经爆炸了,结果如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    哪怎么导出,项目的内存相关信息的文件呢?

    首先,使用命令生成内存溢出的文件:

jmap -dump:format=b,file=heap1.hprof 23427

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    然后,把文件拷贝到服务器上,如下:

docker cp 438559ba685e:/root/bin/heap1.hprof /usr/local/docker/appliaction/heap1.hprof

    回到最初的问题,我们怎么分析内存泄露文件呢?

    我们将使用Eclipse Memory Analysis进行分析内存文件。下载地址:

http://www.eclipse.org/mat/downloads.php

    

    第二步:开始分析hprof文件

    导入成功之后,如下显示:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    将hprof文件导入Eclipse Memory Analyzer可看到上图:

1.Dominator Tree:可以列出占用内存最大的线程,以及线程下面的那些对象占用的空间

2.Leak Suspects:MA分析出的可能导致内存溢出的地方。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    通过Dominator Tree,我们可以看到,MemoryController方法上有问题,user对象太多导致内存溢出,CPU飙升。

    Shallow Heap :一个对象所占用的内存,不包含对其他对象的引用

    Retained Heap :是shallow Heap的总和(单个对象占用内存*此对象的个数),也就是该对象被GC之后所能回收到内存的总和分析方式:

1.查找线程下占用内存较大的对象(上图右边)

2.定位对象在代码里出现的位置(上图左边)

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    第四步:Leak Suspects

    查看线程的相关日志,定位导致内存溢出的代码位置。

    通过图形知道,在MemoryController上有个userList上内存暴涨。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    代码如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    上面的代码就是用来做测试的,没有分析的必要。项目实际中,还需要分析为啥会出这个问题。

    内存死锁的分析也是这样的。

jstack 23427 > 23427.txt
docker cp 438559ba685e:/root/bin/23427.txt /usr/local/docker/appliaction/23427.txt
 sz 23427.txt

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

 

03、使用jdk的jvisualvm查看使用内存使用情况

    第一步,找到jdk的安装目录,如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    启动项目,本地查看。找到相对应的pid,如果是多个项目中,我们可以查看jvm的相关参数等信息,还可以查看图形化界面。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    还可以下载堆内存等信息。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    执行以下程序,内存飙升:

http://localhost:8080/heap
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    点击堆,也可以查看是哪个user,出现了问题。

    

    如果是实际项目呢?我们可以选择远程连接,只要是Java项目的都可以:

    如果出现了以下,错误:

    需要在构建的时候,指定环境变量,如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章