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);
}

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

目录
相关文章
|
3月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
119 4
|
1月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
182 2
|
1月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
197 1
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
7月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
983 4
|
3月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
295 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
3月前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
|
4月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
5月前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
4月前
|
存储 运维 Kubernetes
Java启动参数JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
本文介绍了Java虚拟机(JVM)常用启动参数配置,包括设置初始堆内存(-Xms512m)、最大堆内存(-Xmx1024m)及内存溢出时生成堆转储文件(-XX:+HeapDumpOnOutOfMemoryError),用于性能调优与故障排查。
426 0