JVM(七)java堆空间分代

141 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

前言

java的垃圾回收器一般都采用分代回收的方式,有专门回收新生代的Serial、ParNew、Parallel Scavenge垃圾回收器,有专门回收老年代的CMS、Serial Old回收器,还有老年代和新生代都能回收的G1垃圾回收器。那么思考这样这一个一个问题:为什么需要把Java堆分代?不分代就不能正常工作了吗?

答案:其实不分代完全可以,分代的唯一理由就是优化GC性能。

经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。

  • 新生代:有Eden、两块大小相同的survivor(又称为from/to,s0/s1)构成,to总为空。
  • 老年代:存放新生代中经历多次GC仍然存活的对象。 未命名文件 (33).png

如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描,而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当GC的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

理解了这个问题,对于java堆空间和垃圾回收的设计缘由就清晰了。接着我们看看创建对象时,堆中内存是怎么分配给对象的。

对象分配过程

为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。为了能够安全的为对象分配内存,在分配内存的过程中还包括着垃圾回收。

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial Gc) ,一种是整堆收集((Full GC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
    • 新生代收集(Minor Gc / Young cc):只是新生代的垃圾收集
    • 老年代收集(Major ac / id sc):只是老年代的垃圾收集。
      目前,只有CMS GC会有单独收集老年代的行为。
      注意,很多时候Major GC会和Full Gc混淆使用,需要具体分辨是老年代回收还是整堆回收。
    • 混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。
      目前,只有G1 Gc会有这种行为
  • 整堆收集(Full GC):收集整个java堆和方法区的垃圾。 未命名文件 (34).png

分配过程:

  1. new的对象先放伊甸园区。
  2. 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
  3. 然后将伊甸园中的剩余对象移动到幸存者0区。
  4. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。
  5. 如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
  6. 啥时候能去养老区呢?可以设置次数。默认是15次。new的对象先放伊甸园区。
  7. 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园企中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
  8. 如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。啥时候能去养老区呢?可以设置次数。默认是15次。可以设置参数:-XX:MaxTenuringThreshold=<N>进行设置。
  9. 在养老区,相对悠闲。当养老区内存不足时,再次触发GC:Major Gc,进行养老区的内存清理。
  10. 若养老区执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常

针对不同年龄段的对象分配原则如下所示:

  • 优先分配到Eden
  • 大对象直接分配到老年代
    尽量避免程序中出现过多的大对象
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断
    如果survivor区中相同年龄的所有对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold 中要求的年龄。