java 内存管理

堆和非堆内存

按照官方的说法:”Java 虚拟机具有一个堆 (Heap),堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。在 JVM 中堆之外的内存称为非堆内存 (Non-heap memory)”。

JVM 主要管理两种类型的内存:堆和非堆。

jvm memory management

Heap memory non-heap memory
  Code Cache
Eden Space Perm Gen
Survivor Space Jvm Stack
Old Gen(Tenured Gen) Local Method Statck
Heap memory 组成 详解
Young Gen 即 Eden + From Space + To Space
Eden Space 存放新生的对象
Survivor Space 有两个,存放每次垃圾回收后存活的对象
Old Gen(Tenured Gen) 即 Old Space 主要存放应用程序中生命周期长的存活对象
non-Heap memory 组成 详解
Code Cache 代码缓存区,它主要用于存放 JIT 所编译的代码。
Permanent Gen 保存虚拟机自己的静态 (refective) 数据 主要存放加载的 Class 类级别静态对象如 class 本身,method,field 等等 permanent generation 空间不足会引发 full GC(详见 HotSpot VM GC 种类)
Jvm Stack 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构
Local Method Statck 该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务

堆内存

Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

堆的大小可以固定,也可以扩大和缩小。堆的内存不需要是连续空间。

非堆内存

Java 虚拟机管理堆之外的内存(称为非堆内存)。

Java 虚拟机具有一个由所有线程共享的方法区。方法区属于非堆内存。它存储每个类结构,如运行时常数池、字段和方法数据,以及方法和构造方法的代码。它是在 Java 虚拟机启动时创建的。

方法区在逻辑上属于堆,但 Java 虚拟机实现可以选择不对其进行回收或压缩。与堆类似,方法区的大小可以固定,也可以扩大和缩小。方法区的内存不需要是连续空间。

除了方法区外,Java 虚拟机实现可能需要用于内部处理或优化的内存,这种内存也是非堆内存。例如,JIT 编译器需要内存来存储从 Java 虚拟机代码转换而来的本机代码,从而获得高性能。

几个基本概念

PermGen space:全称是 Permanent Generation space,即永久代。就是说是永久保存的区域, 用于存放 Class 和 Meta 信息,Class 在被 Load 的时候被放入该区域,GC(Garbage Collection) 应该不会对 PermGen space 进行清理,所以如果你的 APP 会 LOAD 很多 CLASS 的话,就很可能出现 PermGen space 错误。

Heap space:存放 Instance。

Java Heap 分为 3 个区,Young 即新生代,Old 即老生代和 Permanent。

Young 保存刚实例化的对象。当该区被填满时,GC 会将对象移到 Old 区。Permanent 区则负责保存反射对象。

JVM 内存限制 (最大值)

首先 JVM 内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM 内存的最大值跟操作系统有很大的关系。简单的说就 32 位处理器虽然可控内存空间有 4GB, 但是具体的操作系统会给一个限制,这个限制一般是 2GB-3GB(一般来说 Windows 系统下为 1.5G-2G,Linux 系统下为 2G-3G),而 64bit 以上的处理器就不会有限制了。

为什么有的机器我将 - Xmx 和 - XX:MaxPermSize 都设置为 512M 之后 Eclipse 可以启动,而有些机器无法启动?

通过上面对 JVM 内存管理的介绍我们已经了解到 JVM 内存包含两种:堆内存和非堆内存,另外 JVM 最大内存首先取决于实际的物理内存和操作系统。所以说设置 VM 参数导致程序无法启动主要有以下几种原因:

  1. 参数中 - Xms 的值大于 - Xmx,或者 - XX:PermSize 的值大于 - XX:MaxPermSize;
  2. -Xmx 的值和 - XX:MaxPermSize 的总和超过了 JVM 内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,如果你的内存是 1024MB,但实际系统中用到的并不可能是 1024MB,因为有一部分被硬件占用了。

如果你有一个双核的 CPU,也许可以尝试这个参数: -XX:+UseParallelGC 让 GC 可以更快的执行。(只是 JDK 5 里对 GC 新增加的参数)

如果你的 WEB APP 下都用了大量的第三方 jar,其大小超过了服务器 jvm 默认的大小,那么就会产生内存益出问题了。解决方法: 设置 MaxPermSize 大小。

建议:将相同的第三方 jar 文件移置到 tomcat/shared/lib 目录下,这样可以减少 jar 文档重复占用内存

JVM 内存设置参数

设置项 说明
-Xms512m 表示 JVM 初始分配的堆内存大小为 512m(JVM Heap(堆内存) 最小尺寸,初始分配)
-Xmx1024m JVM 最大允许分配的堆内存大小为 1024m,按需分配(JVM Heap(堆内存) 最大允许的尺寸,按需分配)
-XX:PermSize=512M JVM 初始分配的非堆内存
-XX:MaxPermSize=1024M JVM 最大允许分配的非堆内存,按需分配
-XX:NewSize/-XX:MaxNewSize 定义 YOUNG 段的尺寸,NewSize 为 JVM 启动时 YOUNG 的内存大小;MaxNewSize 为最大可占用的 YOUNG 内存大小。
-XX:SurvivorRatio 设置 YOUNG 代中 Survivor 空间和 Eden 空间的比例

说明:

  1. 如果 - Xmx 不指定或者指定偏小,应用可能会导致 java.lang.OutOfMemory 错误,此错误来自 JVM 不是 Throwable 的,无法用 try…catch 捕捉。
  2. PermSize 和 MaxPermSize 指明虚拟机为 java 永久生成对象(Permanate generation)如,class 对象、方法对象这些可反射(reflective)对象分配内存限制,这些内存不包括在 Heap(堆内存)区之中。
  3. -XX:MaxPermSize 分配过小会导致:java.lang.OutOfMemoryError: PermGen space。
  4. MaxPermSize 缺省值和 - server -client 选项相关:-server 选项下默认 MaxPermSize 为 64m、-client 选项下默认 MaxPermSize 为 32m。

申请一块内存的过程

  1. JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域
  2. 当 Eden 空间足够时,内存申请结束。否则到下一步
  3. JVM 试图释放在 Eden 中所有不活跃的对象(这属于 1 或更高级的垃圾回收);释放后若 Eden 空间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入 Survivor 区 / OLD 区
  4. Survivor 区被用来作为 Eden 及 OLD 的中间交换区域,当 OLD 区空间足够时,Survivor 区的对象会被移到 Old 区,否则会被保留在 Survivor 区
  5. 当 OLD 区空间不够时,JVM 会在 OLD 区进行完全的垃圾收集(0 级)
  6. 完全垃圾收集后,若 Survivor 及 OLD 区仍然无法存放从 Eden 复制过来的部分对象,导致 JVM 无法在 Eden 区为新对象创建内存区域,则出现”out of memory 错误”

内存回收算法

Java 中有四种不同的回收算法,对应的启动参数为:

–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

Serial Collector

大部分平台或者强制 java -client 默认会使用这种。

young generation 算法 = serial

old generation 算法 = serial (mark-sweep-compact)

这种方法的缺点很明显, stop-the-world, 速度慢。服务器应用不推荐使用。

Parallel Collector

在 linux x64 上默认是这种,其他平台要加 java -server 参数才会默认选用这种。

young = parallel,多个 thread 同时 copy

old = mark-sweep-compact = 1

优点:新生代回收更快。因为系统大部分时间做的 gc 都是新生代的,这样提高了 throughput(cpu 用于非 gc 时间)

缺点:当运行在 8G/16G server 上 old generation live object 太多时候 pause time 过长

Parallel Compact Collector (ParallelOld)

young = parallel = 2

old = parallel,分成多个独立的单元,如果单元中 live object 少则回收,多则跳过

优点:old old generation 上性能较 parallel 方式有提高

缺点:大部分 server 系统 old generation 内存占用会达到 60%-80%, 没有那么多理想的单元 live object 很少方便迅速回收,同时 compact 方面开销比起 parallel 并没明显减少。

Concurrent Mark-Sweep(CMS) Collector

young generation = parallel collector = 2

old = cms

同时不做 compact 操作。

优点:pause time 会降低, pause 敏感但 CPU 有空闲的场景需要建议使用策略 4.

缺点:cpu 占用过多,cpu 密集型服务器不适合。另外碎片太多,每个 object 的存储都要通过链表连续跳 n 个地方,空间浪费问题也会增大。

内存监控方法

jmap -heap 查看 java 堆(heap)使用情况

jmap -heap <pid>

using thread-local object allocation.

Parallel GC with 4 thread(s)   #GC 方式

Heap Configuration:  #堆内存初始化配置

MinHeapFreeRatio=40  #对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
MaxHeapFreeRatio=70  #对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
MaxHeapSize=512.0MB  #对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize  = 1.0MB     #对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
MaxNewSize =4095MB   #对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
OldSize  = 4.0MB     #对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
NewRatio  = 8        #对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8    #对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize= 16.0MB     #对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小
MaxPermSize=64.0MB   #对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小

Heap Usage:          #堆内存分步

PS Young Generation

Eden Space:         #Eden区内存分布

capacity = 20381696 (19.4375MB)             #Eden区总容量
used     = 20370032 (19.426376342773438MB)  #Eden区已使用
free     = 11664 (0.0111236572265625MB)     #Eden区剩余容量
99.94277218147106% used                     #Eden区使用比率

From Space:        #其中一个Survivor区的内存分布

capacity = 8519680 (8.125MB)
used     = 32768 (0.03125MB)
free     = 8486912 (8.09375MB)
0.38461538461538464% used

To Space:          #另一个Survivor区的内存分布

capacity = 9306112 (8.875MB)
used     = 0 (0.0MB)
free     = 9306112 (8.875MB)
0.0% used

PS Old Generation  #当前的Old区内存分布

capacity = 366280704 (349.3125MB)
used     = 322179848 (307.25464630126953MB)
free     = 44100856 (42.05785369873047MB)
87.95982001825573% used

PS Perm Generation #当前的 “永生代” 内存分布

capacity = 32243712 (30.75MB)
used     = 28918584 (27.57891082763672MB)
free     = 3325128 (3.1710891723632812MB)
89.68751488662348% used
-->