一. 什么是堆
几乎所有的对象实例和数组都应该在运行时分配到堆上。
JVM启动时创建,一个jvm实例(一个进程)只有一个堆区,创建时便确定空间大小。堆的大小是可调节的。堆在物理上可以非连续,但是在逻辑上连续。但是不是所有堆空间都是线程共享的,还可以划分线程私有缓存区(TLAB)
方法结束后,堆空间不会被立即回收,需要等待GC。
二. 堆的内部结构
java 7 之前: 新生代、老年代、永久代。 Java8之后,永久代被替换为了元空间。其中,元空间和永久代被看作是方法区的逻辑实现。
四. 参数设置
-Xms, -Xmx
m - memory
一旦堆区超过Xmx,那么将报oom
生产环境中,一般设置Xmx = Xms。
-XX:NewRatio(默认2,一般不调)
-XX:SurvivorRatio(默认8:1:1), -XX:UseAdaptiveSizePolicy
-XX:UseAdaptiveSizePolicy是自适应调整eden,survivor01,survivor02的大小比例。默认情况下是开启的。
开启:-XX:+UseAdaptiveSizePolicy
关闭:-XX:-UseAdaptiveSizePolicy
-Xmn:设置新生代空间的大小(与XX:NewRatio冲突)
-XX:MaxTenuringThreshold=:晋升老年代的存活次数阈值(0-15)。
-XX:HandlePromotionFailure空间分配担保,幸存区放不下直接晋升老年代
-XX:UseTLAB,开启TLAB空间
五. 对象分配过程
对象初始分配区域:Eden。当Eden满了,触发YGC/Minor GC(针对整个年轻代的垃圾回收).
幸存区通过复制算法维护。当对象存活次数到达阈值/或幸存区空间满了,晋升到老年代。
老年代满了则会触发Major GC。对于大对象而言,直接进入老年代。
六. Full GC、Major GC、Full GC
为什么要分代?
七.TLAB
高效地保证线程安全
TLAB在Eden中仅占Eden空间的1%。堆空间上进行内存分配来完成的。由于多线程环境下,对象的分配可能存在竞争条件,需要使用同步机制来保证分配的正确性。然而,传统的同步机制会引入较大的开销,影响了内存分配的性能。线程在TLAB上进行对象分配时,无需加锁或同步,因为每个线程拥有自己的独立空间。
八. 逃逸分析
随着逃逸分析的发展,对象不一定在堆中分配。如果一个对象没有逃逸出方法的话,那么将在栈上分配。通过-XX:+DoEscapeAnalysis
JIT编译器根据逃逸分析的结果,通常有如下优化方式
- 栈上分配
如果一个对象没有逃逸出方法的话,那么将在栈上分配。即方法内new的对象,没办法在方法外被使用。 - 同步省略
如果一个对象被发现只能通过一个对象被访问到,那么就可以通过锁消除省略掉针对该对象的同步。 - 分离对象或标量替换
标量是数据被分割的最小单元。可以被继续分解的数据被称为聚合量。如果方法内new的对象,没办法在方法外被使用,那么可以通过分割为多个标量来实现栈上分配。