技巧💡
堆,主要实例的数据,字符池常量(JDK1.6后,迁移到堆中。数据量大,不适合放在永久代),也是垃圾回收的重点。
堆内存模型
- 几乎所有的Java对象都是在Eden区被new出来的。放不下的,放老年区。
- Java 堆区可以划分为新生代和老年代, 新生代又可以进一步划分为 Eden 区, Survivor 1 区, Survivor 2 区
对象分配策略
- 如果对象再Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor区容纳,则被移动到Survivor空间中,并将对象年龄设置为1,对象再Survivor区每熬过一次MinorGC,年龄就+1,当年龄增加到一定程度(默认为15,不同Jvm,GC都所有不同,对应虚拟机参数XX:+MaxTenuringThreshold)时,就会被晋升到老年代中。
- 优先分配到Eden,每个线程可以向 Java 虚拟机申请一段连续的内存,比如 2048 字节,作为线程私有的 TLAB(类比”私人车库“)
- 大对象直接分配到老年代(尽量避免程序中出现过多的大对象)
- 长期存活的对象分配到老年代
- 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄
GC分类
Minor GC/Young GC: 针对新生代的垃圾收集;
Major GC/Old GC: 针对老年代的垃圾收集.
Full GC: 针对整个 Java 堆以及方法区的垃圾收集.
MinorGC,MajorGC,FullGC
- 部分收集
- 新生代收集(MinorGC (YoungGC))
- 触发条件
- 当年轻代空间不足时,就会触发MinorGC,这里的年轻代指的是Eden代满,Survivor满不会触发GC。每次MinorGC会清理年轻代的内存
- 因为Java对象大多朝生夕灭,所以MinorGC非常频繁(一般使用标记复制算法)
- MinorGC会引发STW
- 触发条件
- 老年代收集(MajorGC/oldGC)
目前只有CMS GC会单独收集老年代的行为 很多时候MajorGC与FullGC混淆使用,具体分辨是老年代回收还是整堆回收
- 触发条件
- 指发生在老年代的GC,对象从老年代消失,我们说“MajorGC”“FullGC”发生了
- 出现了MajorGC,经常会伴随至少一次MinorGC
- 非绝对,在Parallel Scavenge收集器的收集策略里就直接进行MajorGC的策略选择过程
- 也就是老年代空间不足,会先尝试触发MinorGC,如果之后空间还不足,则触发MajorGC
- MajorGC的速度比MinorGC慢10倍以上,STW的时间更长
- 如果MajorGC后,内存还不足,就报OOM了
- 触发条件
- 混合收集
收集整个新生代以及部分老年代的垃圾收集,目前只有G1 GC会有这种行为
- 新生代收集(MinorGC (YoungGC))
- 整堆收集
- 收集整个Java堆和方法区的垃圾收集
-
触发条件
- 1、调用System.gc()时,系统建议执行FullGC,但是不必然执行
- 2、老年代空间不足
- 3、方法区空间不足
- 4、通过MinorGC后进入老年代的平均大小,大于老年代的可用内存
- 5、由Eden区,Survivor 0区向Survivor 1区复制时,对象的大小大于ToSpace可用内存,则把改对象转存到老年代,且老年代的可用内存小于该对象的大小
-
- 收集整个Java堆和方法区的垃圾收集
对象在堆的创建过程
堆是分配对象的唯一选择吗
随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术,将会导致一些微秒变化,所有对象分配到堆上渐渐变得不那么绝对了。 有一种特殊情况,如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配,这样无需堆上分配,也不需要垃圾回收了,也是最常见的堆外存储技术
TaoBaoVM,其中创新的GCIH(GC invisible heap)技术实现了off-heap,实现了将生命周期较长的Java对象从heap中移动heap外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的