前提概要 📕
- JVM的堆内存中分为年轻代与老年代,年轻代又分为Eden区与Survivor区。
- 新对象的创建会分配在年轻代,对象何时进入老年代呢?又有什么样的对象适合放在老年代呢?
- JVM实现的自动内存管理主要是针对对象内存的回收和对象内存的分配。
- 了解对象何时进入老年代有利于我们合理分配堆内存,减少FullGC的发生。
📕 对象在Eden区中分配 📕
目前主流的垃圾收集器都会采用分代回收算法,因此,新创建的对象都会优先分派在新生代的Eden区内,在新生代中为了防止内存碎片问题,因此垃圾收集器一般都选用“复制”算法。
堆内存的新生代分为:Eden区+Survior1区+Survior2区
📕📕 内存分配流程 📕📕
- 每次创建对象时,首先会在Eden区中分配。
- 若Eden区已满,发生MinorGC,存活的对象都迁移到Survior区(to)区。
- 然后执行回收,再将新的对象分配到Eden区。
📕📕📕 对象进入老年代策略 📕📕📕
(1) 迭代年龄判断(静态年龄)
在对象的对象头信息中存储着对象的迭代年龄(MaxTenuringThreshold),迭代年龄会在每次YoungGC之后对象的移区操作中增加,每执行一次MiniorGC。当这个年龄大于到 15(默认) 之后,这个对象将会被移入老年代.
可以通过这个参数设置这个年龄值:
- XX:MaxTenuringThreshold 复制代码
(2) 大对象直接进入老年代
- 当首次加载分配大对象空间而发生YoungGC的时候:会进行初始化PretenureThresSizehold这个值的。
- PretenureThresSizehold=N,有一些占用大量连续内存空间的对象在被分配内存开始就会直接进入老年代。这样的大对象一般是一些数组,长字符串之类的对象。
我们可以通过这个参数设置大对象,这个限额的大小:
- XX:PretenureSizeThreshold 复制代码
注意:此参数只对Serial及ParNew两款收集器有效
(3) 担保分配对象机制(按照空间区计算迁移条件)
- 若Survior(to)区剩余内存太少,导致对象无法放入该区域时,就会启用“分配担保”,将多出的数据对象直接存转移到老年代/或者直接放入老年代,然后清空Eden区和Survior(from)区
(4) 动态对象年龄判定(按照空间区计算迁移条件)
- 虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
📕 引用类型等级 📕
📕📕 强引用 📕📕
平常的代码创建对象都属于强引用,之后当对象变为垃圾对象才会被回收。
📕📕 软引用 📕📕
被SoftReference这个类包裹起来的对象,在进行垃圾收集发现剩余空间不够的时候,全部已创建软引用对象会被一次性回收,这种引用类型常用于对内存比较敏感的缓存中
📕📕 弱引用 📕📕
被WeakReference这个类包裹起来的对象,每次进行垃圾收集操作的时候都会将弱引用对象一次性回收,基本不使用
📕📕 虚引用📕📕
plantomReference又称幽灵引用,随时都会被回收
📕📕 未确认-对象动态年龄判断 📕📕
此策略发生在Survivor区,当Survivor区中的一批对象的总大小大于Survivor区空间大小的一半,在这个区域中,对象年龄大于这批对象的最大年龄的所有对象会被移入老年代.
📕📕📕 看下面的例子 📕📕📕
假设我这里按照年龄划分了10批对象,对象年龄依次为1-10,现在年龄1到3这批对象的总大小大于Survivor空间一半,则对象为4-10的所有对象会被放入老年代
📕📕📕 适合存放老年代的对象 📕📕📕
- 策略一:将可能长期存活的对象直接放入老年代
- 策略二:避免移区时的复制操作浪费资源
- 策略三:不能将还有引用的对象当做垃圾回收掉
- 策略四:将可能长期存活的对象直接放入老年代
观察这几条策略并结合GC区别我们可以发现一些端倪。
📕📕📕年轻代的空间很宝贵📕📕📕
- 不应放入长期对象与较大对象占用空间
- 存活时间短的对象应让其在年轻代存活直至死
- 亡
- 因为这些对象放入老年代后很快死亡,又不能及时回收,造成内存浪费,更快的触发FullGC。
因此在程序运行过程中,合理设置参数,使一些可能长期存活的框架对象与缓存对象,一些大对象应放入老年代。