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日志 */