前言

  最近在做程序迁移到k8s集群的时候,线上程序频发OOM的问题,程序被系统干掉。原本容器镜像启动脚本里有根据/sys/fs/cgroup/memory/memory.limit_in_bytes来动态计算JVM堆内存限制(保留256M内存给JVM堆外和系统用)再设置到JAVA_OPTS环境变量(即-Xmx,JVM高版本有兼容容器化的方法),出现这种情况猜想就是堆外内存占用高的问题了。一般情况下,堆内存占用好跟踪(用JDK工具即可),堆外内存占用高由于情况多种多样跟踪就比较复杂了。本文不涉及详细的技术原理(了解请百度),只提供一个思路。

使用NMT

官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/nmt-8.html
简单来说,NMT(本地内存跟踪)是JDK自带的功能,方法简单,可以用它跟踪JVM内存使用情况。使用方法:

  1. 启用NMT(启动加参数):-XX:NativeMemoryTracking=detail
  2. 简单的执行命令:jcmd $pid VM.native_memory detail
    即可打印:Java Heap、Class、Thread、Code、GC、Compiler、Internal、Symbol、Native Memory Tracking、Arena Chunk 内存可用、已占用情况
    其中:Java Heap即-Xmx的设置和占用情况;
    Thread包含线程数量和线程本身占用内存(-Xss乘以线程数量)的情况;
    Internal包含直接内存(Direct Memory,受-XX:MaxDirectMemorySize限制,由NIO中的ByteBuffer.allocateDirect发起占用)

PS:如官方文档所见,“NMT不跟踪第三方native code内存分配和JDK类库”,如果怀疑native code内存占用高可使用下文gperftools方式

使用gperftools跟踪

代码仓库:https://github.com/gperftools/gperftools
依赖libunwind:https://github.com/libunwind/libunwind
首先需要编译,编译方法不赘述,但是网上各种使用方法感觉都很复杂,本文提出两个脚本简化使用过程。
首先,编译后的目录结构:
安装根目录/
  build/ 编译目录
    gperftools/ gperftools编译目录
    libunwind/ libunwind编译目录
  out/ 输出目录
  track.sh 跟踪执行脚本,后边跟java…执行命令
  view-dump.sh 查看报告脚本,后跟out目录下的dump文件路径
track.sh脚本:

TRACK_HOME=`dirname $0`
export LD_PRELOAD=${TRACK_HOME}/build/gperftools/lib/libtcmalloc.so
export LD_LIBRARY_PATH=${TRACK_HOME}/build/libunwind/lib:$LD_LIBRARY_PATH
export HEAPPROFILE=${TRACK_HOME}/out/track
cmd=$*
echo "enable track:${TRACK_HOME},cmd:${cmd}"
$cmd

view-dump.sh脚本

TRACK_HOME=`dirname $0`
if [ ! -f "$1" ];then
	echo "no file!"
	exit 1
fi
if [ ! -f "$JAVA_HOME/bin/java" ];then
	echo "no java!"
	exit 1
fi
${TRACK_HOME}/build/gperftools/bin/pprof --text $JAVA_HOME/bin/java $1

使用例子:
在程序运行目录下执行:{安装目录}/track.sh java -jar jenkins.war
待生成dump文件后执行:{安装目录}/view-dump.sh {安装目录}/out/track.0001.heap

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐