一、JVM
线程私有:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享:
- 堆
- 方法区
- 直接内存(非运行时数据区的一部分)
堆
- Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
- Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。
JDK1.7以及之前的版本,堆内存通常分为三个部分
- 新生代内存(Young Generation)
- 老年代(Old Generation)
- 永久代(Permanent Generation)
Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。

大部分情况下,对象会先在Eden区分配,再一次新生代垃圾回收后,如果对象存活,则进入S1或者S0,且对象年龄会加一,当累积的某个年龄大小超过survivor区一半时,取这个年龄和MaxTenuringThreshold中更小的值,作为新的晋升年龄阈值,当超过阈值后,对象会晋升到老年代。(老年代中的对象仍然可以被使用,只是在垃圾回收时需要特殊处理,因为老年代中的对象通常生命周期较长,而且垃圾回收不如新生代频繁。)
内存分配和回收原则
对象优先在 Eden 区分配
大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC
大对象直接进入老年代
-
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
-
大对象直接进入老年代主要是为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
JVM 提供了一个
-XX:PretenureSizeThreshold参数,用于指定一个对象的大小超过多少字节时,应直接将其分配到老年代中。
具体来说,当使用-XX:PretenureSizeThreshold=n参数时,如果创建的对象大小大于等于 n 字节,则该对象将直接被分配到老年代中;否则,该对象将被分配到新生代中,并按照默认的规则进行垃圾回收。
需要注意的是,使用-XX:PretenureSizeThreshold参数时应该谨慎,不宜将所有的大对象都直接分配到老年代中,因为这样可能导致老年代空间不足或者垃圾回收效率低下。正确的做法是根据系统实际情况和性能要求合理设置该参数的值。
长期存活的对象将进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(累积的某个年龄大小超过survivor区一半时,取这个年龄和MaxTenuringThreshold中更小的值,作为新的晋升年龄阈值),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
死亡对象判断方法
1. 引用计数法
- 每当有一个地方引用它,计数器就加 1;
- 当引用失效,计数器就减 1;
- 任何时候计数器为 0 的对象就是不可能再被使用的
2. 可达性分析算法
- 通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
垃圾收集算法
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
垃圾收集器
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
- G1收集器