JVM垃圾回收详解
堆空间的基本结构
Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆 内存中对象的分配和回收
Java 堆是垃圾收集器管理的主要区域,因此也被称为 GC 堆 (Garbage Collected Heap) 。
从垃圾回收的角度来说,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆被划分为了几个不同区域,这样我们就可以根据各个区域的特点选择合适的垃圾回收算法。
在 JDK 7 版本及 JDK 7 版本以前,堆内存被通常分为下面三部分:
- 新生代内存(Young Generation)
- 老年代(Old Generation)
- 永久代(Permanent Generation)
下图展示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存 。
内存分配和回收原则
对象优先在 Eden 区分配
大多数情况下,对象在新生代中的 Eden 区分配。当 Eden 区没有足够的空间进行分配时,虚拟机将发起一次 MinorGc
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
大对象直接进入老年代的行为是由虚拟机动态决定的,它与具体使用的垃圾回收器和相关参数有关。大对象直接进入老年代是一种优化策略,旨在避免将大对象放入新生代,从而减少新生代的垃圾回收频率和成本。
- G1垃圾回收器会根据-XX:G1HeapRegionSize堆区域大小和-XX:G1MixedGCLiveThresholdPercen 参数设置的阈值,来决定哪些对象会直接进入老年代。
- Parrallel Scavenge 垃圾回收器中,默认情况下,并没有一个固定的阈值来决定何时直接在老年代分配大对象。而是用虚拟机根据当前内存情况和历史数据动态决定的。
长期存活的对象将直接进入老年代
既然虚拟机采用了粉黛手机的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每一个对象都分配了一个对象年龄(Age) 计数器。
大部分情况,对象都会首先在 Eden 区域分配。如果对象 Eden 出生并经过了第一次 Minor GC 后仍然能够存活,并且能被 Suvivior 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.
对下给你在 Survivor 中没熬过一次 Minior GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值可以通过参数 -XX:MaxTenuringThreshold
来设置
主要进行 gc 的区域
针对 HotSpot Vm 的实现,它里面的 GC 其实准确分类只有两大类:
部分收集
- 新生代收集(Minior GC / Young GC):只对新生代进行垃圾收集
- 老年代收集 (Major GC / Old GC):支队老年代进行垃圾收集。需要注意的是 Major GC 在有的语境种也用于指代整堆收集
- 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集
整堆收集(Full GC):收集整个 Java 堆和方法区
空间分配担保
空间分配担保是为了确保在 Minior GC 之前老年代本身还有容纳新生代所有对象的剩余空间。
《深入了解 Java 虚拟机》第三章对于空间分配担保的描述如下
在发生 Minior GC 之前,虚拟机必须先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 Minior GC 是可以确保安全的。如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure 参数的设置值是否允许担保失败(Handle
Promotion Failure); 如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次 Minior GC ,尽管这次 Minior GC 是有风险的;如果小于,或者参数设置的值不允许冒险,那么这时就要进行一次 Full GC。
,尽管这次 Minior GC 是有风险的;如果小于,或者参数设置的值不允许冒险,那么这时就要进行一次 Full GC。