前言

我们来分析下Java中对象的分配过程及堆溢出的一些测试。

对象分配过程

JVM设计者不仅需要考虑到内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关, 因此还需要考虑GC执行完内存回收后是否存在空间中间产生内存碎片。

  1. new的对象先放在伊甸园区。该区域有大小限制;
  2. 当伊甸园区域填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园预期进行垃圾回收(Minor GC),将伊甸园区域中不再被其他对象引用的对象进行销毁,再加载新的对象放到伊甸园区;
  3. 然后将伊甸园区中的剩余对象移动到幸存者0区;
  4. 如果再次触发垃圾回收,此时上次幸存下来的放在幸存者0区的,如果没有回收,就会放到幸存者1区;
  5. 如果再次经历垃圾回收,此时会重新返回幸存者0区,接着再去幸存者1区。
  6. 如果累计次数到达默认的15次,这会进入养老区。这个累计次数可以通过设置参数,调整阈值 -XX:MaxTenuringThreshold=N;
  7. 养老区内存不足时,会再次出发GC:Major GC 进行养老区的内存清理;
  8. 如果养老区执行了Major GC后仍然没有办法进行对象的保存,就会报OOM异常 。

需要说明的一点是,新生代中的s0和s1,每一时刻只有一个正在使用,另一个为空。所以一个称为from区,一个称为to区,他们两个之间是相互转换的。

分配对象的流程图如下所示:

堆GC

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:一种是部分收集器(Partial GC),另一类是整堆收集器(Full GC)。

部分收集器: 不是完整收集java堆的的收集器,它又分为:

  1. 新生代收集(Minor GC / Young GC): 只是新生代的垃圾收集;
  2. 老年代收集(Major GC / Old GC): 只是老年代的垃圾收集 (CMS GC 单独回收老年代)
  3. 混合收集(Mixed GC):收集整个新生代及老年代的垃圾收集 (G1 GC会混合回收, region区域回收) ;

整堆收集(Full GC):收集整个java堆和方法区的垃圾收集器 。

年轻代GC触发条件

  1. 年轻代空间不足,就会触发Minor GC, 这里年轻代指的是Eden代满,Survivor满不会引发GC ;

  2. Minor GC会引发STW(stop the world) ,暂停其他用户的线程,等垃圾回收接收,用户的线程才恢复。

老年代GC (Major GC)触发机制

  1. 老年代空间不足时,会尝试触发MinorGC. 如果空间还是不足,则触发Major GC;
  2. 如果Major GC , 内存仍然不足,则报错OOM;
  3. Major GC的速度比Minor GC慢10倍以上。

FullGC 触发机制

  1. 调用System.gc() , 系统会执行Full GC ,不是立即执行;
  2. 老年代空间不足;
  3. 方法区空间不足;
  4. 调优过程尽量减少FullGC执行。

Java堆溢出测试

堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证 GC Roots 到对象之间有可达路径来避免垃 圾收集回收机制清除这些对象,当这些对象所占空间超过最大堆容量时,就会产生 OutOfMemoryError 的异常。堆内存异常示例如下:

运行后会报异常,在堆栈信息中可以看到java.lang.OutOfMemoryError: Java heap space 的信息,说明在堆内存空间产生内存溢出的异常。

新产生的对象最初分配在新生代,新生代满后会进行一次 Minor GC ,如果 Minor GC 后空间不足会把该对象和 新生代满足条件的对象放入老年代,老年代空间不足时会进行 Full GC ,之后如果空间还不足以存放新对象则抛 出 OutOfMemoryError 异常。 常见原因:

  1. 内存中加载的数据过多,如一次从数据库中取出过多数据;
  2. 集合对对象引用过多且使用完后没有清空;
  3. 代码中存在死循环或循环产生过多重复对象;
  4. 堆内存分配不合理

总结

本文主要讲了Java中对象分配的过程,及堆GC、堆溢出的测试。