Java - 你一定要知道的 JVM 逃逸分析

简介: Java - 你一定要知道的 JVM 逃逸分析

提到JVM,相信大家一定知道JVM是什么?但是,提到逃逸分析,相信大多数人都可能一脸懵逼,逃逸分析到底是什么呢?接下来给大家分享一下。

在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经历两段编译,第一段编译就是通过javac命令把java文件编译成JVM可以识别的class文件,第二段编译就是把class文件翻译成字节码指令,让计算机识别。

在第二段编译中,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比执行二进制字节码程序慢很多。这就是传统的JVM的解释器的功能。为了解决这种效率问题,引入了JIT(即时编译)技术。

引入JIT技术,Java程序虽然还是通过解释器进行解释执行,但是,当某个方法执行调用的次数比较多的时候,就会被JVM认为是个"热点代码"。那么,如果是你,你会想到怎么做呢?诶,没错,把这些"热点代码"缓存起来。JIT也是这么做的,JIT会把部分"热点代码"翻译成本地机器相关的机器码,并进行优化,然后再把翻译后的机器码缓存起来,以备下次使用。

JVM内存分配策略

在JVM中,JVM管理的内存包括方法区,虚拟机栈,本地方法栈,堆,程序计数器等。(这里就不一一介绍了,感兴趣的同学我之后会写JVM的文章)

一般情况下,JVM运行时数据都存储在栈和堆中。栈用来存放一些基本变量和对象的引用(当然这不是绝对的后面会介绍到),堆用来存放数组的元素和对象,也就是new出来的具体实例。

随着JIT编译器的发展与逃逸分析的技术成熟。栈上分配,标量替换优化技术就会导致对象都分配到堆上这个说法变的不是那么绝对了。

什么是逃逸分析?

逃逸分析就是,当一个对象被new出来之后,它可能被外部所调用,如果是作为参数传递到外部了,就称之为方法逃逸。

例如

非方法逃逸

publicstaticvoidreturnStr(){
Useruser=newUser();
user.setId(1);
user.setName("张三");
user.setAge(18);
}
publicstaticStringreturnStr(){
Useruser=newUser();
user.setId(1);
user.setName("张三");
user.setAge(18);
returnuser.toString();//这里User要实现get,set方法,还要实现toString方法}

方法逃逸

publicstaticUserreturnStr(){
Useruser=newUser();
user.setId(1);
user.setName("张三");
user.setAge(18);
returnuser;//这里User要实现get,set方法}

大家应该看出区别了吧,这里第一段的两个方法均没有逃逸,而第二段的方法却逃逸了,这说明,想要逃逸方法的话,需要让对象本身被外部调用。

使用逃逸分析,编译器可以对代码做以下优化:

  1. 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
  2. 将堆分配转换为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
  3. 分离对象或标量替换,有点对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

这里开启和关闭逃逸分析用这个

  • -XX:+DoEscapeAnalysis : 表示开启逃逸分析
  • -XX:-DoEscapeAnalysis : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定-XX:-DoEscapeAnalysis

实战:通过打印GC来观察是否是栈上分配

publicclassTest {
publicstaticvoidmain(String[] args) {
Testtest=newTest();
test.createUser();
System.out.println("11");
    }
publicvoidcreateUser(){
inti=0;
while(true){
Useruser=newUser();
user.setId(i);
user.setName("张三");
user.setAge(18);
i++;
        }
    }
}
publicclassUser {
privateintid;
privateStringname;
privateintage;
publicintgetId() {
returnid;
    }
publicvoidsetId(intid) {
this.id=id;
    }
publicStringgetName() {
returnname;
    }
publicvoidsetName(Stringname) {
this.name=name;
    }
publicintgetAge() {
returnage;
    }
publicvoidsetAge(intage) {
this.age=age;
    }
}

这里有两个类,第一个是Test类,一个是User实体类,Test类中,死循环来进行创建User对象,在idea中配置 -XX:+PrintGC参数来负责打印GC信息,启动类

结果

image.png这里可以看到程序一直没有结束,但是GC信息一直没有打印,这时候我们把逃逸分析关闭 在idea中配置这两个-XX:+PrintGC -XX:-DoEscapeAnalysis

再次启动类

image.png

这里可以看到控制台一直在输出GC信息,这样是不是也可以得出结论,开启逃逸分析之后如果不是逃逸方法,那么对象就是在栈上分配。

同步省略

在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有发布到其他线程。

如果同步块所使用的锁对象通过这种分析被证明只能被一个线程访问,那么JIT编译器在编译这个同步块的时候就会取消这部分代码的同步,这个取消同步就叫做同步省略,也叫锁消除。

标量替换

通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。开启标量替换参数-XX:+EliminateAllocations,JDK7之后默认开启

标量与聚合量

标量即不可被进一步分解的量,而Java的基本数据类型就是标量(比如int,long等基本数据类型以及reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在Java中对象就是可以被进一步分解的聚合量

在JIT阶段,如果经过逃逸分析,发现一个对象不被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含若干个成员变量来替代。

publicstaticvoidmain(String[] args) {
alloc();
}
privatestaticvoidalloc() {
Pointpoint=newPoint(1,2;
System.out.println("point.x="+point.x+"; point.y="+point.y);
}
classPoint{
privateintx;
privateinty;
}

以上代码中,point对象并没有逃逸出alloc方法,并且point对象是可以拆解成标量的。那么,JIT就会不会直接创建Point对象,而是直接使用两个标量int x ,int y来替代Point对象。

替换后

privatestaticvoidalloc() {
intx=1;
inty=2;
System.out.println("point.x="+x+"; point.y="+y);
}

这种替换可以大大减少堆内存的占用,因为一旦不需要创建对象了,那么就不需要分配堆内存了。

目录
相关文章
|
27天前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
18天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
25 0
|
15天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
20天前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
18天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
22天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
31 1
|
22天前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
48 1
|
1月前
|
监控 Java 开发者
Java虚拟机(JVM)深度优化指南####
本文深入探讨了Java虚拟机(JVM)的工作原理及其性能优化策略,旨在帮助开发者通过理解JVM的内部机制来提升Java应用的运行效率。不同于传统的技术教程,本文采用案例分析与实战技巧相结合的方式,为读者揭示JVM调优的艺术。 ####
58 8
|
28天前
|
监控 算法 Java
深入理解Java虚拟机(JVM)的垃圾回收机制
【10月更文挑战第21天】 本文将带你深入了解Java虚拟机(JVM)的垃圾回收机制,包括它的工作原理、常见的垃圾收集算法以及如何优化JVM垃圾回收性能。通过本文,你将对JVM垃圾回收有一个全新的认识,并学会如何在实际开发中进行有效的调优。
41 0
|
26天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
210 1
下一篇
DataWorks