1.堆得核心概述

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域
  • Java堆区在JVM启动的时候即被创建,其空间大小也就被确定了,是JVM管理的最大一块内存空间
    • 堆内存的大小是可以调节的
  • <>规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的
  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓存区(Thread Local Allocation Buffer, TLAB)
  • <>中对Java堆的描述是: 所有的对象实例以及数组都应当运行时分配到堆上.
  • 数组和对象可能永远不会存储在栈上面, 因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置
  • 在方法结束以后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除
  • 堆, GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域

image.png

1.内存细分

现代垃圾收集器大部分都基于分代收集理论设计, 堆空间细分为:

  • Java 7之前堆内存逻辑上分为三部分: 新生区+老年代+永久代
    • Young Generation Space 新生区 Young/New
    • Tenure generation space 老年代 Old/Tenure
    • Permantent Space 永久代 Perm
  • Java 8之后堆内存逻辑上分为三部分: 新生区+老年代+元空间
    • Young Generation Space 新生区 Young/New
    • Tenure generation space 老年代 Old/Tenure
    • Meta Space 元空间 Meta

约定: 新生区<==>新生代<==>年轻代 养老区<==>老年代<==>老年区 永久区<==>永久代

2.堆空间大小的设置

  • Java堆区用于存储Java对象实例, 那么堆的大小在JVM启动时就已经设定好了,可以通过-Xms 和 -Xms来进行设置
    • -Xms用于表示堆区的起始内存, 等价于 -XX:InitialHeapSize
    • -Xmx用于表示堆区的最大内存, 等价于-XX:MaxHeapSize
    • -Xmn用于表示新生代中初始化和最大大小, 初始化大小等价于-XX:NewSize 最大初始化大小-XX:MaxNewSize
    • -Xss: 用于栈的大小, 等价于 -XX:ThreadStackSize , 默认是1024k 在32位机器就是512k
  • 一旦堆区中的内存大小超过了”-Xmx”所指定的最大内存时,将会抛出OutofMemoryError异常
  • 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区大小,从而提高性能
  • 默认情况下, 初始化内存大小, 物理电脑内存大小/ 64 最大内存大小, 物理电脑内存大小/4
  • 查看参数的方式

    • 方式1: jps / jstat -gc 进程Id
    • 方式2: -XX:+PrintGCDetails ```java /**

        1. 设置堆空间大小的参数
      • -Xms 用来设置堆空间大小(年轻代+老年代)的初始化内存大小
      • -X 是JVM的运行参数
      • ms 是memory start
      • -Xmx 默认堆空间大小 (年轻代+老年代)的最大内存大小
      • -X 是JVM运行参数
      • mx 是memory max *
        1. 默认的堆空间大小
      • 初始化内存大小: 物理电脑内存大小/64
      • 最大内存大小: 物理电脑内存大小/4 *
      • 3.在实际开发中建议将-Xms和-Xmx设置成一样的大小, 这可以减少GC进行回收之后又将空间进行重新分配, 从而提高性能 *
        1. 查看GC两种方式
      • <1. jps + jstat -gc 进程Id
      • <2 -XX:+PrintGCDetails */ public class TestHeap {

      public static void main(String[] args)throws Exception { // 返回Java虚拟机中堆初始化内存大小 long totalMemory = Runtime.getRuntime().totalMemory(); // 返回Java虚拟机中最大内存大小 long maxMemory = Runtime.getRuntime().maxMemory();

      System.out.println(totalMemory / 1024 / 1024 + “M”); System.out.println(maxMemory / 1024 / 1024 + “M”);

      System.out.println(totalMemory / 1024 / 1024 / 1024 64); System.out.println(maxMemory / 1024 / 1024 / 1024 4); // TimeUnit.SECONDS.sleep(10000); } }

  1. <a name="Qzb8V"></a>
  2. ## 3.OOM问题排查
  3. ```java
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. import java.util.concurrent.TimeUnit;
  7. public class OOMTest {
  8. public static void main(String[] args) throws Exception {
  9. List<TestDemo> list = new ArrayList<>();
  10. while (true) {
  11. TimeUnit.MILLISECONDS.sleep(20);
  12. list.add(new TestDemo(1024 * 1024 * 100 ));
  13. }
  14. }
  15. }
  16. class TestDemo {
  17. private byte[] bytes;
  18. public TestDemo(int length) {
  19. this.bytes = new byte[length];
  20. }
  21. # 老年区满了出现OOM
  22. }

image.png
image.png

2.年轻代和老年代

  • 存储在JVM中的Java对象可以被划分为两类
    • 一类是生命周期较短额瞬时对象, 这类对象的创建和消亡都非常迅速
    • 另外一类对象的生命周期确非常长,在某些极端的情况下还能够与JVM的生命周期保持一致
  • Java堆区进一步细分的话,可以划分为年轻代(Yound Gen)和老年去(Old Gen)
  • 其中年轻代又可以划分为Eden空间,Survivor0空间Survivor1空间(有时也叫做from区和to区)

image.png

image.png

  • 配置新生代和老年代在堆中结构的比占
    • 默认-XX: NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆得1/3

-XX:NewRatio=4 设置时 新生代和老年区就是1:4的比例
image.png

  • 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
  • 查看新生代和老年代的占比 jps / jinfo -flag NewRatio 进程Id 查看SurvivorRatio区 jps / jinfo -flag SurvivorRatio

新生代区和老年区默认比例为1:2, 列如下图 200m:400m(-Xmx600m -Xms600m)
image.png

  • 在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1:1

Eden区和Survivior区发现并不是8:1:1的比例而是6:1:1的比例,这是为什么呢?
这是因为JVM有着自适应的内存分配策略, 我们可以用-XX:-UseAdaptiveSizePolicy 关闭这个策略, 默认是开启的
image.png

  • 当然开发人员可以通过选项”-XX:SurvivorRatio”调整这个空间比例,比如-XX:SurvivorRatio=8 , 此参数是设置新生代中Eden区和Survivor区的比例

使用-XX:SurvivorRatio=8 Eden区和Survivor就是8:1:1
image.png

  • 几乎所有的Java对象都是在Eden区被new出来的
  • 绝大部分的Java对象的销毁都在新生代进行了.
    • IBM公司的专门研究,新生代中80%的对象都是朝生夕死的
  • 可以使用-Xmn设置新生代最大内存大小
    • 这个参数一般使用默认值即可
    • 存在一个疑问, 如果-XX:NewRatio和-Xmn同时设置, 那又该如何生效呢

-Xmx600m -Xms600m -XX:SurvivorRatio=8 -XX:NewRatio=4 -Xmn200m
由下图可以看出 如果NewRatio和Xmn同时设置的情况下, 有限使用Xmn来设置年轻代的所占的内存大小
image.png

  • -XX:+UseAdaptiveSizePolicy 关闭自适应的内存分配策略

    3.图解对象分配过程

    1.概述

    为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配,在哪里分配等问题并且由于内存分配算法和内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否在内存空间中产生内存碎片
  1. new的对象先放在伊甸园区,此区有大小限制, 默认初始化是物理内存的1/64 / 3 6, 最大就是 1 / 4 / 3 6
  2. 当伊甸园区的空间被填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁,再加载新的对象放到伊甸园区
  3. 然后将伊甸园区中的剩余对象移动到幸存者0区(S0 SurvivorGen)
  4. 如果再次触发垃圾回收机制,此时上次幸存下来的放到幸存者0区,如果没有回收,就会放到幸存者1区
  5. 如果再次经历垃圾回收机制,此时又会重新放入到Survivor0Gen, 之后再次GC就会放入Survivor1Gen
  6. 啥时候能去老年代区? 可以设置次数,默认是15次
    1. 可以设置次数: -XX:MaxTenuringThreshold=进行设置

image.png
image.png

2.对象分配的特殊情况

image.png

4.常用调优工具

  • JDK命令
  • Jconsole
  • VisualVM
  • Jprofiler
  • Java Flight Recorder
  • GCViewer
  • GC Easy

4.Minor / GC Major GC / Full GC

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的, 一部分时候回收的都是指新生代..

针对HotSpotVM的实现, 它里面的GC按照回收区域又分为两大种类型: 一种是部分收集(Partial GC),一种是整堆收集(Full GC)

1.垃圾回收机制

  • 部分收集, 不是完整收集整个Java堆的垃圾收集, 其中又分为:
    • 新生代收集(Minor GC/Young GC): 只是新生代的垃圾收集
    • 老年代收集(Major GC/Old GC): 只是老年代的垃圾收集
      • 目前只有CMS GC会有单独收集老年代的行为
      • 注意, 很多时候Major GC会和Full GC混淆使用, 需要具体分辨是老年代回收还是整堆回收
    • 混合收集(Mixed GC): 收集整个新生代以及部分老年代的垃圾收集.
      • 目前, 只有G1GC会有这个行为
  • 整堆收集(Full GC): 收集整个java堆和方法区的垃圾收集

    2.最简单的分代式GC策略

  • 年轻代GC(Minor GC)触发机制:

    • 当年轻代空间不足时, 就会触发Minor GC, 这里的年轻代满指的是Eden(伊甸园区)代满, Survivor满不会引发GC, (每次Minor GC都会清理年轻代的内存)
    • 因为Java对象大多都具备朝生夕灭的特性 , 所以MInor GC非常频繁 , 一般回收速度也比较快, 这一定义既清晰又易于理解
    • Minor GC会引发STW , 暂停其他用户的线程 , 等待垃圾回收结束 , 用户线程才恢复运行
  • 老年代GC(Major GC / Full GC)触发机制:
    • 指发生在老年代的GC, 对象从老年代消失时, 我们说”Major Gc”或者是”Full GC”发生了
    • 出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的, 在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)
      • 也就是在老年代空间不足时 , 会先尝试触发Minor GC, 如果之后空间还不足. 则触发Major GC
    • Major GC的速度一般会比Minor GC慢10倍以上. STW的时间更长
    • 如果Major Gc后, 内存还是不足, 就会出现OOM
    • Major GC的速度一般会比Minor GC慢10倍以上了.
  • Full GC触发机制:
    • 调用System.gc() , 系统建议执行Full GC, 但是不必然执行
    • 老年代空间不足
    • 方法区空间不足
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    • 由Eden区 , Sucvivor space0(From Space) 区向Survivor Space1(To Space)区复制时, 对象大小大于To Space可用内存, 则把该对象转存到老年代. 且老年代的可用内存小于改对象大小

说明: Full GC是开发或调优中尽量避免的, 这样短暂时间会短一些

  1. public class GCTestDemo {
  2. public static void main(String[] args) {
  3. try {
  4. List<String> list = new ArrayList<>();
  5. String str = "andav.info";
  6. while (true) {
  7. list.add(str);
  8. str += str;
  9. }
  10. } catch (Throwable throwable) {
  11. throwable.printStackTrace();
  12. }
  13. }
  14. }
  1. [GC (Allocation Failure) [PSYoungGen: 2030K->492K(2560K)] 2030K->592K(9728K), 0.0032759 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
  2. -- 此处2030K表示垃圾回收之前新生代区大小 492K表示 新生代垃圾回收之后的大小 2560K(eden space+from space + to space)表示新生代总共大小 2030K表示堆区GC之前的大小 592K表示堆区GC之后的大小 9728K表示堆区总共大小
  3. [GC (Allocation Failure) [PSYoungGen: 2292K->496K(2560K)] 2393K->1495K(9728K), 0.0016818 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  4. [GC (Allocation Failure) [PSYoungGen: 2496K->160K(2560K)] 7336K->5639K(9728K), 0.0049528 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  5. [GC (Allocation Failure) [PSYoungGen: 160K->176K(2560K)] 5639K->5655K(9728K), 0.0014012 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
  6. [Full GC (Allocation Failure) [PSYoungGen: 176K->0K(2560K)] [ParOldGen: 5479K->4214K(7168K)] 5655K->4214K(9728K), [Metaspace: 2989K->2989K(1056768K)], 0.0048889 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
  7. [GC (Allocation Failure) [PSYoungGen: 20K->32K(2560K)] 6795K->6806K(9728K), 0.0025552 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
  8. [Full GC (Ergonomics) [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 6774K->5494K(7168K)] 6806K->5494K(9728K), [Metaspace: 2989K->2989K(1056768K)], 0.0038947 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
  9. [GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 5494K->5494K(8704K), 0.0015570 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  10. [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 5494K->5478K(7168K)] 5494K->5478K(8704K), [Metaspace: 2989K->2989K(1056768K)], 0.0169945 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
  11. Heap
  12. PSYoungGen total 1536K, used 70K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
  13. eden space 1024K, 6% used [0x00000007bfd00000,0x00000007bfd11b50,0x00000007bfe00000)
  14. from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  15. to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  16. ParOldGen total 7168K, used 5478K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
  17. object space 7168K, 76% used [0x00000007bf600000,0x00000007bfb59ba8,0x00000007bfd00000)
  18. Metaspace used 3022K, capacity 4556K, committed 4864K, reserved 1056768K
  19. class space used 324K, capacity 392K, committed 512K, reserved 1048576K
  20. java.lang.OutOfMemoryError: Java heap space
  21. at java.util.Arrays.copyOfRange(Arrays.java:3664)
  22. at java.lang.String.<init>(String.java:207)
  23. at java.lang.StringBuilder.toString(StringBuilder.java:407)
  24. at com.anda.dachang.active_use.GCTestDemo.main(GCTestDemo.java:16)

5.堆空间分代思想

  • 经研究 , 不同对象的生命周期不同, 70%-99%的对象都是临时对象
    • 新生代: 有Eden , 两块大小相同的Survivor(又称之为from/to , s0/s1)构成,to总为空
    • 老年代: 存放新生代中经历多次GC依然存活的对象

image.png

  • 其实部分代完全可以, 分代的唯一理由就是优化GC性能, 如果没有分代, 那么所有的对象都在一块,GC的时候要找到哪些没用到的对象, 这样就会对堆的所有区域进行扫描, 而且很多对象都是招生夕死, 如果分代的话, 把创建的对象放到某一个地方, 当GC的时候先把这一块存储”朝生夕死”对象的区域进行回收, 这样就会腾出很大的空间出来

image.png

6.内存分配策略

如果对象在Eden出生并经过第一次MinorGC后仍然存活, 并且能被Survivor容纳的话, 将被移动到Survivor空间中, 并且将对象年龄设置1, 对象在Survivor区中每熬过一次MinorGC, 年龄就增加一岁 , 当它的年龄增加到一定程度(默认为15岁,其实每个JVM , 每个GC都有所不同的)时, 就会被晋升到老年代中.

对象晋升老年代的年龄阈值,可以通过选项 -XX:MaxTenuringThreshold来设置

针对不同年龄段的对象分配原则如下所示:

  • 优先分配到Eden区
  • 大对象直接分配到老年区
    • 尽量避免程序中出现过多的大对象
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断
    • 如果Survivor区中相同年龄的所有对象大小的总和和大于Survivor空间的一半, 年龄大于或等于该年龄的对象可以直接进入老年代, 无须等到MaxTenuringThreshold 中的要求的年龄.
  • 空间分配担保

    • -XX: HandlePromotionFailure

      1. /**
      2. * -Xmx60m -Xms60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
      3. *
      4. * 初始化内存和最大内存设置的是60m 默认新生代和老年代区的大小是1:2, 这里设置的参数也是NewRatio也是2(即1:2)
      5. * 所以年轻代和老年代就是1:2 , 20:40
      6. * 伊甸园区和S0区/S1区默认是8:1:1 , 也就是16:2:2
      7. *
      8. * eden 16m
      9. * s from 2m
      10. * s to 2m
      11. *
      12. * old 40m use 20m
      13. */
      14. public class OldGenTest {
      15. public static void main(String[] args) {
      16. /**
      17. * 此处直接使用 20m的对象直接存入, 由于20m>16m, 所以无法直接存入eden区, 而是直接存入old区
      18. */
      19. byte[] bytes = new byte[1024 * 1024 * 20];
      20. }
      21. }

      ```java Heap PSYoungGen total 18432K, used 2300K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000) eden space 16384K, 14% used [0x00000007bec00000,0x00000007bee3f2c8,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 3183K, capacity 4496K, committed 4864K, reserved 1056768K class space used 354K, capacity 388K, committed 512K, reserved 1048576K

  1. <a name="z9PcU"></a>
  2. # 7.为对象分配内存: TLAB
  3. <a name="idhtv"></a>
  4. ## 1.TLAB (Thread Local Allocation Buffer)
  5. - 堆区是线程共享区域, 任何线程都可以访问到堆区中的共享的数据
  6. - 由于对象实例的创建在JVM中非常频繁, 因此在并发环境下从堆区中划分内存空间是线程不安全的
  7. - 为了避免多个线程操作同一个地址, 需要使用加锁等机制, 进而影响分配速度
  8. - 从内存模型而不是垃圾收集的角度, 对Eden区域继续进行划分, JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内
  9. - 多个线程同时分配内存时, 使用TLAB可以避免一系列的非线程安全问题 , 同时还能够提升内存分配的吞吐量,因此我可以将这种内存分配方式称之为快速分配策略
  10. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25783451/1652522062459-66b0391e-e646-4e11-a3b0-4331ce313309.png#clientId=u74455fd7-3c6d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=618&id=u6b4d6090&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1236&originWidth=1568&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89664&status=done&style=none&taskId=uc38f26f2-e0f1-4385-b32b-5d43800bc67&title=&width=784)
  11. <a name="yK0VN"></a>
  12. ## 2.TLAB说明:
  13. - 尽管不是所有的对象实例都能够在TLAB中成功分配内存, 但**JVM确实是将TLAB作为内存分配的首选**
  14. - 在程序中, 可以使用"-XX:UseTLAB"设置是否开启TLAB空间
  15. - 默认情况下. TLAB空间的内存非常小, **仅占有整个Eden空间的1%** , 当然我们可以通过选项 "-XX:TLABWasteTargetPercent" 设置TLAB空间所占用Eden空间的百分比大小
  16. - 一旦对象在TLAB空间分配内存失败. JVM就会尝试通过**使用加锁机制**确保数据操作的原子性, 从而直接在Eden空间中分配内存.
  17. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25783451/1652541893230-06265f3c-3cbe-4c32-b9c1-e89079ce6b61.png#clientId=u74455fd7-3c6d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=848&id=u24983aac&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1696&originWidth=2410&originalType=binary&ratio=1&rotation=0&showTitle=false&size=257081&status=done&style=none&taskId=ua518fd82-341f-4c6f-98e6-e23c64dfc3a&title=&width=1205)
  18. <a name="jjNfK"></a>
  19. ## 3.如何查看TLAB时候开启呢?
  20. - 编写Java代码并且运行
  21. ```java
  22. /**
  23. * 测试-XX:+UseTLAB
  24. * @author anda
  25. * @since 1.0
  26. */
  27. public class TLABTest {
  28. public static void main(String[] args) throws Exception {
  29. TimeUnit.SECONDS.sleep(100000);
  30. }
  31. }
  • 查看当前运行进程 jps

image.png

  • 查看是否开启 jinfo -flag UseTLAB 28672

image.png

8.小结堆空间的参数设置

  1. /**
  2. * 测试堆空间常用的JVM参数
  3. * -XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
  4. * -XX:+PrintFlagsFinal : 查看所有的参数的最终值(可能会存在修改, 不再是初始值)
  5. * -Xms: 初始化堆空间内存大小(默认为物理内存大小的1/64)(-XX:HeapInitialSize)
  6. * -Xmx: 最大堆空间内存(默认为物理内存的1/4)(-XX:MaxHeapSize)
  7. * -Xmn: 设置新生代的大小(-XX:NewSize(设置新生代初始化大小) -XX:MaxNewSize(设置新生代最大大小))
  8. * -Xss: 设置栈的大小(默认是1024k在64位机器上 , 如果是在32位机器上就是256k或者是512k)
  9. * -XX:NewRatio: 配置新生代和老年代在堆结构的占比(默认是1:2 Young:Old)
  10. * -XX:SurvivorRation 设置新生代中Eden和S0/S1空间的比例(默认占比8:1:1 Eden:S0:S1)
  11. * -XX:MaxTenuringThreshold: 设置新生代垃圾的最大年龄(默认是15)
  12. * -XX:+PrintGCDetails: 输出详细的GC日志 打印gc简要信息 1.-XX:+PrintGC 2. -verbose:gc
  13. * -XX:HandlePromotionFailure: 是否设置空间分配担保 , (该参数可以在1.6之前是生效的, 之后JVM就遗弃该参数了)
  14. *
  15. * @author anda
  16. * @since 1.0
  17. */
  18. public class HeapArgsTest {
  19. public static void main(String[] args) {
  20. System.out.println("=============");
  21. }
  22. }
  • -XX:PrintFlagsInitial: 查看所有参数的默认初始值

image.png

  • -XX:PrintFlagsFinal: 查看所有参数的最终值(可能存在修改,不再是初始值)

image.png

  • -Xmx: 最大堆空间内存(默认是物理内存的1/4 , -XX:MaxHeapSize)
  • -Xms: 初始化堆空间的内存大小(默认是物理内存的1/64, -XX:InitialHeapSize)
  • -Xmn: 年轻代初始化大小(-XX:NewSize , -XX:MaxNewSize)
  • -Xss:: 设置栈的大小(-XX:ThreadStackSize )
  • -XX:NewRatio 设置新生代和老年代在堆空间的占比(默认是1:2)
  • -XX:SurvivorRatio 设置Eden/S0/S1之间的占比(默认是8:1:1)
  • -XX:MaxTenuringThreshold: 设置新生代垃圾最大年龄是15
  • -XX:+PrintGCDetails 打印GC的详细信息
  • -XX:+HandlePromotionFailure 是否设置空间分配担保 , (该参数可以在1.6之前是生效的, 之后JVM就遗弃该参数了)

在发生Minor GC之前, 虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间

  • 如果大于 , 此次Minor GC是安全的
  • 如果小于, 则虚拟机会查看-XX:HandlePromotionFailure 设置值是否允许担保失败
    • 如果HandlePromotionFailure =true, 那么会继续检查老年代最大可以用连续空间是否大于历次晋升到老年代的对象的平均大小
      • 如果大于, 则尝试进行一次Minor Gc, 但是这次Minor Gc依然是有风险的
      • 如果小于, 则一定会改成Full GC
    • 如果HandlePromotionFailure=false, 则直接改成为Full GC

在JDK6 Update24, HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略, 观察Open JDK还是保留了此参数, 但是代码中已经不会再使用此参数了
Open JDK规则改成了只要老年代的连续空间大于新生代对象总大小,或者历次晋升的平均大小就会进行Minor GC, 否则将进行Full GC.

9.逃逸分析

在Java虚拟机中 ,对象是在Java堆中分配内存的, 这是一个普遍的常识, 但是 , 有一种特殊情况, 那就是如果经过逃逸分析(Escape Analysis)后发现, 一个对象并没有逃逸出方法的话 , 那么就有可能被优化成栈上分配, 这样就无需在堆上分配内存 , 也无需进行垃圾回收了, 这也是最常见的堆外存储技术了.

1.逃逸分析概述

  • 如果将堆上的对象分配到栈 , 需要使用逃逸分析手段.
  • 这是一种有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法
  • 通过逃逸分析, Java Hotspot编译器能够分析出一个新的对象的引用使用范围从而决定是否要将这个对象分配到堆上
  • 逃逸分析的基本行为就是分析对象动态作用域:
    • 当一个对象在方法中被定义后, 对象只在方法内部使用, 则认为没有发生逃逸
    • 当一个对象在方法中被定义后, 他被外部方法所引用, 则认为发生逃逸, 列如作为调用参数传递到其他地方中.
  • 没有发生逃逸的对象, 则可以分配到栈上, 随着方法执行的结束, 栈空间就被移除了

2.参数设置

  • 在JDK6u23版本之后 , Hotspot中默认就已经开启了逃逸分析.
  • 如果使用比较早的版本, 可以使用以下方式
    • 选项”-XX: +DoEscapeAnalysis”显示开启逃逸分析
    • 通过”-XX:+PrintEscapeAnalysis” 查看逃逸分析的筛选结果