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

  

 

  

 

posted on 2022-04-19 11:28  anpeiyong  阅读(24)  评论(0编辑  收藏  举报

导航