堆的概述:
①堆是java内存管理核心区,一个JVM实例对应一个堆
②JVM启动时即创堆,大小确定(可调节)
③堆在逻辑视为连续,物理上不连续
④可以划分为线程私有部分(TLAB)
⑤(几乎)所有对象实例及数组运行时,分配在堆上
⑥方法结束后,堆中对象不释放,仅在GC时释放
堆内存:
内存划分:
JAVA7及之前:
新生区、养老区、永久代
JAVA8及之后:
新生区、养老区、元空间
年轻代又分:Eden、S0、S1(实际S0与S1同时只有一个在起作用)
老年代与年轻代都在堆内,而永久代、元空间在方法区内。
堆(新生代+老年代)大小设置:
-Xms:设置起始内存(默认为电脑内存的64分之一)
-Xmx:最大内存(默认为电脑内存的4分之一)
OOM错误:即堆大小超过了-Xmx
注:在日常使用时,起始内存和最大内存通常设置为相同大小(因为若起始内存小于最大内存:系统会对堆进行扩容,而扩容多出的部分会被频繁扩容、释放,给机器带来压力)
内存分配比例:
新生代占三分之一堆空间、老年代三分之二(可调整)
Eden:S0:S1 = 8:1:1(经过自适应通常为6:1:1)(需要重新设置值达到默认比例)
对象分配过程:
①Eden中new出对象(大部分对象都是在Eden中new出)
②Eden满时:进行第一次MinorGC/YGC:释放垃圾,有用对象放入S0(进行年龄计数)
③Eden再次满:YGC:有用对象、S0中有用对象都放入S1
④Eden满:当前S0/S1内年龄为15(默认,可更改):放入老年代(晋升promotion)(进入老年代不再用年龄计数)
注:①YGC只在Eden满时触发,而YGC会将整个年轻代都进行垃圾回收
②S0/S1采用复制算法
③动态对象年龄判断:若S区中相同年龄大小总和大于S区一半时,则年龄大于等于该年龄对象可直接进入老年代
对象分配特殊情况:
①对象过大时,不放S0/S1,直接放入老年代;若老年代放不下,除法FullGC;若仍然放不下,则OOM错误
②S0/S1满时,对象可直接放入老年代
TLAB:
背景:因为堆是线程共享的,而对象实例创建频繁,在并发环境下堆空间线程不安全,在Eden内为每个线程划分空间(Eden部分控件划分多个TLAB,剩余部分为共享空间)
堆空间参数设置:
-XX:+PrintFlagsInitial:打印参数默认值
-XX:+PrintFlagsFinal:打印参数最终值
-XX:NewRatio:设置新生代大小占比
-XX:SurvivorRatio:设置Eden与S0/S1比例关系
-XX:MaxTenuringThreshold:设置年龄阈值
-XX:+PrintGCDetails:打印GC处理日志
-XX:HandlePromotionFailure:空间分配担保
分配担保:
MinorGC之前,先查JVM老年代最大连续可用控件是否大于新生代所有对象的总空间:
a.大于:则GC安全
b.小于:查分配担保参数:
①为true:继续查老年代最大连续可用空间是否大于历次晋升老年代对象的平均大小:若大于,则可进行MinorGC(有风险);若小于,进行FullGC
②为false:进行FullGC
堆是分配对象的唯一选择吗:
①经过逃逸分析后,若对象没有逃逸出方法,可能被优化为栈上分配
②在TaoBaoVM中,有GCIH,会将长生命周期对象从堆中移至堆外,且GC不管