一. 什么是堆

几乎所有的对象实例和数组都应该在运行时分配到堆上。
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的对象,没办法在方法外被使用,那么可以通过分割为多个标量来实现栈上分配。
堆是JVM中用于存储对象实例的主要区域,分为新生代、老年代,Java8后永久代被元空间取代。对象首先分配在Eden区,经过YGC后存活的移至Survivor区,达到一定存活次数或Survivor区满则进入老年代。FullGC主要发生在老年代空间不足时。TLAB是线程安全的对象分配优化,而逃逸分析可能导致对象在栈上分配,提高性能。
241





