通过JNI启动JVM的Linux进程占用4GiB虚拟内存问题排查
在Red Hat Linux机器上,通过JNI实例化JVM的C++进程,仅启动进程(未实例化对象或调用任何库),用ps命令查看VSZ(虚拟内存大小)竟达到约4GiB。但相同Java版本的AIX系统中,VSZ仅为8MB,需要解释该差异原因。
复现代码
C++代码
// C++ #include <jni.h> #include "unistd.h" // sleep int main() { JavaVMInitArgs vm_args = {}; JNIEnv* env_ = 0; JavaVM* jvm_ = 0; vm_args.version = JNI_VERSION_1_8; jint res = JNI_CreateJavaVM(&jvm_, (void**)&env_, &vm_args); sleep(10); return 0; }
Makefile
# Makefile JAVA_HOME = /etc/alternatives/java_sdk CPPFLAGS = -I$(JAVA_HOME)/include/linux -I$(JAVA_HOME)/include LDFLAGS = -L$(JAVA_HOME)/jre/lib/amd64/server -ljvm all: test test: main.cpp $(CXX) $(CPPFLAGS) $< -o $@ $(LDFLAGS)
测试结果
Red Hat Linux环境
执行./test后等待2-3秒,运行以下命令:
$ ps -Ao"pid,time,vsize,rssize,pcpu,args" | grep -E "PID|test" | grep -v grep PID TIME VSZ RSS %CPU COMMAND 372587 00:00:00 4078292 25304 1.2 ./test
VSZ达到4GB,延长sleep时长也无变化,说明这不是初始化瞬间的临时现象,长期运行此类进程可能引发JVM堆空间相关问题。
AIX环境
$ ps -Ao"pid,time,vsize,rssize,pcpu,args" | grep -E "PID|test" | grep -v grep PID TIME VSZ RSS %CPU COMMAND 37552202 00:00:00 8544 8548 0.0 ./test
VSZ仅为8MB。
环境信息
两个系统使用的Java版本一致:
$ java -version openjdk version "1.8.0_312" OpenJDK Runtime Environment (build 1.8.0_312-b07) OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
Red Hat系统版本:
$ cat /etc/os-release NAME="Red Hat Enterprise Linux" VERSION="8.3 (Ootpa)" ID="rhel" ID_LIKE="fedora" VERSION_ID="8.3" PLATFORM_ID="platform:el8" PRETTY_NAME="Red Hat Enterprise Linux 8.3 (Ootpa)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:redhat:enterprise_linux:8.3:GA" HOME_URL="https://www.redhat.com/" BUG_REPORT_URL="https://bugzilla.redhat.com/" REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8" REDHAT_BUGZILLA_PRODUCT_VERSION=8.3 REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" REDHAT_SUPPORT_PRODUCT_VERSION="8.3"
原因分析
这个差异主要源于Linux和AIX系统的虚拟内存管理机制不同,以及OpenJDK在不同平台上的内存初始化策略差异:
Linux的内存预分配策略
在Linux上,OpenJDK的Server VM默认会预分配整个Java堆的虚拟内存空间(即使实际还未使用物理内存)。JDK 8中Server VM的默认堆大小上限是4GB(-Xmx4g),这就是VSZ显示4GB的原因。虚拟内存预分配是Linux下JVM的默认行为,目的是减少后续内存分配时的开销,确保内存地址空间连续。需要注意的是,VSZ是进程的虚拟内存总大小,包含已分配但未实际使用的内存块,而RSS才是实际占用的物理内存(测试中RSS仅25MB左右),所以该现象不会立即消耗物理内存,但可能导致进程虚拟内存空间占用过大,在内存紧张的环境下影响其他进程。AIX的内存管理机制
AIX采用延迟内存分配(lazy allocation)策略,JVM在AIX上不会预先分配整个堆的虚拟内存,而是在实际需要时才逐步分配。此外,AIX的ps命令统计VSZ的方式也和Linux不同,它只统计已经实际提交(committed)的内存,而非预分配的虚拟内存空间,所以显示的VSZ远小于Linux。JVM参数的影响
如果在Linux上手动指定较小的堆大小(比如-Xmx256m),VSZ会相应减小。可以通过修改JavaVMInitArgs添加启动参数验证:vm_args.nOptions = 1; const char* options[] = {"-Xmx256m"}; vm_args.options = (JavaVMOption*)malloc(vm_args.nOptions * sizeof(JavaVMOption)); for (int i = 0; i < vm_args.nOptions; i++) { vm_args.options[i].optionString = (char*)options[i]; }修改后进程的VSZ会显著降低。
内容的提问来源于stack exchange,提问作者ABu

