技巧💡

堆,主要实例的数据,字符池常量(JDK1.6后,迁移到堆中。数据量大,不适合放在永久代),也是垃圾回收的重点。

堆内存模型

  1. 几乎所有的Java对象都是在Eden区被new出来的。放不下的,放老年区。
  2. Java 堆区可以划分为新生代老年代, 新生代又可以进一步划分为 Eden 区, Survivor 1 区, Survivor 2 区

对象分配策略

  1. 如果对象再Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor区容纳,则被移动到Survivor空间中,并将对象年龄设置为1,对象再Survivor区每熬过一次MinorGC,年龄就+1,当年龄增加到一定程度(默认为15,不同Jvm,GC都所有不同,对应虚拟机参数XX:+MaxTenuringThreshold)时,就会被晋升到老年代中。
  2. 优先分配到Eden,每个线程可以向 Java 虚拟机申请一段连续的内存,比如 2048 字节,作为线程私有的 TLAB(类比”私人车库“)
  3. 大对象直接分配到老年代(尽量避免程序中出现过多的大对象)
  4. 长期存活的对象分配到老年代
  5. 如果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会有这种行为

  • 整堆收集
    • 收集整个Java堆和方法区的垃圾收集
      • 触发条件

        • 1、调用System.gc()时,系统建议执行FullGC,但是不必然执行
        • 2、老年代空间不足
        • 3、方法区空间不足
        • 4、通过MinorGC后进入老年代的平均大小,大于老年代的可用内存
        • 5、由Eden区,Survivor 0区向Survivor 1区复制时,对象的大小大于ToSpace可用内存,则把改对象转存到老年代,且老年代的可用内存小于该对象的大小

对象在堆的创建过程

堆是分配对象的唯一选择吗

随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术,将会导致一些微秒变化,所有对象分配到堆上渐渐变得不那么绝对了。 有一种特殊情况,如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配,这样无需堆上分配,也不需要垃圾回收了,也是最常见的堆外存储技术

TaoBaoVM,其中创新的GCIH(GC invisible heap)技术实现了off-heap,实现了将生命周期较长的Java对象从heap中移动heap外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的