理解 JVM 如何使用 Windows 和 Linux 上的本机内存

时间:2023-01-02 19:58:45

         虚拟地址空间大小可能比处理器的物理地址大小更小。32 位 Intel x86 最初拥有的 32 位物理地址仅允许处理器寻址 4GB 存储空间。后来,添加了一种称为物理地址扩展(Physical Address Extension,PAE)的特性,将物理地址大小扩大到了 36 位,允许安装或寻址至多 64GB RAM。PAE 允许操作系统将 32 位的 4GB 虚拟地址空间映射到一个较大的物理地址范围,但是它不允许每个进程拥有 64GB 虚拟地址空间。这意味着如果您将大于 4GB 的内存放入 32 位 Intel 服务器中,您将无法将所有内存直接映射到一个单一进程中。

类和类加载器
        Java 应用程序由一些类组成,这些类定义对象结构和方法逻辑。Java 应用程序也使用 Java 运行时类库(比如 java.lang.String)中的类,也可以使用第三方库。这些类需要存储在内存中以备使用。
存储类的方式取决于具体实现。Sun JDK 使用永久生成(permanent generation,PermGen)堆区域。Java 5 的 IBM 实现会为每个类加载器分配本机内存块,并将类数据存储在其中。

        从最基本的层面来看,使用更多的类将需要使用更多内存。(这可能意味着您的本机内存使用量会增加,或者您必须明确地重新设置 PermGen 或共享类缓存等区域的大小,以装入所有类)。记住,不仅您的应用程序需要加载到内存中,框架、应用服务器、第三方库以及包含类的 Java 运行时也会按需加载并占用空间。

        Java 运行时可以卸载类来回收空间,但是只有在非常严酷的条件下才会这样做。不能卸载单个类,而是卸载类加载器,随其加载的所有类都会被卸载。只有在以下情况下才能卸载类加载器:
Java 堆不包含对表示该类加载器的 java.lang.ClassLoader 对象的引用。
Java 堆不包含对表示类加载器加载的类的任何 java.lang.Class 对象的引用。
在 Java 堆上,该类加载器加载的任何类的所有对象都不再存活(被引用)。
需要注意的是,Java 运行时为所有 Java 应用程序创建的 3 个默认类加载器( bootstrap、extension 和 application )都不可能满足这些条件,因此,任何系统类(比如 java.lang.String)或通过应用程序类加载器加载的任何应用程序类都不能在运行时释放。

JNI(Java Native Interface)
       JNI 支持本机代码(使用 C 和 C++ 等本机编译语言编写的应用程序)调用 Java 方法,反之亦然。Java 运行时本身极大地依赖于 JNI 代码来实现类库功能,比如文件和网络 I/O。JNI应用程序可能通过 3 种方式增加 Java 运行时的本机内存占用:

       JNI 应用程序的本机代码被编译到共享库中,或编译为加载到进程地址空间中的可执行文件。大型本机应用程序可能仅仅加载就会占用大量进程地址空间。
本机代码必须与 Java 运行时共享地址空间。任何本机代码分配或本机代码执行的内存映射都会耗用 Java 运行时的内存。

多线程

        每启用一个线程会占用 1MB 的内存空间,2000 个你可以算一下需要多少个内存空间。这还只是线程占用的空间,至于 Java 进程本身,以及数据处理占用的空间都还没有算上去。操作系统对于每个进程所允许的最大线程数也是有限制的,默认情况下 Windows 不会超过 2000 个,Linux 系统在 1000 个左右。

        当出现 java.lang.OutOfMemoryError 或看到有关内存不足的错误消息时,要做的第一件事是确定哪种类型的内存被耗尽。最简单的方式是首先检查 Java 堆是否被填满。如果 Java 堆未导致 OutOfMemory 条件,那么您应该分析本机堆使用情况。

 

是什么在使用本机内存?
一旦确定本机内存被耗尽,下一个逻辑问题是:是什么在使用这些内存?这个问题很难回答,因为在默认情况下,Windows 和 Linux 不会存储关于分配给特定内存块的代码路径的信息。
当尝试理解本机内存都到哪里去了时,您的第一步是粗略估算一下,根据您的 Java 设置,将会使用多少本机内存。如果没有对 JVM 工作机制的深入知识,很难得出精确的值,但您可以根据以下指南粗略估算一下:
Java 堆占用的内存至少为 -Xmx 值。
每个 Java 线程需要堆栈空间。堆栈空间因实现不同而异,但是如果使用默认设置,每个线程至多会占用 756KB 本机内存。
直接 ByteBuffer 至少会占用提供给 allocate() 例程的内存值。
如果总数比您的最大用户空间少得多,那么您很可能不安全。Java 运行时中的许多其他组件可能会分配大量内存,进而引起问题。但是,如果您的初步估算值与最大用户空间很接近,则可能存在本机内存问题。如果您怀疑存在本机内存泄漏,或者想要准确了解内存都到哪里去了,使用一些工具将有所帮助。