诊断docker container 里面的 Java 进程内存泄漏
在公司有台台式机, 平时办公不用它, CPU 性能还可以, 有16G 内存, 为了充分利用它, 在它上面装了docker, 然后通过container的方式安装了不少工具, 比如: MySQL+phpmyadmin, ElasticSearch+kibana, MongoDB+mongoUI, Splunk, Clickhouse, Prometheus, Jupter Notebook, Neo4J, Janusgraph, Nginx 等应用, 平时做个测试或者小工具, 直接连这些应用, 非常方便. 直到有一天, 给它接了一个外接显示器, 第二天早上看到下面这个系统日志, 发现这个上面有个应用一直OOM.
当时看到的窗口截图:
从上面截图可以看到有个java 应用一直OOM, 然后被系统 OOM killer 杀掉, 从这个截图还可以看到 pid 和应用的命令是 java. 因为被杀的很快, 所以这个pid 一直更新, 所以只能从 java 的提示找起.
首先, 我们通过top 确实看到有个java 应用使用了2个CPU, 也可以看到 load 也不低 (6左右), 如下图:
我们通过下面的命令行找到这个进程的启动命令 (因为这个进程一直被杀, 它的进程号一直在变, 且进程号最大, 所以在最后一行):
supra@suprabox:/proc/3677$ cat /proc/$(pgrep java | tail -1)/cmdline
/usr/share/elasticsearch/jdk/bin/java-Xshare:auto-Des.networkaddress.cache.ttl=60-Des.networkaddress.cache.negative.ttl=10-XX:+AlwaysPreTouch-Xss1m-Djava.awt.headless=true-Dfile.encoding=UTF-8-Djna.nosys=true-XX:-OmitStackTraceInFastThrow-XX:+ShowCodeDetailsInExceptionMessages-Dio.netty.noUnsafe=true-Dio.netty.noKeySetOptimization=true-Dio.netty.recycler.maxCapacityPerThread=0-Dio.netty.allocator.numDirectArenas=0-Dlog4j.shutdownHookEnabled=false-Dlog4j2.disable.jmx=true-Dlog4j2.formatMsgNoLookups=true-Djava.locale.providers=SPI,COMPAT--add-opens=java.base/java.io=ALL-UNNAMED-XX:+UseG1GC-Djava.io.tmpdir=/tmp/elasticsearch-4941307185827500736-XX:+HeapDumpOnOutOfMemoryError-XX:+ExitOnOutOfMemoryError-XX:HeapDumpPath=data-XX:ErrorFile=logs/hs_err_pid%p.log-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m-Des.cgroups.hierarchy.override=/-Xms7948m-Xmx7948m-XX:MaxDirectMemorySize=4167041024-XX:G1HeapRegionSize=4m-XX:InitiatingHeapOccupancyPercent=30-XX:G1ReservePercent=15-Des.path.home=/usr/share/elasticsearch-Des.path.conf=/usr/share/elasticsearch/config-Des.distribution.flavor=default-Des.distribution.type=docker-Des.bundled_jdk=true-cp/usr/share/elasticsearch/lib/*org.elasticsearch.bootstrap.Elasticsearch-Ediscovery.type=single-nodes
通过这个结果, 我们很快定位到这个一个 elasticsearch 应用里面的 java 进程, 还可以看到里面设置了 heap的初始值和最大值都将近8G, 而我这个总共16G的物理机(没设置swap), 其它进程加起来大概用了8G多, 所以这个 elasticsearch 根本不能申请到8G的的heap, 所以频繁被杀.
通过 docker ps
我们可以明显看到这个 elasticsearch up time 只有10秒. 如下图:
既然定位到了应用, 去修复它, 就有了方向. 官方文档关于 heap 的设置: https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html#heap-size-settings
其中说到:
"By default, Elasticsearch automatically sets the JVM heap size based on a node’s roles and total memory. We recommend the default sizing for most production environments."
所以, 这个 elasticsearch 只知道这个物理机的内存情况, 却不知道它上面运行的其他进程占用内存的情况, 以为只有它自己.
于是我们可以在启动时候, 给它加上heap 设置的参数: ES_JAVA_OPTS="-Xms2g -Xmx2g"
, 如下:
sudo docker network create elastic
sudo docker run --restart always --name es01 --network elastic -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -d -e ES_JAVA_OPTS="-Xms1g -Xmx1g" docker.elastic.co/elasticsearch/elasticsearch:7.16.1
sudo docker run --restart always --name kib01 --network elastic -p 5601:5601 -e "ELASTICSEARCH_HOSTS=http://es01:9200" -d docker.elastic.co/kibana/kibana:7.16.1
于是, 这个机器 load 又降了下来, 恢复了宁静.