关于java:深入理解堆

14次阅读

共计 30059 个字符,预计需要花费 76 分钟才能阅读完成。

第 8 章 深刻了解堆

1、堆的外围概述

1.1、意识堆内存

堆与过程

  1. 堆针对一个 JVM 过程来说是惟一的,也就是 一个过程只有一个 JVM
  2. 然而 过程蕴含多个线程,他们是共享同一堆空间的

对堆的意识

  1. 一个 JVM 实例只存在一个堆内存,堆也是 Java 内存治理的外围区域。
  2. Java 堆区 在 JVM 启动的时候即被创立 ,其空间大小也就确定了,堆是 JVM 治理的最大一块内存空间,并且 堆内存的大小是能够调节的
  3. 《Java 虚拟机标准》规定,堆能够处于物理上不间断的内存空间中,但在逻辑上它应该被视为间断的
  4. 所有的线程共享 Java 堆,在这里还能够划分 线程公有的缓冲区(Thread Local Allocation Buffer,TLAB)。
  5. 《Java 虚拟机标准》中对 Java 堆的形容是:所有的对象实例以及数组都该当在运行时调配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)
  6. 老师说:从理论应用角度看的,” 简直 ” 所有的对象实例都在这里分配内存。因为还有一些对象是在栈上调配的(逃逸剖析,标量替换)
  7. 数组和对象可能永远不会存储在栈上,因为栈帧中保留援用,这个援用指向对象或者数组在堆中的地位。
  8. 在办法完结后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。

    • 也就是触发了 GC 的时候,才会进行回收
    • 如果堆中对象马上被回收,那么用户线程就会收到影响,因为有 stop the word
  9. 堆,是 GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。

一个 JVM 实例只存在一个堆内存,并且堆内存的大小是能够调节的

如何设置堆内存大小

  • 过程 1
-Xms10m -Xmx10m

  • 过程 2
-Xms20m -Xmx20m

1.2、查看堆内存

应用 JDK 自带的工具:Java VisualVM,来查看堆内存

  • Java VisualVM 在 JDK 的 bin 目录下

  • 过程 1:堆内存为 10 M

  • 过程 2:堆内存为 20 M

代码示例

  • 代码
public class SimpleHeap {
    private int id;

    public SimpleHeap(int id) {this.id = id;}

    public void show() {System.out.println("My ID is" + id);
    }

    public static void main(String[] args) {SimpleHeap sl = new SimpleHeap(1);
        SimpleHeap s2 = new SimpleHeap(2);
        int[] arr = new int[10];
        Object[] arr1 = new Object[10];
    }
}
  • 字节码指令

1.3、堆内存分区

堆内存细分

  1. Java 7 及之前堆内存逻辑上分为三局部:新生区 + 养老区 + 永恒区

    • Young/New Generation Space 新生区,又被划分为 Eden 区和 Survivor 区
    • Old/Tenure generation space 养老区
    • Permanent Space 永恒区 Perm
  2. Java 8 及之后堆内存逻辑上分为三局部:新生区 + 养老区 + 元空间

    • Young/New Generation Space 新生区,又被划分为 Eden 区和 Survivor 区
    • Old/Tenure generation space 养老区
    • Meta Space 元空间 Meta

  1. 约定:新生区
  2. 堆空间内部结构,JDK1.8 之前从永恒代 替换成 元空间

Java VisualVM 查看堆内存

  • 装置 Visual GC

  • 呐:新生代、老年代、元空间

2、设置堆内存大小与 OOM

2.1、设置堆内存

设置堆空间大小

  1. Java 堆区用于存储 Java 对象实例,那么堆的大小在 JVM 启动时就曾经设定好了,大家能够通过选项 ”-Xms” 和 ”-Xmx” 来进行设置。

    • -Xms用于示意堆区的起始内存,等价于-XX:InitialHeapSize
    • -Xmx则用于示意堆区的最大内存,等价于-XX:MaxHeapSize
  2. 一旦堆区中的内存大小超过 ”-Xmx” 所指定的最大内存时,将会抛出 OutofMemoryError 异样。
  3. 通常会将 -Xms 和 -Xmx 两个参数配置雷同的值,其目标是为了可能在 Java 垃圾回收机制清理完堆区后不须要从新分隔计算堆区的大小,从而进步性能。
  4. 默认状况下:

    • 初始内存大小:物理电脑内存大小 /64
    • 最大内存大小:物理电脑内存大小 /4

代码示例

  • 代码

public class HeapSpaceInitial {public static void main(String[] args) {long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;

        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms :" + initialMemory + "M");
        System.out.println("-Xmx :" + maxMemory + "M");
    }
}
  • 两种查看堆内存的形式

    • 形式一:命令行顺次执行如下两个指令

      • jps
      • jstat -gc 过程 id
    • 形式二:设置虚拟机参数 -XX:+PrintGCDetails
  • 为什么设置 600MB,算进去只有 575MB 呢?

    • JVM 认为幸存者 to 区并不寄存对象(to 区始终为空),所以没把它算上
    • 能够看到新生区的大小 = 伊甸园区大小 + 幸存者 from 区大小
    • 即 179200KB = 153600KB + 25600KB

2.2、OOM 举例

OOM 举例

  • 代码

public class OOMTest {public static void main(String[] args) {ArrayList<Picture> list = new ArrayList<>();
        while(true){
            try {Thread.sleep(20);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            list.add(new Picture(new Random().nextInt(1024 * 1024)));
        }
    }
}
  • 设置虚拟机参数
-Xms600m -Xmx600m
  • 监控堆内存变动:Old 区域一点一点在变大,直到最初一次垃圾回收器无奈回收垃圾时,堆内存被撑爆,抛出 OutOfMemoryError 谬误

  • 堆内存变动图

  • 剖析起因:* 大对象导致堆内存溢出

3、年老代与老年代

3.1、Java 对象分类

Java 对象的分类

存储在 JVM 中的 Java 对象能够被划分为两类:

  1. 一类是生命周期较短的刹时对象,这类对象的创立和沦亡都十分迅速

    • 生命周期短的,及时回收即可
    • 另外一类对象的生命周期却十分长,在某些极其的状况下还可能与 JVM 的生命周期保持一致
  2. Java 堆区进一步细分的话,能够划分为 年老代 (YoungGen)和 老年代(oldGen)
  3. 其中年老代又能够划分为 Eden 空间、Survivor0 空间和 Survivor1 空间(有时也叫做 from 区、to 区)

3.2、配置新老比例

配置新生代与老年代的比例

配置新生代与老年代在堆构造的占比(个别不会调)

  1. 默认-XX:NewRatio=2,示意新生代占 1,老年代占 2,新生代占整个堆的 1 /3
  2. 能够批改-XX:NewRatio=4,示意新生代占 1,老年代占 4,新生代占整个堆的 1 /5
  3. 当发现在整个我的项目中,生命周期长的对象偏多,那么就能够通过调整老年代的大小,来进行调优

新生区中的比例

  1. 在 HotSpot 中,Eden 空间和另外两个 survivor 空间缺省所占的比例是 8 : 1 : 1,当然开发人员能够通过选项 -XX:SurvivorRatio 调整这个空间比例。比方 -XX:SurvivorRatio=8
  2. 简直所有的 Java 对象都是在 Eden 区被 new 进去的。绝大部分的 Java 对象的销毁都在新生代进行了(有些大的对象在 Eden 区无奈存储时候,将间接进入老年代)
  3. IBM 公司的专门钻研表明,新生代中 80% 的对象都是 ” 朝生夕死 ” 的。
  4. 能够应用选项 ”-Xmn” 设置新生代最大内存大小,但这个参数个别应用默认值就能够了。
  5. 新生区的对象默认生命周期超过 15,就会去养老区养老

代码示例

  • 代码

public class EdenSurvivorTest {public static void main(String[] args) {System.out.println("我只是来打个酱油~");
        try {Thread.sleep(1000000);
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }
}
  • 通过命令行查看各种比例

    • 查看新生代与老年代的比例
jps
jinfo -flag NewRatios &#x8FDB;&#x7A0B;id
  • 查看新生区中伊甸园区与幸存者区的比例
jps
jinfo -flag SurvivorRatio &#x8FDB;&#x7A0B;id
  • 设置 JVM 参数
-Xms600m -Xmx600m -XX:SurvivorRatio=8
  • 新生区中:伊甸园区 : 幸存者 0 区 : 幸存者 1 区 = 8 : 1 : 1
  • 新生区 : 老年区 = 1 : 2

4、图解对象调配过程

4.1、对象调配过程

对象调配过程

对象调配难点:

为新对象分配内存是一件十分谨严和简单的工作,JVM 的设计者们不仅须要思考内存如何调配、在哪里调配等问题,并且因为内存调配算法与内存回收算法密切相关,所以还须要思考 GC 执行完内存回收后是否会在内存空间中产生内存碎片。

对象调配过程

  1. new 的对象先放伊甸园区。此区有大小限度。
  2. 当伊甸园的空间填满时,程序又须要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(MinorGC),将伊甸园区中的不再被其余对象所援用的对象进行销毁。再加载新的对象放到伊甸园区。
  3. 而后将伊甸园中的残余对象挪动到幸存者 0 区。
  4. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者 0 区的,如果没有回收,就会放到幸存者 1 区。
  5. 如果再次经验垃圾回收,此时会从新放回幸存者 0 区,接着再去幸存者 1 区。
  6. 啥时候能去养老区呢?能够设置次数。默认是 15 次。能够设置新生区进入养老区的年龄限度,设置 JVM 参数:-XX:MaxTenuringThreshold=N 进行设置
  7. 在养老区,绝对悠闲。当养老区内存不足时,再次触发 GC:Major GC,进行养老区的内存清理
  8. 若养老区执行了 Major GC 之后,发现仍然无奈进行对象的保留,就会产生 OOM 异样。

4.2、图解对象调配

图解对象调配过程

  • 咱们创立的对象,个别都是寄存在 Eden 区的,当咱们 Eden 区满了后,就会触发 GC 操作,个别被称为 YGC / Minor GC 操作

  • 当咱们进行一次垃圾收集后,红色的对象将会被回收,而绿色的独享还被占用着,寄存在 S0(Survivor From)区。同时咱们给每个对象设置了一个年龄计数器,通过一次回收后还存在的对象,将其年龄加 1。
  • 同时 Eden 区持续寄存对象,当 Eden 区再次存满的时候,又会触发一个 MinorGC 操作,此时 GC 将会把 Eden 和 Survivor From 中的对象进行一次垃圾收集,把存活的对象放到 Survivor To 区,同时让存活的对象年龄 + 1

  • 咱们持续一直的进行对象生成和垃圾回收,当 Survivor 中的对象的年龄达到 15 的时候,将会触发一次 Promotion 降职的操作,也就是将年老代中的对象降职到老年代中

代码示例

  • 代码

public class HeapInstanceTest {byte[] buffer = new byte[new Random().nextInt(1024 * 200)];

    public static void main(String[] args) {ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
        while (true) {list.add(new HeapInstanceTest());
            try {Thread.sleep(10);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }
}
  • 留神【伊甸园区、幸存者区、老年区】的内存变化趋势

4.3、非凡状况阐明

思考:幸存区满了咋办?

  1. 特地留神,在 Eden 区满了的时候,才会触发 MinorGC,而 幸存者区满了后,不会触发 MinorGC 操作
  2. 如果 Survivor 区满了后,将会触发一些非凡的规定,也就是可能间接降职老年代

对象调配的非凡状况

  1. 如果来了一个新对象,先看看 Eden 是否放的下?

    • 如果 Eden 放得下,则间接放到 Eden 区
    • 如果 Eden 放不下,则触发 YGC,执行垃圾回收,看看还能不能放下?放得下最好当然最好咯~~~
  2. 将对象放到老年区又有两种状况:

    • 如果 Eden 执行了 YGC 还是无奈放不下该对象,那没得方法,只能阐明是超大对象,只能间接怼到老年代
    • 那万一老年代都放不下,则先触发重 GC,再看看能不能放下,放得下最好,但如果还是放不下,那只能报 OOM 啦~~~
  3. 如果 Eden 区满了,将对象往幸存区拷贝时,发现幸存区放不下啦,那只能便宜了某些新对象,让他们间接降职至老年区

4.4、罕用调优工具

罕用的 JVM 调优工具

罕用调优工具

  1. JDK 命令行
  2. Eclipse:Memory Analyzer Tool
  3. Jconsole
  4. Visual VM(实时监控 举荐~)
  5. Jprofiler(举荐~)
  6. Java Flight Recorder(实时监控)
  7. GCViewer
  8. GCEasy

Jprofiler 根本应用

  • 在 IDEA 中启动 Jprofiler

  • 点击 Instrumentation

  • 抉择默认配置即可,点击【OK】启动

  • 喏~~~

总结

  1. 针对幸存者 s0,s1 区的总结:复制之后有替换,谁空谁是 to
  2. 对于垃圾回收:频繁在新生区收集,很少在老年代收集,简直不在永恒代和元空间进行收集
  3. 新生代采纳复制算法的目标:是为了缩小内碎片

5、GC 垃圾回收器

5.1、分代收集思维

Minor GC、Major GC、Full GC

  1. 咱们都晓得,JVM 的调优的一个环节,也就是垃圾收集,咱们须要尽量的防止垃圾回收,因为在垃圾回收的过程中,容易呈现 STW(Stop the World)的问题,而 Major GC 和 Full GC 呈现 STW 的工夫,是 Minor GC 的 10 倍以上
  2. JVM 在进行 GC 时,并非每次都对下面三个内存区域一起回收的,大部分时候回收的都是指新生代。针对 Hotspot VM 的实现,它外面的 GC 依照回收区域又分为两大种类型:一种是局部收集(Partial GC),一种是整堆收集(FullGC)

分代收集:

  1. 局部收集:不是残缺收集整个 Java 堆的垃圾收集。其中又分为:

    • 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
    • 老年代收集(Major GC/Old GC):只是老年代的圾收集。

      • 目前,只有 CMS GC 会有独自收集老年代的行为。
      • 留神,很多时候 Major GC 会和 Full GC 混同应用,须要具体分辨是 老年代回收还是整堆回收
    • 混合收集(Mixed GC):收集整个新生代以及局部老年代的垃圾收集。

      • 目前,只有 G1 GC 会有这种行为
  2. 整堆收集(Full GC):收集整个 java 堆和办法区的垃圾收集。

5.2、Young GC

年老代 GC(Minor GC)触发机制

  1. 当年老代空间有余时,就会触发 Minor GC,这里的年老代满指的是 Eden 代满,Survivor 满不会引发 GC。(每次 Minor GC 会清理年老代的内存)
  2. 因为 Java 对象大多都具备朝生夕灭的个性,所以 Minor GC 十分频繁,个别回收速度也比拟快。这一定义既清晰又易于了解。
  3. Minor GC 会引发 STW,暂停其它用户的线程,等垃圾回收完结,用户线程才复原运行

; 5.3、Major/Full GC

老年代 GC(MajorGC/Full GC)触发机制

  1. 指产生在老年代的 GC,对象从老年代隐没时,咱们说 “Major Gc” 或 “Full GC” 产生了
  2. 呈现了 MajorGc,常常会随同至多一次的 Minor GC

    • 但非相对的,在 Parallel Scavenge 收集器的收集策略里就有间接进行 MajorGC 的策略抉择过程
    • 也就是在老年代空间有余时,会先尝试触发 Minor GC(哈?我有点迷?),如果之后空间还有余,则触发 Major GC
  3. Major GC 的速度个别会比 Minor GC 慢 10 倍以上,STW 的工夫更长,如果 Major GC 后,内存还有余,就报 OOM 了

Full GC 触发机制(前面细讲)

触发 Full GC 执行的状况有如下五种:

  1. 调用 System.gc()时,零碎倡议执行 Fu11GC,然而不必然执行
  2. 老年代空间有余
  3. 办法区空间有余
  4. 通过 Minor GC 后进入老年代的均匀大小大于老年代的可用内存
  5. 由 Eden 区、survivor spacee(From Space)区向 survivor space1(To Space)区复制时,对象大小大于 To Space 可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

阐明:Full GC 是开发或调优中尽量要防止的。这样 STW 工夫会短一些

GC 日志剖析

  • 代码

public class GCTest {public static void main(String[] args) {
        int i = 0;
        try {List<String> list = new ArrayList<>();
            String a = "atguigu.com";
            while (true) {list.add(a);
                a = a + a;
                i++;
            }

        } catch (Throwable t) {t.printStackTrace();
            System.out.println("遍历次数为:" + i);
        }
    }
}
  • JVM 参数
-Xms9m -Xmx9m -XX:+PrintGCDetails
  • GC 日志:在 OOM 之前,肯定会触发一次 Full GC,因为只有在老年代空间有余时候,才会爆出 OOM 异样
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xms9m -Xmx9m -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=13200:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter08" com.atguigu.java1.GCTest
[GC (Allocation Failure) [PSYoungGen: 2020K->510K(2560K)] 2020K->812K(9728K), 0.0021339 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2550K->488K(2560K)] 2852K->2278K(9728K), 0.0005931 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1949K->504K(2560K)] 3740K->3062K(9728K), 0.0005918 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1319K->0K(2560K)] [ParOldGen: 6782K->4864K(7168K)] 8102K->4864K(9728K), [Metaspace: 3452K->3452K(1056768K)], 0.0050464 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4864K->4864K(9728K), 0.0003452 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4864K->4846K(7168K)] 4864K->4846K(9728K), [Metaspace: 3452K->3452K(1056768K)], 0.0061555 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
&#x904D;&#x5386;&#x6B21;&#x6570;&#x4E3A;&#xFF1A;16
Heap
 PSYoungGen      total 2560K, used 134K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 6% used [0x00000000ffd00000,0x00000000ffd219f0,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 7168K, used 4846K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 67% used [0x00000000ff600000,0x00000000ffabba00,0x00000000ffd00000)
 Metaspace       used 3498K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 384K, capacity 388K, committed 512K, reserved 1048576K
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at com.atguigu.java1.GCTest.main(GCTest.java:21)

Process finished with exit code 0
  • [PSYoungGen: 1319K->0K(2560K)]:年老代总空间为 2560K,以后占用 1319K,通过垃圾回收后残余 0K
  • [ParOldGen: 6782K->4864K(7168K)]:老年代总空间为 7168K,以后占用 6782K,通过垃圾回收后残余 4864K
  • 8102K->4864K(9728K):堆内存总空间为 9728K,以后占用 8102K,通过垃圾回收后残余 4864K
  • [Metaspace: 3452K->3452K(1056768K)]:元空间总空间为 1056768K,以后占用 3452K,通过垃圾回收后残余 3452K
  • 0.0050464 secs:垃圾回收用时 0.0050464 secs
[Full GC (Ergonomics) [PSYoungGen: 1319K->0K(2560K)] [ParOldGen: 6782K->4864K(7168K)] 8102K->4864K(9728K), [Metaspace: 3452K->3452K(1056768K)], 0.0050464 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

6、堆空间分代思维

为什么须要分代?

  1. 为什么要把 Java 堆分代?不分代就不能失常工作了吗?经钻研,不同对象的生命周期不同。70%-99% 的对象是长期对象。

    • 新生代:有 Eden、两块大小雷同的 survivor(又称为 from/to,s0/s1)形成,to 总为空。
    • 老年代:寄存新生代中经验屡次 GC 依然存活的对象。
  2. 其实不分代齐全能够,分代的惟一理由就是优化 GC 性能

    • 如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC 的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。
    • 而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当 GC 的时候先把这块存储 ” 朝生夕死 ” 对象的区域进行回收,这样就会腾出很大的空间进去。

7、内存调配策略

内存调配策略或对象晋升(Promotion)规定

  1. 如果对象在 Eden 出世并通过第一次 Minor GC 后依然存活,并且能被 Survivor 包容的话,将被挪动到 Survivor 空间中,并将对象年龄设为 1。
  2. 对象在 Survivor 区中每熬过一次 MinorGC,年龄就减少 1 岁,当它的年龄减少到肯定水平(默认为 15 岁,其实每个 JVM、每个 GC 都有所不同)时,就会被降职到老年代
  3. 对象降职老年代的年龄阀值,能够通过选项 -XX:MaxTenuringThreshold 来设置

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

  1. 优先调配到 Eden:开发中比拟长的字符串或者数组,会间接存在老年代,然而因为新创建的对象都是朝生夕死的,所以这个大对象可能也很快被回收,然而因为老年代触发 Major GC 的次数比 Minor GC 要更少,因而可能回收起来就会比较慢
  2. 大对象间接调配到老年代:尽量避免程序中呈现过多的大对象
  3. 长期存活的对象调配到老年代
  4. 动静对象年龄判断:如果 Survivor 区中雷同年龄的所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象能够间接进入老年代,毋庸等到 MaxTenuringThreshold 中要求的年龄。
  5. 空间调配担保:-XX:HandlePromotionFailure,也就是通过 Minor GC 后,所有的对象都存活,因为 Survivor 比拟小,所以就须要将 Survivor 无奈包容的对象,寄存到老年代中。

代码示例

  • 代码

public class YoungOldAreaTest {public static void main(String[] args) {byte[] buffer = new byte[1024 * 1024 * 20];

    }
}
  • JVM 参数
-Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
  • 整个过程并没有进行垃圾回收,并且 ParOldGen 区间接占用了 20MB 的空间,阐明大对象间接怼到了老年代中
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=5495:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter08" com.atguigu.java1.YoungOldAreaTest
Heap
 PSYoungGen      total 18432K, used 2637K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  eden space 16384K, 16% used [0x00000000fec00000,0x00000000fee935c8,0x00000000ffc00000)
  from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
  to   space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
 ParOldGen       total 40960K, used 20480K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
  object space 40960K, 50% used [0x00000000fc400000,0x00000000fd800010,0x00000000fec00000)
 Metaspace       used 3469K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

8、为对象分配内存

8.1、为什么有 TLAB

问题:堆空间都是共享的么?

不肯定,因为还有 TLAB 这个概念,在堆中划分出一块区域,为每个线程所独占

为什么有 TLAB(Thread Local Allocation Buffer)?

  1. TLAB:Thread Local Allocation Buffer,也就是为每个线程独自调配了一个缓冲区
  2. 堆区是线程共享区域,任何线程都能够拜访到堆区中的共享数据
  3. 因为对象实例的创立在 JVM 中十分频繁,因而在并发环境下从堆区中划分内存空间是线程不平安的
  4. 为防止多个线程操作同一地址,须要应用 加锁等机制,进而影响调配速度。

8.2、什么是 TLAB

什么是 TLAB?

  1. 从内存模型而不是垃圾收集的角度,对 Eden 区域持续进行划分,JVM 为每个线程调配了一个公有缓存区域,它蕴含在 Eden 空间内
  2. 多线程同时分配内存时,应用 TLAB 能够防止一系列的非线程平安问题,同时还可能晋升内存调配的吞吐量 ,因而咱们能够将这种内存调配形式称之为 疾速调配策略
  3. 据我所知所有 OpenJDK 衍生进去的 JVM 都提供了 TLAB 的设计。

8.3、TLAB 调配过程

TLAB 的阐明

  1. 只管不是所有的对象实例都可能在 TLAB 中胜利分配内存,但 JVM 的确是将 TLAB 作为内存调配的首选
  2. 在程序中,开发人员能够通过选项 ” -XX:UseTLAB“ 设置是否开启 TLAB 空间。
  3. 默认状况下,TLAB 空间的内存十分小,仅占有整个 Eden 空间的 1%,当然咱们能够通过选项 ” -XX:TLABWasteTargetPercent“ 设置 TLAB 空间所占用 Eden 空间的百分比大小。
  4. 一旦对象在 TLAB 空间分配内存失败时,JVM 就会尝试着通过 应用加锁机制确保数据操作的原子性,从而间接在 Eden 空间中分配内存。

TLAB 调配过程

代码示例

  • 代码

public class TLABArgsTest {public static void main(String[] args) {System.out.println("我只是来打个酱油~");
        try {Thread.sleep(1000000);
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }
}
  • 查看 UseTLAB 标记位的状态
C:\Users\Heygo>jps
C:\Users\Heygo>jinfo -flag UseTLAB 15420
  • 我并没有设置任何 JVM 参数,通过命令行查看 TLAB 是否开启:论断是默认状况是开启 TLAB

9、堆空间参数设置

9.1、罕用参数设置

官网文档

https://docs.oracle.com/javas…

罕用参数设置

  1. -XX:+PrintFlagsInitial:查看所有的参数的默认初始值
  2. -XX:+PrintFlagsFinal:查看所有的参数的最终值(可能会存在批改,不再是初始值)
  3. -Xms:初始堆空间内存(默认为物理内存的 1 /64)
  4. -Xmx:最大堆空间内存(默认为物理内存的 1 /4)
  5. -Xmn:设置新生代的大小(初始值及最大值)
  6. -XX:NewRatio:配置新生代与老年代在堆构造的占比
  7. -XX:SurvivorRatio:设置新生代中 Eden 和 S0/S1 空间的比例
  8. -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
  9. -XX:+PrintGCDetails:输入具体的 GC 解决日志
  10. -XX:+PrintGC 或 -verbose:gc:打印 gc 简要信息
  11. -XX:HandlePromotionFalilure:是否设置空间调配担保

9.2、空间调配担保

对于空间调配担保

在产生 Minor GC 之前,虚构机会查看老年代最大可用的间断空间是否大于新生代所有对象的总空间。

  • 如果大于,则此次 Minor GC 是平安的
  • 如果小于,则虚构机会查看 -XX:HandlePromotionFailure 设置值是否允担保失败。

    • 如果 HandlePromotionFailure=true,那么会持续查看 老年代最大可用间断空间是否大于历次降职到老年代的对象的均匀大小

      • 如果大于,则尝试进行一次 Minor GC,但这次 Minor GC 仍然是有危险的;
      • 如果小于,则进行一次 Full GC。
    • 如果 HandlePromotionFailure=false,则进行一次 Full GC。

历史版本

  1. 在 JDK6 Update 24 之后,HandlePromotionFailure 参数不会再影响到虚拟机的空间调配担保策略,察看 openJDK 中的源码变动,尽管源码中还定义了 HandlePromotionFailure 参数,然而在代码中曾经不会再应用它。
  2. JDK6 Update 24 之后的规定变为只有老年代的间断空间大于新生代对象总大小或者历次降职的均匀大小就会进行 Minor GC,否则将进行 Full GC。即 HandlePromotionFailure=true

代码示例

  • 代码

public class HeapArgsTest {public static void main(String[] args) {}}

10、致命面试题

堆是调配对象的惟一抉择么?

在《深刻了解 Java 虚拟机》中对于 Java 堆内存有这样一段形容:

  1. 随着 JIT 编译期的倒退与逃逸剖析技术逐步成熟,栈上调配、标量替换 优化技术将会导致一些奥妙的变动,所有的对象都调配到堆上也慢慢变得不那么 ” 相对 ” 了。
  2. 在 Java 虚拟机中,对象是在 Java 堆中分配内存的,这是一个广泛的常识。然而,有一种非凡状况,那就是如果通过逃逸剖析(Escape Analysis)后发现,一个对象并没有逃逸出办法的话,那么就可能被优化成栈上调配。这样就无需在堆上分配内存,也毋庸进行垃圾回收了。这也是最常见的堆外存储技术。
  3. 此外,后面提到的基于 OpenJDK 深度定制的 TaoBao VM,其中翻新的 GCIH(GC invisible heap)技术实现 off-heap,将生命周期较长的 Java 对象从 heap 中移至 heap 外,并且 GC 不能治理 GCIH 外部的 Java 对象,以此达到升高 GC 的回收频率和晋升 GC 的回收效率的目标。

如何将堆上的对象调配到栈,须要应用逃逸剖析伎俩。

  1. 这是一种能够无效缩小 Java 程序中同步负载和内存堆调配压力的跨函数全局数据流剖析算法。
  2. 通过逃逸剖析,Java Hotspot 编译器可能剖析出一个新的对象的援用的应用范畴从而决定是否要将这个对象调配到堆上。
  3. 逃逸剖析的根本行为就是剖析对象动静作用域:

    • 当一个对象在办法中被定义后,对象只在办法外部应用,则认为没有产生逃逸
    • 当一个对象在办法中被定义后,它被内部办法所援用,则认为产生逃逸。例如作为调用参数传递到其余中央中。

10.1、逃逸剖析

逃逸剖析举例

  • 没有产生逃逸的对象,则能够调配到栈上,随着办法执行的完结,栈空间就被移除
public void my_method() {V v = new V();

    v = null;
}
  • 上面代码中的 StringBuffer sb 产生了逃逸
public static StringBuffer createStringBuffer(String s1, String s2) {StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}
  • 如果想要 StringBuffer sb 不产生逃逸,能够这样写
public static String createStringBuffer(String s1, String s2) {StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();}
  • 逃逸剖析的举例

public class EscapeAnalysis {

    public EscapeAnalysis obj;

    public EscapeAnalysis getInstance(){return obj == null? new EscapeAnalysis() : obj;
    }

    public void setObj(){this.obj = new EscapeAnalysis();
    }

    public void useEscapeAnalysis(){EscapeAnalysis e = new EscapeAnalysis();
    }

    public void useEscapeAnalysis1(){EscapeAnalysis e = getInstance();

    }
}

逃逸剖析参数设置

  1. 在 JDK 1.7 版本之后,HotSpot 中默认就曾经开启了逃逸剖析
  2. 如果应用的是较早的版本,开发人员则能够通过:

    • 选项 ”-XX:+DoEscapeAnalysis” 显式开启逃逸剖析
    • 通过选项 ”-XX:+PrintEscapeAnalysis” 查看逃逸剖析的筛选后果

逃逸剖析论断

开发中能应用局部变量的,就不要应用在办法外定义

逃逸剖析之代码优化

应用逃逸剖析,编译器能够对代码做如下优化:

  1. 栈上调配:将堆调配转化为栈调配。如果一个对象在子程序中被调配,要使指向该对象的指针永远不会产生逃逸,对象可能是栈上调配的候选,而不是堆上调配
  2. 同步省略:如果一个对象被发现只有一个线程被拜访到,那么对于这个对象的操作能够不思考同步。
  3. 拆散对象或标量替换:有的对象可能不须要作为一个间断的内存构造存在也能够被拜访到,那么对象的局部(或全副)能够不存储在内存,而是存储在 CPU 寄存器中。

10.2、栈上调配

栈上调配

  1. JIT 编译器在编译期间依据逃逸剖析的后果,发现如果一个对象并没有逃逸出办法的话,就可能被优化成栈上调配。
  2. 调配实现后,持续在调用栈内执行,最初线程完结,栈空间被回收,局部变量对象也被回收。这样就毋庸进行垃圾回收了。
  3. 常见的栈上调配的场景:在逃逸剖析中,曾经阐明了,别离是给成员变量赋值、办法返回值、实例援用传递。

栈上调配举例

  • 代码

public class StackAllocation {public static void main(String[] args) {long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {alloc();
        }

        long end = System.currentTimeMillis();
        System.out.println("破费的工夫为:" + (end - start) + "ms");

        try {Thread.sleep(1000000);
        } catch (InterruptedException e1) {e1.printStackTrace();
        }
    }

    private static void alloc() {User user = new User();
    }

    static class User {}}

未开启逃逸剖析的状况

  • JVM 参数设置
-Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
  • 日志打印:产生了 GC,耗时 46ms
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xmx256m -Xms256m -XX:-DoEscapeAnalysis -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=4674:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter08" com.atguigu.java2.StackAllocation
[GC (Allocation Failure) [PSYoungGen: 65536K->808K(76288K)] 65536K->816K(251392K), 0.0009467 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 66344K->872K(76288K)] 66352K->880K(251392K), 0.0006768 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
&#x82B1;&#x8D39;&#x7684;&#x65F6;&#x95F4;&#x4E3A;&#xFF1A; 46 ms
  • 堆下面有好多好多 User 对象

开启逃逸剖析的状况

  • JVM 参数设置
-Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
  • 日志打印:并没有产生 GC,耗时 3ms,栈上调配是真的快啊
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xmx256m -Xms256m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=4732:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter08" com.atguigu.java2.StackAllocation
&#x82B1;&#x8D39;&#x7684;&#x65F6;&#x95F4;&#x4E3A;&#xFF1A; 3 ms
  • 其实我有点迷糊,不是说栈上调配吗,为啥堆中还是有 User 对象?

10.3、同步省略

同步省略

  1. 线程同步的代价是相当高的,同步的结果是升高并发性和性能
  2. 在动静编译同步块的时候,JIT 编译器能够借助逃逸剖析来判断同步块所应用的锁对象是否只可能被一个线程拜访而没有被公布到其余线程。
  3. 如果没有,那么 JIT 编译器在编译这个同步块的时候就会勾销对这部分代码的同步。这样就能大大提高并发性和性能。这个 勾销同步的过程就叫同步省略,也叫锁打消
  • 例如上面的智障代码,基本起不到锁的作用
public void f() {Object hellis = new Object();
    synchronized(hellis) {System.out.println(hellis);
    }
}
  • 代码中对 hellis 这个对象加锁,然而 hellis 对象的生命周期只在 f()办法中,并不会被其余线程所拜访到,所以在 JIT 编译阶段就会被优化掉,优化成:
public void f() {Object hellis = new Object();
    System.out.println(hellis);
}

字节码剖析

  • 代码
public void f() {Object hellis = new Object();
    synchronized(hellis) {System.out.println(hellis);
    }
}
  • 留神:字节码文件中并没有进行优化,能够看到加锁和开释锁的操作仍然存在,* 同步省略操作是在解释运行时产生的

10.4、标量替换

拆散对象或标量替换

  1. 标量(scalar)是指一个无奈再分解成更小的数据的数据。Java 中的原始数据类型就是标量
  2. 绝对的,那些还能够合成的数据叫做聚合量(Aggregate),Java 中的对象就是聚合量,因为他能够分解成其余聚合量和标量。
  3. 在 JIT 阶段,如果通过逃逸剖析,发现一个对象不会被外界拜访的话,那么通过 JIT 优化,就会 把这个对象拆解成若干个其中蕴含的若干个成员变量来代替。这个过程就是标量替换

标量替换举例

  • 代码
public static void main(String args[]) {alloc();
}
class Point {
    private int x;
    private int y;
}
private static void alloc() {Point point = new Point(1,2);
    System.out.println("point.x" + point.x + ";point.y" + point.y);
}
  • 以上代码,通过标量替换后,就会变成
private static void alloc() {
    int x = 1;
    int y = 2;
    System.out.println("point.x =" + x + "; point.y=" + y);
}

论断:

  1. 能够看到,Point 这个聚合量通过逃逸剖析后,发现他并没有逃逸,就被替换成两个聚合量了。
  2. 那么标量替换有什么益处呢?就是能够大大减少堆内存的占用。因为一旦不须要创建对象了,那么就不再须要调配堆内存了。
  3. 标量替换为栈上调配提供了很好的根底。

标量替换参数设置

参数 -XX:+ElimilnateAllocations:开启了标量替换(默认关上),容许将对象打散调配在栈上。

代码示例

  • 代码

public class ScalarReplace {
    public static class User {
        public int id;
        public String name;
    }

    public static void alloc() {User u = new User();
        u.id = 5;
        u.name = "www.atguigu.com";
    }

    public static void main(String[] args) {long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {alloc();
        }
        long end = System.currentTimeMillis();
        System.out.println("破费的工夫为:" + (end - start) + "ms");
    }
}

未开启标量替换

  • JVM 参数
-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
  • 日志剖析:随同着 GC 的垃圾回收,用时 46ms
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=12593:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter08" com.atguigu.java2.ScalarReplace
[GC (Allocation Failure)  25600K->816K(98304K), 0.0009418 secs]
[GC (Allocation Failure)  26416K->792K(98304K), 0.0007337 secs]
[GC (Allocation Failure)  26392K->792K(98304K), 0.0006104 secs]
[GC (Allocation Failure)  26392K->856K(98304K), 0.0009474 secs]
[GC (Allocation Failure)  26456K->824K(98304K), 0.0007392 secs]
[GC (Allocation Failure)  26424K->808K(101376K), 0.0009449 secs]
[GC (Allocation Failure)  32552K->720K(101376K), 0.0010633 secs]
[GC (Allocation Failure)  32464K->720K(100352K), 0.0004493 secs]
&#x82B1;&#x8D39;&#x7684;&#x65F6;&#x95F4;&#x4E3A;&#xFF1A; 46 ms

Process finished with exit code 0

开启标量替换

  • JVM 参数
-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
  • 日志剖析:无垃圾回收,用时 4ms
"C:\Program Files\Java\jdk1.8.0_144\bin\java" -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar=12607:C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;C:\Users\Heygo\Desktop\JVMDemo\out\production\chapter08" com.atguigu.java2.ScalarReplace
&#x82B1;&#x8D39;&#x7684;&#x65F6;&#x95F4;&#x4E3A;&#xFF1A; 4 ms

Process finished with exit code 0

逃逸剖析参数设置总结

  1. 上述代码在主函数中调用了 1 亿次 alloc()办法,进行对象创立
  2. 因为 User 对象实例须要占据约 16 字节的空间,因而累计调配空间达到将近 1.5GB。
  3. 如果堆空间小于这个值,就必然会产生 GC。应用如下参数运行上述代码:
-server -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations

这里设置参数如下:

  1. 参数 -server:启动 Server 模式,因为在 server 模式下,才能够启用逃逸剖析。
  2. 参数 -XX:+DoEscapeAnalysis:启用逃逸剖析
  3. 参数 -Xmx10m:指定了堆空间最大为 10MB
  4. 参数 -XX:+PrintGC:将打印 GC 日志。
  5. 参数 -XX:+EliminateAllocations:开启了标量替换(默认关上),容许将对象打散调配在栈上,比方对象领有 id 和 name 两个字段,那么这两个字段将会被视为两个独立的局部变量进行调配

逃逸剖析的有余

  1. 对于逃逸剖析的论文在 1999 年就曾经发表了,但直到 JDK1.6 才有实现,而且这项技术到现在也并不是非常成熟的。
  2. 其根本原因就是无奈保障逃逸剖析的性能耗费肯定能高于他的耗费。尽管通过逃逸剖析能够做标量替换、栈上调配、和锁打消。然而逃逸剖析本身也是须要进行一系列简单的剖析的,这其实也是一个绝对耗时的过程。一个极其的例子,就是通过逃逸剖析之后,发现没有一个对象是不逃逸的。那这个逃逸剖析的过程就白白浪费掉了。
  3. 尽管这项技术并不非常成熟,然而它也是即时编译器优化技术中一个非常重要的伎俩。留神到有一些观点,认为通过逃逸剖析,JVM 会在栈上调配那些不会逃逸的对象,这在实践上是可行的,然而取决于 JVM 设计者的抉择。
  4. 据我所知,Oracle Hotspot JVM 中并未这么做,这一点在逃逸剖析相干的文档里曾经阐明,所以能够明确所有的对象实例都是创立在堆上。
  5. 目前很多书籍还是基于 JDK7 以前的版本,JDK 曾经产生了很大变动,intern 字符串的缓存和动态变量已经都被调配在永恒代上,而永恒代曾经被元数据区取代。然而 intern 字符串缓存和动态变量并不是被转移到元数据区,而是间接在堆上调配,所以这一点同样合乎后面一点的论断:对象实例都是调配在堆上。

堆是调配对象的惟一抉择么?

综上:对象实例都是调配在堆上。What the fuck?

11、堆小结

  1. 年老代是对象的诞生、成长、沦亡的区域,一个对象在这里产生、利用,最初被垃圾回收器收集、完结生命。
  2. 老年代搁置长生命周期的对象,通常都是从 Survivor 区域筛选拷贝过去的 Java 对象。
  3. 当然,也有非凡状况,咱们晓得一般的对象可能会被调配在 TLAB 上;
  4. 如果对象较大,无奈调配在 TLAB 上,则 JVM 会试图间接调配在 Eden 其余地位上;
  5. 如果对象太大,齐全无奈在新生代找到足够长的间断闲暇空间,JVM 就会间接调配到老年代。
  6. 当 GC 只产生在年老代中,回收年老代对象的行为被称为 Minor GC。
  7. 当 GC 产生在老年代时则被称为 Major GC 或者 Full GC。
  8. 个别的,Minor GC 的产生频率要比 Major GC 高很多,即老年代中垃圾回收产生的频率将大大低于年老代。

你只管学习,我来负责记笔记???? 关注公众号!, 更多笔记,等你来拿,谢谢



正文完
 0