目录

  • 1、难以分配内存
  • 2、大对象直接分配在老年代
  • 3、容易触发Full GC


1、难以分配内存

数组可以通过下标快速访问元素,是因为它的内存地址是连续的。
绝大多数JVM管理内存的方式,并不是指针碰撞,而是空闲列表
这就意味着,堆中可用内存空间相对比较分散,存在大量内存碎片,JVM要为大数组分配一大块可用的连续内存空间是比较困难的

借用网友的两张图说明。

jenkins grovy数组_老年代


jenkins grovy数组_老年代_02

2、大对象直接分配在老年代

数组也是对象,且数组越大,对象占用的空间越大。
如果大对象直接分配在新生代,首先会导致JVM难以为其他小对象分配内存,过早的触发GC,其次,一旦大对象在经历第一次GC时活了下来,那么标记复制算法将很难进行工作。
首先,大对象的复制成本较高,其次Survivor区很难存放大对象,最终大对象还是要进入老年代,反而增加了额外的开销。

  • 如下图所示:

-XX:PretenureSizeThreshold参数用于设定对象所占内存大于该值直接进入老年代,只针对Serial和ParNew收集器有效。

3、容易触发Full GC

JVM处理朝生夕死的小对象是很拿手的,这类对象通常被分配在Eden区(开启逃逸分析后,直接栈上分配内存,效果更好),在经历第一次GC时就会被回收,内存回收效率是非常高的,几乎不耗费多少时间,而且不会导致用户线程暂停,即不会出现STW(Stop-The-World)。

但是,当数组被分配进老年代后,JVM要想回收这块内存空间就只能触发Full GCMajor GC,Full GC的开销是非常大且非常耗时的,JVM需要扫描整个堆,这会导致用户线程暂停,服务阻塞,进而降低整个应用程序的吞吐量。

分别用小对象和大数组测试两种场景下的GC情况:

  • 小对象:
  • 大数组: