# jvm内存模型

JVM堆内存模型在1.7与1.8中有着较大的区别，需要分别说明。

## JDK1.7 JVM堆内存模型

在Java1.7中，根据内存中对象按照生命周期长短进行划分，将内存区域拆分为三个`generation`(代)：

![img](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-3b4fc7787e49ec5bf8648851f5ffae7ac13ed2e9%2F0_1299600540nIt7.gif?alt=media)

* **新生代(Young)**：生命周期比较短的对象，比如局部变量
* **老年代(Tenured \[ten nie de])**：生命周期比较长的对象，比如`ApplicationContext`，`SessionFactory`
* **永久代(Perm \[po m])**：基本不会死亡的对象，比如加载的class信息

**在JVM中，GC采用了分代算法，根据不同的代，来使用不同的回收算法**：

* 新生代中存活对象较少，通常采用复制算法
* 老年代中存活对象较多，通常采用标记清理法，或者标记整理法

**每个不同的代，需要使用不同的GC算法，所以可以将GC分为以下几类：**

* `Minor GC`：负责年轻代的内存回收，采用复制算法
* `Major GC`：负责老年代的内存回收，采用标记清除法
* `Full GC`：清理整个堆空间—包括年轻代和老年代

**年轻代Young中，又分为三个区域：**

* `Eden`区(一蹬)：Java新对象的出生地
  * 如果新创建的对象占用内存很大，则直接分配到老年代
  * 当Eden区内存不够的时候就会触发`MinorGC`，对新生代区进行一次垃圾回收
* `ServivorFrom`区：上一次GC的幸存者，作为这一次GC的被扫描者
* `ServivorTo`区：保留了一次`MinorGC`过程中的幸存者

**Minor GC的回收流程**：

![img](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-fd7a9fe3f3671e7f65d26175ce987ee23b4b3a8f%2F1018541-20170308200940484-1226739905.jpg?alt=media)

![img](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-51c9b30e5921521e7fa7dbe03ff41c41eb564cfb%2F1018541-20170308194923281-651017951.jpg?alt=media)

* Minor GC 采用复制算法
* 每当对象熬过一次GC扫描，它的年龄就会+1
* 先扫描`Eden`和`SurvivorFrom`中的所有对象，找出存活的对象，并将其放入到`SurvivorTo`区域
* 同时将这些对象的年龄+1
* 如果年龄达到了老年代的要求( 默认是 15 岁，可以通过参数`-XX:MaxTenuringThreshold` 来设定)，便将其移动到老年代中，如果老年代已经满了，就放入永久代
* 然后清空 `Eden` 和 `SurvivorFrom`区域
* 然后将`SurvivorFrom`与`ServivorTo`的角色互换，这样，下次仍然还会扫描`SurvivorFrom`和`Eden`区域

**Major GC的回收流程**：

* Major GC采用标记清除算法
* 首先扫描以一次老年代，并标记处没有存活的对象，并回收没有标记的对象
* 因为需要全扫描，所以耗时很长
* Major GC会产生内存碎片，为了减少内存损耗，我们一般需要进行合并或者标记出来方便下次直接分配（内存整理）

**永久代没有垃圾回收：**

* 内存的永久保存区域，主要存放Class和Meta（元数据）的信息
* GC不会在主程序运行期对永久区域进行清理
* 永久代的区域会随着加载的Class的增多而胀满，最终抛出OOM异常
* jdk1.8中已经移除了永久代，采用了元空间

## JDK1.8 JVM堆内存模型

![jdk1.8jvm内存模型](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-d931bb2a676e55a4d1be6923be21dddd329a4c5e%2F20180505114217244915.jpg?alt=media)

jdk1.8中移除了永久代，并使用元空间(`Metaspace`)替代，**永久代需要被舍弃可以总结为以下几个**：

* 因为永久代不会执行GC，而内存空间总是有限制，所以永久代始终存在OOM问题
* 类及方法的信息等比较难确定其大小，因此对于永久代的大小指定比较困难，太小容易出现永久代溢出，太大则容易导致老年代溢出（因为堆空间有限，此消彼长）
* 永久代会为 GC 带来不必要的复杂度
* Oracle 可能会将`HotSpot`与 `JRockit` 合二为一

**元空间：**

* 元空间不再存放在堆内存中，也不是虚拟机内存中，而是放到本地内存的空间中，元数据的大小限制，只会跟物理机的内存大小有关，从而解决了OOM的问题。

## 参考

* [JVM的新生代、老年代、MinorGC、MajorGC](https://www.cnblogs.com/ygj0930/p/6522828.html)
* [深入理解Java虚拟机 \&GC分代年龄](https://www.cnblogs.com/xiarongjin/p/8309839.html)
* [JVM调优：选择合适的GC collector （一）](https://blog.csdn.net/historyasamirror/article/details/6233007)
