目录
- 1、难以分配内存
- 2、大对象直接分配在老年代
- 3、容易触发Full GC
1、难以分配内存
数组可以通过下标快速访问元素,是因为它的内存地址是连续的。
绝大多数JVM管理内存的方式,并不是指针碰撞
,而是空闲列表
。
这就意味着,堆中可用内存空间相对比较分散,存在大量内存碎片,JVM要为大数组分配一大块可用的连续内存空间是比较困难的。
借用网友的两张图说明。
2、大对象直接分配在老年代
数组也是对象,且数组越大,对象占用的空间越大。
如果大对象直接分配在新生代
,首先会导致JVM难以为其他小对象分配内存,过早的触发GC,其次,一旦大对象在经历第一次GC时活了下来,那么标记复制算法
将很难进行工作。
首先,大对象的复制成本较高,其次Survivor区
很难存放大对象,最终大对象还是要进入老年代,反而增加了额外的开销。
- 如下图所示:
-XX:PretenureSizeThreshold参数用于设定对象所占内存大于该值直接进入老年代,只针对Serial和ParNew收集器有效。
3、容易触发Full GC
JVM处理朝生夕死
的小对象是很拿手的,这类对象通常被分配在Eden区
(开启逃逸分析后,直接栈上分配内存,效果更好),在经历第一次GC时就会被回收,内存回收效率是非常高的,几乎不耗费多少时间,而且不会导致用户线程暂停,即不会出现STW(Stop-The-World)。
但是,当数组被分配进老年代
后,JVM要想回收这块内存空间就只能触发Full GC
或Major GC
,Full GC的开销是非常大且非常耗时的,JVM需要扫描整个堆,这会导致用户线程暂停,服务阻塞,进而降低整个应用程序的吞吐量。
分别用小对象和大数组测试两种场景下的GC情况:
- 小对象:
- 大数组: