一、堆空间内存结构
堆空间内存分为年轻代和老年代。年轻代又细分为Eden区,Survivor1区和Survivor2区(又称为from区和to区)
为什么要对堆空间进行分代?
因为不同对象的生命周期不同,绝大部分对象都是临时对象,如果不进行分代设计,那每次GC都要对所有对象进行扫描,影响GC性能,通过分代设计,大部分时候GC只需要对其中一块空间进行垃圾回收,优化了整体性能。
1. 对象在堆中的生命周期
(1)new的对象先放在Eden区。
(2)当Eden区满,而程序又需要创建新的对象时,JVM垃圾回收器首先对Eden区进行垃圾回收(Young/Minor GC),将Eden区中不被引用的对象进行销毁,然后将Eden区剩余的所有对象转移到S0区。再将新的对象放到Eden区。
(3)当再次触发Young GC时,在Eden区和S0区中进行回收,之前就在S0区且此次没有被回收的对象会被转移到S1区,Eden中存活的对象也会转移到S1区。
(4)当再次触发Young GC时,S1区中没有被回收的对象又会被转移到S0区,Eden中存活的对象也会转移到S0区(也就是说一个时刻S0区和S1区中只有一个区中有对象,这个区也被称为from区,另一个区则为to区,to区就是下一次垃圾回收时幸存对象要进入的区)
(5)当一个对象经历多次(默认为15次,可通过-XX:MaxTenuringThreshold参数进行设置)垃圾回收仍然幸存,则会被转移到老年区。
这个过程中还会有一些特殊情况:
(1)Young GC后若S区放不下幸存的对象,则部分对象直接晋升老年代。
(2)若Young GC后Eden区还是放不下要创建的对象(超大对象),则尝试直接将其放到老年代,若老年代放不下,则触发Full GC(对整个堆和方法区进行垃圾回收),若Full GC后老年代还是放不下,则抛出OOM错误。
垃圾回收会引发STW,会先暂停其他用户线程,等垃圾回收结束,用户线程才恢复运行。Full GC比Young GC要慢很多,STW暂停时间在十倍以上,因此在开发和调优中要尽量避免Full GC。
2. 堆中各个区大小参数设置
-XX:NewRatio 设置年轻代和老年代空间大小比例。默认值为2:1
-XX:SurvivorRatio 设置年轻代中Eden区和survivor区大小比例。默认大概为8:1:1,根据实际自适应
3. GC分类
二、堆空间大小的设置与查看
1. 默认堆空间的大小
- 初始内存大小:系统物理内存大小 / 64
- 最大内存大小:系统物理内存大小 / 4
2. 通过运行参数设置堆空间大小
- -Xms 用于设置堆空间初始内存大小,例:-Xms200M
- -Xmx 用于设置堆空间最大内存大小,例:-Xmx200M
通常会将-Xms和-Xmx设置为相同的值,其目的是为了能够在垃圾回收清理完堆区后不需要重新分隔计算堆区的大小,以提高性能。
3. 在代码中查看堆空间大小
但这样计算出来的数会比堆空间实际分配的内存要小些,因为S区同一时刻只占用一个,这里计算就只计算了一个S区
4. 在命令行中查看jvm进程的堆空间大小
使用jstat命令
jstat -gc 进程id
C代表总大小,U代表已使用大小
5. 使用可视化工具jvisualvm查看
在命令行输入jvisualvm命令(jdk自带可视化工具),打开jvisualvm可视化界面,安装Visual GC插件,即可查看jvm进程实时内存情况
三、线程私有缓存区域 TLAB
在Eden空间中,JVM为每个线程分配了一个私有缓存区域(TLAB,Thread Local Allocation Buffer)。使用TLAB可以避免一系列线程安全问题,并且由于无需对地址空间加锁,提升了内存分配速率。我们将这个内存分配方式称为快速内存分配策略。
每个线程使用堆空间时优先使用TLAB空间,用完了或者不够用才使用公共的Eden空间
默认情况下,TLAB所占的空间非常小,仅占用整个Eden空间的1%。也可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占Eden空间比例大小。
四、堆空间常用参数小结
五、堆是分配对象存储的唯一选择吗?代码优化技巧
jdk 6u23版本之后,Hotspot JVM就默认开启了逃逸分析。之前的版本可通过-XX:+DoEscapeAnalysis选项显示开启。
通过逃逸分析有如下代码优化技巧
1. 栈上分配
JIT即时编译器在编译期间根据逃逸分析的结果,发现如果一个对象没有逃逸出方法的话,就可以被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就能降低对堆空间的占用并且无需进行垃圾回收了。因此,在开发中能使用局部变量时,就不要定义在方法之外。
2. 同步省略
3. 标量替换
参数-XX:+EniminateAllocations开启标量替换(默认是开启的),允许将对象打散分配在栈上。
主要还是通过标量替换实现的栈上分配,Hotspot没有直接实现栈上分配。