对象在Eden区分配:
默认情况下,新生代和老年代大小比率是1:2;新生代又可分为eden区和survivor区,默认比例是8:1:1;
大多数情况下,对象在eden区分配的,当eden区没有足够的空间进行分配时,就会触发minorGC,
minor GC :
发生在新生代,minorGC一般比较频繁,且速度较快;
Full GC :
会回收老年代、新生代、方法区的垃圾,速度比minorGC慢很多;
大对象的内存分配:
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数可设置大对象大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。
长期存活对象内存情况:
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
对象动态年龄判断机制:
这里直接举例说明:
情况1:对象在eden出生,并且Survivor0区和Survivor1都是null,当进行首次minor GC时,会把eden区仍存活的对象放到Survivor0区,如果这批存活的对象总大小超过Survivor0的50%(-XX:TargetSurvivorRatio可以指定),那么这批对象都会被放到老年代(因为此时分代年龄都一样为1)
情况2:对象在eden出生,并且Survivor0区已有仍存活对象,Survivor1是null,当进行minor GC时,会把eden区和Survivor0区仍存活的对象放到Survivor1区,如果这批存活的对象总大小超过Survivor0的50%,那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了(年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。)
老年代空间担保机制:
年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。
如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生"OOM"。当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”
对象内存回收:
几乎所所有的对象实例都是在堆中存放着,垃圾回收就是要判断哪些对象已经不被任何途径使用了。
内存回收算法:
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,如下面代码所示:除了对象aTestUserA和 aTestUserB相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。
1、引用计数法
public class ATestUser {
Object instance = null;
public static void main(String[] args) {
// 创建两个对象实例
ATestUser aTestUserA = new ATestUser();
ATestUser aTestUserB = new ATestUser();
// 互相引用
aTestUserA.instance = aTestUserB;
aTestUserB.instance = aTestUserA;
// 失去指针引用
aTestUserA = null;
aTestUserB = null;
}
}
2、可达性分析算法
将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等。
在可达性分析算法中不可达的对象,也不是立即清理。需要先进行判断是否重新写finalize()方法:1、如果没覆盖finalize()方法,将会被直接回收。
2、如果这个对象覆盖了finalize()方法,将会执行finalize()方法。这里是可回收对象最后一次逃脱回收机会,如果方法里面使对象重新与引用链上任何一个对象建立关联,那么次对象将被移除回收集合,否者被回收。
举例:
public class ATest {
public static List<AUser> list1= new ArrayList<>();
public static void main(String[] args) {
List<AUser> list2= new ArrayList<>();
while (true) {
// 不会被回收
list2.add(new AUser());
// 会被回收 ,但是当重写finalize方法,重新赋予引用链上时,不会被回收
new AUser();
}
}
}
对AUser重写finalize:
@Override
protected void finalize() throws Throwable {
// 重新赋予引用链上
ATest.list1.add(this);
}
注:重写finalize不推荐使用,这里只是借助代码理解垃圾回收执行原理