JVM 堆是被所有线程共享的一块内存区域,所有对象和数组都在堆上进行内存分配。
JVM 堆内存划分
-
JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。
-
年轻代又分为 Eden 和 Survivor 区。Survivor 区由 FromSpace 和 ToSpace 组成。Eden 区占大容量,Survivor 两个区占小容量,默认比例是 8:1:1。
-
堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据 GC 算法回收。
-
非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
JDK1.8 版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在 JVM 中,而是使用本地内存。
元空间有注意有两个参数:
-
MetaspaceSize:初始化元空间大小,控制发生 GC 阈值
-
MaxMetaspaceSize:限制元空间大小上限,防止异常占用过多物理内存
为什么移除永久代?
移除永久代原因:为融合 HotSpot JVM 与 JRockit VM(新 JVM 技术)而做出的改变,因为 JRockit 没有永久代。
有了元空间就不再会出现永久代 OOM 问题了!
为什么分代?
将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及 GC 频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。
新生代
新生代包含一个 Eden 区和两个 Survivor 区。大多数情况下,新创建的对象会在新生代 Eden 区中分配,当 Eden 区没有足够的空间进行分配时,虚拟机会触发一次 Minor GC,如果 Survivor 区空间允许的话,该对象将被分配到 Survivor。
老年代
大对象直接在老年代中分配,大对象指需要大量连续内存空间的 Java 对象。
Minor GC 和 Full GC 的区别?
-
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多数都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度比较快。
-
老年代 GC(Full GC):指发生在老年代的垃圾收集动作,速度非常慢,所以要尽量减少 Full GC 的发生。
分代收集如何判定对象的年龄?
虚拟机给每个对象定义了一个对象年龄计数器,如果对象在 Eden 区出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间,并且对象年龄设置为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当对象的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。
为什么新生代中要有 Survivor 区?
防止频繁触发 FULL GC。如果没有 Survivor,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,这样会使老年代很快被填满,导致老年代触发 FULL GC,由于老年代的内存空间远大于新生代,所以进行一次 Full GC 消耗的时间比 Minor GC 长得多。
为什么要设置两个 Survivor 区?
防止产生内存空间碎片。如果只有 Survivor1,那么每一次当 Eden 区满时,触发 Minor GC 并把对象移入 Survivor1 中,如此循环对导致 Survivor1 中产生大量的空间碎片;所以需要有 Survivor2,当 Eden 再一次满时,触发 Minor GC,虚拟机会把 Eden 中和 Survivor1 中的存活对象通过复制算法移入 Survivor2 中,这样 Survivor2 就不会产生内存碎片,同时 Eden 和 Survivor1 会清理内存,保证下一次 Minor GC 触发时的操作。
JVM 堆内存常用参数
参数 | 描述 |
---|---|
-Xms | 堆内存初始大小,单位m、g |
-Xmx(MaxHeapSize) | 堆内存最大允许大小,一般不要大于物理内存的 80% |
-XX:NewSize(-Xns) | 年轻代内存初始大小 |
-XX:MaxNewSize(-Xmn) | 年轻代内存最大允许大小,也可以缩写 |
-XX:SurvivorRatio=8 | 年轻代中 Eden 区与 Survivor 区的容量比例值,默认为 8,即 8:1 |
GC 流程
基本所有数据都会保存在 JVM 的堆内存之中。
对于整个的 GC 流程里面,最需要处理的事年轻代与老年代的内存清理操作。
元空间(永久代)都不在 GC 范围内。
具体流程:
-
当现在有一个新的对象产生,JVM需要为该对象进行内存空间的申请。
-
先判断 Eden 区是否有内存空间,如果有,直接将新对象保存在 Eden 区。
-
如果 Eden 区的内存空间不足,会自动执行一个 Minor GC 操作,将 Eden 区的无用内存空间进行清理。
-
清理 Eden 区之后继续判断 Eden 区内存空间情况,如果充足,则将新对象直接保存在 Eden 区
-
如果执行了 Minor GC 之后发现 Eden 区的内存依然不足,那就判断存活区的内存空间,并将 Eden 区的部分活跃对象保存在存活区。
-
活跃对象迁移到存活区后,继续判断 Eden 区内存空间情况,如果充足,则将新对象直接保存在 Eden 区。
-
如果存活区也没有空间了,则继续判断老年区,如果老年区充足,则将存活区的部分活跃对象保存在老年区。
-
存活区的活跃对象迁移到老年区后,则将 Eden 区的部分活跃对象保存在存活区。
-
活跃对象迁移到存活区后,继续判断 Eden 区内存空间情况,如果充足,则将新对象直接保存在 Eden 区。
-
如果老年区也满了,这时候产生 Major GC(Full GC)进行老年区的内存清理。
-
如果老年区执行了 Major GC 之后发现无法进行对象保存,会产生 OutOfMemoryError 异常。
参考
https://blog.51cto.com/lizhenliang/2164876
https://juejin.im/post/6844903982595309581
https://www.jianshu.com/p/82cbcabd4ef9