JAVA进程内存(本机内存)的使用情况,java进程
JAVA进程内存(本机内存)的使用情况,java进程
什么是本机内存:
即进程内存。本机内存是可用于运行时进程的内存,它与 Java 应用程序使用的 java 堆内存不同。每种虚拟化资源(包括 Java 堆和 Java 线程)都必须
存储在本机内存中,虚拟机在运行时使用的数据也是如此。
里面存储哪些内容:
Java 堆:使用的本机内存大小保持不变,而且由 -Xmx 值(最大堆大小)指定。
垃圾收集:对于维护 Java 堆的内存管理系统,需要更多本机内存来维护它的状态。当进行垃圾收集时,必须分配数据结构来跟踪空闲存储空间和记录进度。这些数据结构的确切大小和性质因实现的不同而不同,但许多数据结构都与堆大小成正比。
即时 (JIT) 编译器:字节码编译使用本机内存(使用方式与 gcc 等静态编译器使用内存来运行一样),但 JIT 编译器的输入(字节码)和输出(可执行代码)必须也存储在本机内存中。包含多个经过 JIT 编译的方法的 Java 应用程序会使用比小型应用程序更多的本机内存。
类和类加载器:Java 应用程序由一些类组成,这些类定义对象结构和方法逻辑。Java 应用程序也使用 Java 运行时类库(比如 java.lang.String)中的类,也可以使用第三方库。这些类需要存储在内存中以备使用。
存储类的方式取决于具体实现。Sun JDK 使用永久生成(permanent generation,PermGen)堆区域。
Java 5 的 IBM 实现会为每个类加载器分配本机内存块,并将类数据存储在其中。
在运行时生成类
许多 JEE 应用程序使用 JavaServer Pages (JSP) 技术来生成 Web 页面。使用 JSP 会为执行的每个 .jsp 页面生成一个类,并且这些类会在加载它们的类加载器的整个生存期中一直存在 —— 这个生存期通常是 Web 应用程序的生存期。
使用 Java 反射:
Java 运行时必须将一个反射对象(比如 java.lang.reflect.Field)的方法连接到被反射到的对象或类。这可以通过使用 Java 本机接口(Java Native Interface,JNI)访问器来完成,这种方法需要的设置很少,但是速度缓慢。
在运行时为您想要反射到的每种对象类型动态构建一个类。后一种方法在设置上更慢,但运行速度更快,非常适合于经常反射到一个特定类的应用程序。
Java 运行时在最初几次反射到一个类时使用 JNI 方法,但当使用了若干次 JNI 方法之后,访问器会膨胀为字节码访问器,这涉及到构建类并通过新的类加载器进行加载。执行多次反射可能导致创建了许多访问器类和类加载器。保持对反射对象的引用会导致这些类一直存活,并继续占用空间。因为创建字节码访问器非常缓慢,所以 Java 运行时可以缓存这些访问器以备以后使用。一些应用程序和框架还会缓存反射对象,这进一步增加了它们的本机内存占用。
JNI
JNI 支持本机代码(使用 C 和 C++ 等本机编译语言编写的应用程序)调用 Java 方法,反之亦然。Java 运行时本身极大地依赖于 JNI 代码来实现类库功能,比如文件和网络 I/O。
JNI 应用程序可能通过 3 种方式增加 Java 运行时的本机内存占用:
JNI 应用程序的本机代码被编译到共享库中,或编译为加载到进程地址空间中的可执行文件。大型本机应用程序可能仅仅加载就会占用大量进程地址空间。
本机代码必须与 Java 运行时共享地址空间。任何本机代码分配或本机代码执行的内存映射都会耗用 Java 运行时的内存。
某些 JNI 函数可能在它们的常规操作中使用本机内存。GetTypeArrayElements 和 GetTypeArrayRegion 函数可以将 Java 堆数据复制到本机内存缓冲区中,以供本机代码使用。是否复制数据依赖于运行时实现。(IBM Developer Kit for Java 5.0 和更高版本会进行本机复制)。通过这种方式访问大量 Java 堆数据可能会使用大量本机堆。
NIO
Java 1.4 中添加的新 I/O (NIO) 类引入了一种基于通道和缓冲区来执行 I/O 的新方式。就像 Java 堆上的内存支持 I/O 缓冲区一样,NIO 添加了对直接 ByteBuffer 的支持(使用 java.nio.ByteBuffer.allocateDirect() 方法进行分配), ByteBuffer 受本机内存而不是 Java 堆支持。直接 ByteBuffer 可以直接传递到本机操作系统库函数,以执行 I/O — 这使这些函数在一些场景中要快得多,因为它们可以避免在 Java 堆与本机堆之间复制数据。
对于在何处存储直接 ByteBuffer 数据,很容易产生混淆。应用程序仍然在 Java 堆上使用一个对象来编排 I/O 操作,但持有该数据的缓冲区将保存在本机内存中,Java 堆对象仅包含对本机堆缓冲区的引用。非直接 ByteBuffer 将其数据保存在 Java 堆上的 byte[] 数组中。
线程
应用程序中的每个线程都需要内存来存储器堆栈(用于在调用函数时持有局部变量并维护状态的内存区域)。每个 Java 线程都需要堆栈空间来运行。根据实现的不同,Java
线程可以分为本机线程和 Java 堆栈。除了堆栈空间,每个线程还需要为线程本地存储(thread-local storage)和内部数据结构提供一些本机内存。
堆栈大小因 Java 实现和架构的不同而不同。一些实现支持为 Java 线程指定堆栈大小,其范围通常在 256KB 到 756KB 之间。
如何回收
Java 运行时可以卸载类来回收空间,但是只有在非常严酷的条件下才会这样做。不能卸载单个类,而是卸载类加载器,随其加载的所有类都会被卸载。只有在以下情况下才能
卸载类加载器:
Java 堆不包含对表示该类加载器的 java.lang.ClassLoader 对象的引用。
Java 堆不包含对表示类加载器加载的类的任何 java.lang.Class 对象的引用。
在 Java 堆上,该类加载器加载的任何类的所有对象都不再存活(被引用)。
Java 运行时为所有 Java 应用程序创建的 3 个默认类加载器( bootstrap、extension 和 application )都不可能满足这些条件,因此,任何系统类(比如 java.lang.String)或通过应用程序类加载器加载的任何应用程序类都不能在运行时释放。
线程占用内存的回收或高效利用:使线程利用率较高,减少空闲线程。
回收时机:
运行时也只会将收集类加载器作为 GC 周期的一部分。一些实现只会在某些 GC 周期中卸载类加载器。
NIO 直接 ByteBuffer 对象会自动清理本机缓冲区,但这个过程只能作为 Java 堆 GC 的一部分来执行。
如何配置
配置堆栈大小
配置类加载大小
配置线程堆栈大小
可重复使用的结构:
NIO、类加载器、线程
依赖于GC回收的部件:
NIO、类加载器
不可回收,必须保证的的部分:
JIT、JNI,类加载器在大部分情况也很难回收。
如何搭配使用:
对可控部分的合理配置,保证不可控部分有必要的内存。
合理控制堆大小,以促使GC发生,高效利用可重复使用的部分。
相关文章
- 暂无相关文章
用户点评