最近,我遇到了一个奇怪的情况:我的程序的内存使用量超出了用于堆的最大值。即使在运行GC之后,部分内存也不可用。我已经知道将JVM内存的一部分分配给本机内存,并将部分本机内存分配给C代码,但是我的程序中甚至没有一行本机代码。在多次检查和分析代码后,我发现了一个有趣的问题。在深入探讨问题之前,让我们看一下Java内存的概念。
Java中的内存管理
JVM将内存分为两个主要空间,堆和本机内存。堆空间用于分配Java对象,而本机内存是OS可用的内存。Java 7和8在内存管理模型上有一个关键区别。Java 7具有PermGen;PermGen是堆中的内存区域,JVM使用它来存储类元数据,静态内容,原始变量。Java 8取消了PermGen并添加了元空间。实际上,Metaspace和PermGen做相同的事情。主要区别在于PermGen是Java堆的一部分,而Metaspace不是该堆的一部分。而是元空间是本机内存的一部分, 仅受主机操作系统限制。
本机内存
本机内存是普通JVM堆之外的内存区域,但通常是操作系统为JVM进程保留的总内存的一部分。本机内存的一部分分配给了C堆;C堆是Java程序中本机C程序使用的空间。
本机内存跟踪(NMT)
NMT是一种内存跟踪工具,用于监视程序的本机内存使用情况。您可以使用jcmd实用程序访问NMT数据。NMT默认情况下未启用;您可以通过将以下参数添加到JVM选项来启用它:
-XX:NativeMemoryTracking=detail-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
为了在执行期间监视和跟踪内存更改,您应该使用以下命令:
jcmd VM.native_memory detail.diff
是Linux中Java程序的确切进程ID。在我的情况下,我的是'92165',因此我执行了以下命令:
jcmd 92165 VM.native_memory detail.diff.
考虑到监视是基于时间的方法,这意味着您应该将时间T2中的内存状态与时间T1中的状态进行比较,因此您需要使用以下命令将T1的状态标记为基线:
jcmd 92165 VM.native_memory baseline
我还按时间顺序执行了以下命令:
2020年12月19日9:07:10 IRS:jcmd 92165 VM.native_memory baseline
2020年12月19日下午12:07:10国税局:jcmd 92165 VM.native_memory detail.diff.
的输出是这样的:
reserved 3892 KB for Thread Stack from [Thread::record_stack_base_and_size()+0xca] [0x9f586000 - 0x9f791000] reserved 1680KB for Thread Stack from [Thread::record_stack_base_and_size()+0xca]
我尝试jcmd 92165 VM.native_memory detail.diff在不同的时间执行命令,并且每时每刻都得到不同的结果,这意味着我的本机内存使用量随着时间的推移而增长。
所以有什么问题?!
在上一部分中,我说过本机内存会随着时间增长,并且可以确定程序中没有本机代码,但是那是什么问题呢?我在监视的同时开始跟踪程序,我发现了一件有趣的事情:程序中使用了java.util.zip代码。一旦我到达此代码,本机内存的使用就会大大增加。问题很明显。该程序包在内部进行分类并使用本机代码。如果您在jdk8 source上查看JDK源代码,则可以找到类似这样的有趣代码,以及基于本机代码和C的某些类的实现:
src/share/native/java/util/zip/ZipFile.c JNIEXPORT jlong JNICALL Java_java_util_zip_ZipFile_open(JNIEnv *env, jclass cls, jstring name, jint mode, jlong lastModified, jboolean usemmap) { const char *path = JNU_GetStringPlatformChars(env, name, 0); char *msg = 0; jlong result = 0; int flag = 0; jzfile *zip = 0; if (mode & OPEN_READ) flag |= O_RDONLY; if (mode & OPEN_DELETE) flag |= JVM_O_DELETE; if (path != 0) { zip = ZIP_Get_From_Cache(path, &msg, lastModified); if (zip == 0 && msg == 0) { ZFILE zfd = 0; #ifdef WIN32 zfd = winFileHandleOpen(env, name, flag); if (zfd == -1) { /* Exception already pending. */ goto finally; } #else zfd = JVM_Open(path, flag, 0); if (zfd < 0) { throwFileNotFoundException(env, name); goto finally; } #endif zip = ZIP_Put_In_Cache0(path, zfd, &msg, lastModified, usemmap); } if (zip != 0) { result = ptr_to_jlong(zip); } else if (msg != 0) { ThrowZipException(env, msg); free(msg); } else if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, 0); } else { ThrowZipException(env, "error in opening zip file"); } finally: JNU_ReleaseStringPlatformChars(env, name, path); } return result; } JNIEXPORT jint JNICALL Java_java_util_zip_ZipFile_getTotal(JNIEnv *env, jclass cls, jlong zfile) { jzfile *zip = jlong_to_ptr(zfile); return zip->total; } JNIEXPORT jboolean JNICALL Java_java_util_zip_ZipFile_startsWithLOC(JNIEnv *env, jclass cls, jlong zfile) { jzfile *zip = jlong_to_ptr(zfile); return zip->locsig; } JNIEXPORT void JNICALL Java_java_util_zip_ZipFile_close(JNIEnv *env, jclass cls, jlong zfile) { ZIP_Close(jlong_to_ptr(zfile)); }
请记住,本机代码不仅在这些类中。有许多组件(例如与设备交互的组件或通信组件)可能使用本机代码。
原文链接:http://codingdict.com