内存分配策略
对象的内存分配就是在堆上分配(有些在栈上分配: JIT编译,逃逸分析技术),在堆上对象又主要在新生代的Eden上,如果启动了本地线程缓冲,将按线程优先在TLAB上分配。少数会直接分配在老年代中。具体由垃圾收集器组合与虚拟机参数决定。
垃圾收集器 Serial / Serial Old (ParNew / Serial Old) 组合下:
1.普通对象
2.大对象直接老年代
如:长字符串,长数组。由于Java对象大都 “朝生夕灭” 所以新生代GC 非常频繁,而且速度快。 老年代GC频率要少,效率慢10倍以上。大对象对虚拟机是个坏消息。
3.长期存活对象进入老年区
为了适应不同的内存状况,对象不一定年龄达到最大值才能进入老年代。如果在Survivor空间中相同年龄的对象大小的总和大于Survivor大小的一半,那么其它年龄大于这些对象的可以直接进入老年代。
3.空间分配担保
当Eden区发生Minor GC后仍有大量对象存活,而Survivor区无法全部放下,此时就需要老年代进行分配担保。虚拟机先检查老年代持续空间是否足够,足够则安全分配,不够则看配置是否允许分配担保失败,不允许则执行Full GC。允许则检查最大持续空间是否大于历次晋升到老年代对象的平均大小,大于则Minor GC(有风险),小于Full GC.
垃圾收集器
不同虚拟机GC日志输出不一样,在eclipse配置 Run a Java Application 如下图:
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
System.gc();
while(true) {
list.add(new OOMObject());
}
}
}
1.运行日志为
-
GC / Full GC :代表 不停止java工作线程 / 停止java工作线程。
-
(. . . . . .) : Allocation Failure 分配担保时出错。
-
[ PsYougGen. . . . . ] : [ 收集器+新生代/老年代 :GC前该内存区已使用量 —>GC后该内存区已使用量(该内存区总量),GC花费时间 ] 。
-
[ ] 后面 GC前java堆已使用容量 —> GC后Java堆已使用容量(java堆已使用容量)。
-
[Times : 用户态消耗cpu时间 , 内核态消耗cpu时间 ,Wall Clock Time ]。
2.垃圾收集算法
-
标记清除算法: 缺点 1.标记与清除效率不高;2.产出大量碎片内存。
-
复制算法:内存分割成两块,第一块内存用完了,将还存活的对象复制到第二块,然后清除第一块。高效,内存开销大。
-
标记整理算法:先将存活的对象向一端移动,然后清除未存活的对象。
-
分代收集:新生代,老年代。
3.垃圾收集器
-
Serial : 作用新生代,单线程,复制算法,需要停止Java工作线程。
-
Serial Old :作用于老年代,单线程,标记整理算法,需要停止Java工作线程。
-
ParNew :Serial的多线程版本,其他与Serial一样。
-
Parallel Scavenge :作用新生代,复制算法,并行多线程,可控吞吐量 吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间)。 高吞吐量 可以高效率利用cpu时间,尽快完成程序运算任务,适合后台运算而不需要太多交互的任务。
-
Parallel Old :作用老年代,标记整理算法,多线程。
-
CMS :作用老年代,并发收集,低停顿。以最短回收停顿时间为目的。1.初始标记;2.并发标记;3.重新标记;4.并发清除。1和3 需要停止java工作线程。2阶段进行GC Roots Tracing。3阶段修正并发标记时因用户程序继续运行而产生标记变动的对象的标记。
-
G1 : 作用新生代和老年代,面向服务端应用,并发与并行,分代收集,空间整合,可预测停顿。运行步骤:1.初始标记;2并发标记;3最终标记;4刷选回收。