Java 中的内存区域主要有堆和栈两部分。由于栈是线程私有,随着线程的结束而结束,因此垃圾回收主要在堆中进行,堆中几乎存放了 Java 中所有的对象实例。
堆内存模型
堆内存分为两大部分:新生代和老年代,比例关系为1:2。
新生代分为 Eden、ServivorFrom、ServivorTo 三个区域,比例为8:1:1。ServivorFrom 与 ServivorTo 又被称为幸存者区。
注:图中用 S0 与 S1 指代 ServivorFrom 与 ServivorTo
新生代:主要是用来存放新生的对象
- Eden:对象被创建的时候首先放到这个区域
- SurvivorTo:执行垃圾回收时 Eden 与 SurvivorFrom 区域存活的对象被放入到此区域
- SurvivorFrom:存活的对象被放入 SurvivorTo 之后,清空此区域,SurvivorTo 和 SurvivorFrom 的标记会互换,始终保证一个survivor是空的。
- 当篮子放不下时将存活的对象挑出来放到第二个篮子里,并将第一个篮子清空;
- 继续往第一个篮子中放对象,当篮子又放不下时,将第一和第二个篮子中仍存活的对象挑出来放到第三个篮子,并将第一和第二个篮子清空;
- 继续往第一个篮子中放对象,当再次放不下时,将第一和第三个篮子中仍存活的对象放到第二个篮子中,并将第一和第三个篮子清空
- 重复上述过程,始终保证第二个篮子和第三个篮子中有一个是空的
至于为什么按照 8:1:1 的大小比例来划分新生代,主要是因为研究表明新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中的一块 Survivor(可以通过虚拟机参数 -XX:SurvivorRatio 来配置比例)。
这种垃圾收集算法称为复制算法。新生代中执行的垃圾回收叫做 Minor GC 或 Young GC,每一次 Minor GC 后留下来的对象 age + 1。
老年代:用于存放新生代中经过多次垃圾回收仍然存活的对象,也就是age达到了一定大小;也有可能是新生代分配不了内存的大对象会直接进入老年代。
查看JVM内存信息
JDK 中自带了许多查看和调试 JVM 的工具,通过这些工具可以很方便的查看当前线程状态、内存信息等数据。
例如,我们可以通过命令行工具 jps 获取当前所有 Java 进程的pid:
C:\>jps -l
15828 thread.Local
8676
10520 org.jetbrains.jps.cmdline.Launcher
8168 sun.tools.jps.Jps
获得了进程的 pid 之后,就可以使用 jmap 工具输出该进程的堆内存使用状况了(仅摘取部分)。
C:\>jmap -heap 15828
Heap Configuration:
MaxHeapSize = 2109734912 (2012.0MB)
NewSize = 44040192 (42.0MB)
MaxNewSize = 703070208 (670.5MB)
OldSize = 88080384 (84.0MB)
NewRatio = 2
SurvivorRatio = 8
Heap Usage:
PS Young Generation
Eden Space:
capacity = 33554432 (32.0MB)
used = 5375456 (5.126434326171875MB)
free = 28178976 (26.873565673828125MB)
16.02010726928711% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation
capacity = 88080384 (84.0MB)
used = 0 (0.0MB)
free = 88080384 (84.0MB)
0.0% used
注:PS 指 Parallel Scavenge 并行收集器,是一种吞吐量优先的垃圾回收机制。