JVM---运行时数据区-堆
堆
/**
* 【运行时数据区---堆】
* what?
* 一个JVM实例 只有 一个堆内存;
* JVM启动时 堆内存被创建,内存大小也就确定了;
* 堆内存大小 是可以调节的;
* JVM规范规定,堆内存 可以在 物理上不连续,但在 逻辑上 应该是连续的;
* ***所有的线程 共享Java堆内存,但是 还可以划分 线程私有的缓冲区(TLAB, Thread Local Allocation Buffer);
* 堆 是GC的重点区域;
*
* <堆内存细分>
* 现代的 GC收集器 大部分 基于 分代收集理论 设计;
*
* 堆内存逻辑上细分为:
* Java7及之前:
* 新生代Young Generation Space
* 分为 Eden、Survivor(S0, S1)
* 老年代Tenure Generation Space
* 永久代Permanent Space
*
* Java8及之后:
* 新生代Young Generation Space
* 分为 Eden、Survivor(S0, S1)
* 老年代Tenure Generation Space
* 元空间Meta Space
*
* <堆内存大小>
* 默认大小:
* 初始内存大小:物理内存大小 / 64
* 最大内存大小:物理内存大小 / 4
*
* 可以通过-Xms, -Xmx进行设置:
* -Xms:
* 堆区的起始内存,等价于 -XX:InitialHeapSize
* -X:JVM运行的参数
* ms:memory start
*
* -Xmx:
* 堆区的最大内存,等价于 -XX:MaxHeapSize
* -X:JVM运行的参数
* mx:memory max
*
* ***实际使用时,建议-Xms与-Xmx设置为一样;
*
* 查看设置的参数:
* 方式一:
* jps :查看当前Java进程
* jstat -gc 进程ID
*
* 方式二:
* VM参数 加 -XX:+PrintGCDetails
*/
年轻代&老年代
/**
* 【堆---年轻代与老年代】
* 存储在JVM中的Java对象 可以划分为 2类:
* a,生命周期 比较短的瞬时对象,这类对象 的创建和消亡 非常迅速;
* b,另一类 生命周期 非常长,在某些极端情况下 还能与 JVM生命周期 一致;
*
* 堆区 逻辑上可分为 新生代、老年代;
* 新生代 又分为 Eden、Survivor0(from)、Survivor1(to);
*
* 新生代 与 老年代 配置:
* 默认:
* -XX:NewRatio = 2
* 新生代 占 1,老年代 占 2
*
* 在HotSpot中,Eden 与 Survivor0(from)、Survivor1(to) 默认 比例 8:1:1;
*
* 自定义:
* -XX:NewRatio = 4
* 新生代 占 1, 老年代 占 4
*
* -XX:SurvivorRatio=8
* JVM 默认开启 自适应,如果 需要显式指定大小,需要关闭 自适应 -XX:-UseAdaptiveSizePolicy
*
* -Xmn (一般不使用):
* 设置新生代的最大内存大小;
*
* ***
* 几乎所有的Java对象 都是在Eden 被new出来的;
* 绝大部分 Java对象 的销毁 都在 新生代 进行;
* (新生代 80% 对象 都是 朝生夕死)
*/
对象在堆中不同区域的分配过程
/**
* 【堆---对象分配过程】
* 为新对象分配内存是非常严谨和复杂的任务,JVM的设计者不仅要考虑内存如何划分、在哪里分配等问题,
* 且由于内存分配算法与内存回收算法密切相关,还需要考虑GC执行完成后是否会在内存中产生内存碎片;
*
* 1、大部分 new 的对象 优先 放在 Eden区;
* 2、当 Eden 被填满 又需要创建新的对象时,垃圾回收器 将对 Eden 进行 MinorGC:
* 将 不再被引用的对象 进行销毁;
* 将 剩下可用的对象 移动到 Survivor0 区,并 在对象上的age+1;
* 3、当 Eden 再次被填满 又需要创建新的对象时,垃圾回收器 将对 Eden 进行 MinorGC:
* 将 不再被引用的对象 进行销毁;
* 同时 对 Survivor0 区对象 进行 MinorGC :
* 将 Survivor0不被引用的对象 进行销毁;
* 将 Eden 幸存对象 及 Survivor0幸存对象 移动至 Survivor1,并在对象上 的age+1;
* 4、如此往复,当 Survivor 区 对象的age > 15 时,Survivor 幸存对象 将被 移动至 老年代;
* Survivor 对象 移动至 老年代 阈值设置:
* -XX:MaxTenuringThreshold=
*
* <对象分配的特殊情况>
* 对于 大对象 ,优先 考虑 放在 Eden :
* 如果 Eden 内存够:
* 存放
* 如果 Eden 内存不够 :
* 进行 Minor GC:
* MinorGC 后 Eden够 :
* 存放
* MinorGC 后 Eden不够 :
* 放在 老年代;
* 如果 老年代 空间够
* 存放;
* 如果 老年代 空间不够
* 进行 MajorGC(FullGC):
* MajorGC 老年代 够:
* 存放;
* MajorGC 老年代 不够:
* OOM;
*/
MinorGC(YoungGC)、MajorGC(OldGC)、FullGC
Full GC:
一种清理整个堆空间(包括年轻代、老年代以及元空间或永久代)的垃圾回收操作;
Major GC:
针对老年代进行的垃圾回收操作;
Minor GC:
针对年轻代进行的垃圾回收操作;
/**
* 【MinorGC(YoungGC)、MajorGC(OldGC)、FullGC】
* JVM在进行GC时,并不是 每次 都对 新生代、老年代、方法区 一起 回收,大部分回收 指的都是 新生代;
*
* 在HotSpot VM实现中,GC 按照 回收区域 分为 :
* 1、部分收集 Partial GC:
* Minor GC(YoungGC):
* 针对 新生代(Eden、S0,S1);
*
* ***触发时机:
* 当 Eden 内存不足 时触发 MinorGC(Survivor 满 不会触发)
* Minor GC 会 触发 Stop The World(STW),暂停 用户线程,等GC结束后,用户线程才能恢复;
*
* Major GC (Old GC):
* 针对 老年代;
* ***目前,只有CMS GC 才会单独收集老年代;
* Major GC 一般 比 Minor GC 慢10倍以上,STW时间更长;
* 如果 Major GC后,老年代 内存还不足,触发 OOM;
*
* 混合 GC(Mixed GC):
* 新生代、部分 老年代 进行 GC;
* ***目前,只有G1 GC会有这种行为;
*
* 2、整堆收集 Full GC
* 针对 整个 堆、方法区 进行 GC;
*
* ***触发时机:
* a, 调用System.gc()时,系统建议执行FullGC,但不必然执行;
* b, 老年代空间不足
* c, 方法区空间不足
*/
堆空间分代思想
/**
* 【堆空间分代思想】
* 经研究表明,不同对象的生命周期不一样,70%-99% 对象 都是 临时对象;所以,将 堆分为:
* 新生代:1(Eden :Survivor0 :Survivor1 = 8:1:1)、老年代:2
*
* <堆空间分代唯一的理由>:
* 优化GC性能;
*/
堆-内存分配策略
/**
* 【堆-内存分配策略】
* 1、优先分配在Eden
* 2、对于 大对象 直接 分配到 老年代;
* (大对象:占用连续空间比较大)
* 3、长期存活的对象 分配到 老年代;
* (长期存活:Survivor 区的对象 age 达到 阈值 )
* 4、动态年龄判断
* 如果 Survivor 区 相同age所有对象的大小的总和 大于 Survivor 区的一半,age >= 该年龄 的对象 直接进入老年代,无需要等待达到 阈值;
* 5、空间分配担保
* -XX:HandlePromotionFailure
*/
private static void test2() {
byte[] arr = new byte[1024*1024*20]; // 20m
/**
* -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
*
* YoungG(20m Eden:16m S0:2m S1:2m) OldGen(40m)
*
* 结果:大对象直接进入OldGen
*
* Heap
* PSYoungGen total 18432K, used 2300K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000)
* eden space 16384K, 14% used [0x00000007bec00000,0x00000007bee3f3a0,0x00000007bfc00000)
* from space 2048K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007c0000000)
* to space 2048K, 0% used [0x00000007bfc00000,0x00000007bfc00000,0x00000007bfe00000)
* ParOldGen total 40960K, used 20480K [0x00000007bc400000, 0x00000007bec00000, 0x00000007bec00000)
* object space 40960K, 50% used [0x00000007bc400000,0x00000007bd800010,0x00000007bec00000)
* Metaspace used 3290K, capacity 4496K, committed 4864K, reserved 1056768K
* class space used 363K, capacity 388K, committed 512K, reserved 1048576K
*/
}
堆---TLAB
/**
* 【堆---TLAB】
* why?
* 堆是 一个进程中 多个线程 共享的区域;
* 多个线程 在同一个 堆中 并发 创建对象时,会引发 堆空间安全问题;
* 为避免 多个线程 操作 同一个内存,使用了 加锁机制;
*
* 位置
* 新生代的Eden区域
*
* what?
* TLAB(Thread Local Allocation Buffer)
* 从内存分配的角度,在 新生代的Eden区 中,JVM为 每个线程 分配了一个 私有空间 TLAB;
* 这种分配策略称为 快速分配策略;
*
* 好处
* 多线程同时分配内存时,使用TLAB可以避免内存分配的安全问题;
* 同时还能提升内存分配的吞吐量;
*
* 如何查看是否开启TLAB
* jinfo -flag UseTLAB 进程ID
* (TLAB 默认开启)
* -XX:+UseTLAB +为开启
*
* 设置TLAB空间大小
* 默认,TLAB空间很小,只占 Eden的 1%,可以通过 -XX:TLABWasteTargetPercent 进行自定义;
*
* TLAB空间不足如何处理?
* 一旦 在TLAB中 分配失败,JVM将使用 加锁机制,在 Eden中进行分配对象,保证操作的原子性;
*
* JVM将TLAB 当做 内存分配的首选,尽管 不是所有的对象 都能在 TLAB 中分配;
*/
堆---参数设置
/**
* 【堆---参数设置】
* -XX:+PrintFlagsInitial
* 查看所有参数的默认值;
* -XX:+PrintFlagsFinal
* 查看所有参数的最终值;
* -Xms
* 堆空间初始化大小(默认物理内存的 1/64)
* -Xmx
* 最大堆空间(默认为 物理内存的 1/4)
* -Xmn
* 设置新生代大小
* -XX:NewRatio
* 设置 新生代 与 老年代 比例
* -XX:SurvivorRatio
* 设置 新生代 中 Eden 与 Survivor 的比例
* -XX:MaxTenuringThreshold
* 设置 新生代对象最大年龄 阈值
* -XX:+PrintGCDetails
* 输出 详细的GC日志
* -XX:+PrintGC
* 输出 简化的GC日志
*/
浙公网安备 33010602011771号