JVM内存详解-阅读笔记

 

 

 

 

 

JVM内存详解

1 本机内存介绍

1.1 硬件限制

1.2 操作系统和虚拟内存

1.2.1 虚拟内存可增加可用内存空间, 但是可能引起性能问题

1.2.2 内核空间和用户空间

2 内存耗尽时现象

当Java 堆耗尽时,Java 应用程序很难正常运行,因为Java 应用程序必须通过分配对象来完成工作。只要Java 堆被填满,就会出现糟糕的GC 性能并抛出表示Java 堆被填满的OutOfMemoryError。

相反,一旦Java运行时开始运行并且应用程序处于稳定状态,它可以在本机堆完全耗尽之后继续正常运行。不一定会发生奇怪的行为,因为需要分配本机内存的操作比需要分配Java 堆的操作少得多。但一些常见操作还是会报错:启动线程、加载类以及执行某种类型的网络和文件I/O。

3 如何占用本机内存

3.1 java堆

Java 堆是分配了对象的内存区域。

堆的大小可以在Java 命令行使用 -Xmx 和 -Xms 选项来控制(mx 表示堆的最大大小,ms 表示初始大小)。

Java堆占用的本机内存为-Xmx,未被使用的部分为保留内存,保留内存不会分配给其他程序使用;

对于维护Java 堆的内存管理系统,需要更多本机内存来维护它的状态。例如行垃圾收集。

 

3.2 垃圾收集

3.3 即时(JIT)编译

JIT 编译器在运行时编译Java 字节码来优化本机可执行代码。这极大地提高了Java 运行时的速度,并且支持Java 应用程序以与本机代码相当的速度运行。

但JIT 编译器的输入(字节码)和输出(可执行代码)必须也存储在本机内存中。

3.4 类和类加载器

存储类的方式取决于具体实现。Sun JDK 使用永久生成(permanent generation,PermGen)堆区域。Java 5 的IBM 实现会为每个类加载器分配本机内存块,并将类数据存储在其中。

从最基本的层面来看,使用更多的类将需要使用更多内存。

Java运行时可以卸载类来回收空间,但是只有在非常严酷的条件下才会这样做。不能卸载单个类,而是卸载类加载器,随其加载的所有类都会被卸载。只有在以下情况下才能卸载类加载器:

  Java 堆不包含对表示该类加载器的 java.lang.ClassLoader 对象的引用。

  Java 堆不包含对表示类加载器加载的类的任何 java.lang.Class 对象的引用。

  在Java 堆上,该类加载器加载的任何类的所有对象都不再存活(被引用)。

需要注意的是,Java 运行时为所有Java 应用程序创建的3 个默认类加载器(bootstrap、extension 和 application )都不可能满足这些条件,因此,任何系统类(比如 java.lang.String)或通过应用程序类加载器加载的任何应用程序类都不能在运行时释放。

3.5 JNI

JNI 应用程序可能通过3 种方式增加Java 运行时的本机内存占用:

JNI 应用程序的本机代码被编译到共享库中,或编译为加载到进程地址空间中的可执行文件。大型本机应用程序可能仅仅加载就会占用大量进程地址空间。

本机代码必须与Java 运行时共享地址空间。任何本机代码分配或本机代码执行的内存映射都会耗用Java 运行时的内存。

某些JNI 函数可能在它们的常规操作中使用本机内存。GetTypeArrayElements 和GetTypeArrayRegion 函数可以将Java堆数据复制到本机内存缓冲区中,以供本机代码使用。是否复制数据依赖于运行时实现。(IBM Developer Kit for Java 5.0 和更高版本会进行本机复制)。

通过这种方式访问大量Java 堆数据可能会使用大量本机堆

3.6 NIO

直接bytebuffer会操作本机内存;

3.7 线程

线程的堆栈空间、线程本地存储(thread-local storage)和内部数据结构会占用本机内存。

堆栈大小因Java 实现和架构的不同而不同。一些实现支持为Java 线程指定堆栈大小,其范围通常在256KB 到756KB 之间。

4 调试方法和技术

4.1 检查java堆

JavaCore文件、heapdump文件;

 

4.2 检查本机堆

Windows 提供的PerfMon 工具;

Linux 使用命令行工具(比如 ps、top 和 pmap)能够显示应用程序的本机内存占用情况。配合使用GCMV,进行长时间跟踪后的分析。

由于JVM 前期阶段的本机内存增长而耗尽本机内存,以及内存使用随负载增加而增加,这些都是尝试在可用空间中做太多事情的例子。在这些场景中,您的选择是:

  减少本机内存使用。缩小Java 堆大小是一个好的开端。

  限制本机内存使用。如果您的本机内存随负载增加而增加,可以采取某种方式限制负载或为负载分配的资源。

  增加可用地址空间。这可以通过以下方式实现:调优您的操作系统(例如,在Windows 上使用/3GB 开关增加用户空间,或者在Linux 上使用庞大的内核空间),更换平台(Linux 通常拥有比Windows 更多的用户空间),或者 转移到64 位操作系统。

 

 

4.3 是什么在使用本机内存

根据您的Java 设置,将会使用多少本机内存。根据以下指南粗略估算一下:

Java 堆占用的内存至少为-Xmx 值。

每个Java 线程需要堆栈空间。堆栈空间因实现不同而异,但是如果使用默认设置,每个线程至多会占用756KB 本机内存。

直接 ByteBuffer 至少会占用提供给allocate() 例程的内存值。

UMDH 支持就地 调试Windows 上本机内存泄漏,在Linux 上,您可能需要进行一些传统的调试,而不是依赖工具来解决问题。下面是一些建议的调试步骤:

提取测试案例。生成一个独立环境,您需要能够在该环境中再现本机内存泄漏。这将使调试更加简单。

尽可能缩小测试案例。尝试禁用函数来确定是哪些代码路径导致了本机内存泄漏。如果您拥有自己的JNI 库,可以尝试一次禁用一个来确定是哪个库导致了内存泄漏。

缩小Java 堆大小。Java 堆可能是进程的虚拟地址空间的最大使用者。通过减小Java 堆,可以将更多空间提供给本机内存的其他使用者。

关联本机进程大小。一旦您获得了本机内存随时间的使用情况,可以将其与应用程序工作负载和GC 数据比较。如果泄漏程度与负载级别成正比,则意味着泄漏是由每个事务或操作路径上的某个实体引起的。如果当进行垃圾收集时,本机进程大小显著减小,这意味着您没遇到内存泄漏,您拥有的是具有本机支持的对象组合(比如直接 ByteBuffer)。通过缩小Java 堆大小(从而迫使垃圾收集更频繁地发生),或者在一个对象缓存中管理对象(而不是依赖于垃圾收集器来清理对象),您可以减少本机支持对象持有的内存量。

5 消除限制

更改为64位,但是64位可能引入对象膨胀的问题;

必须关注物理内存是否满足Java程序使用,一旦物理内存不足,发生频繁的与虚拟内存交换,会严重影响性能。

 

 

 

 

 

posted on 2013-10-29 14:34  z-vipper  阅读(328)  评论(0编辑  收藏  举报

导航