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

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

目录
相关文章
|
5月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
531 4
|
1月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
144 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
2月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
2月前
|
存储 运维 Kubernetes
Java启动参数JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
本文介绍了Java虚拟机(JVM)常用启动参数配置,包括设置初始堆内存(-Xms512m)、最大堆内存(-Xmx1024m)及内存溢出时生成堆转储文件(-XX:+HeapDumpOnOutOfMemoryError),用于性能调优与故障排查。
276 0
|
3月前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
4月前
|
存储 监控 算法
Java程序员必学:JVM架构完全解读
Java 虚拟机(JVM)是 Java 编程的核心,深入理解其架构对开发者意义重大。本文详细解读 JVM 架构,涵盖类加载器子系统、运行时数据区等核心组件,剖析类加载机制,包括加载阶段、双亲委派模型等内容。阐述内存管理原理,介绍垃圾回收算法与常见回收器,并结合案例讲解调优策略。还分享 JVM 性能瓶颈识别与调优方法,分析 Java 语言特性对性能的影响,给出数据结构选择、I/O 操作及并发同步处理的优化技巧,同时探讨 JVM 安全模型与错误处理机制,助力开发者提升编程能力与程序性能。
Java程序员必学:JVM架构完全解读
|
3月前
|
Java 数据库连接 API
互联网大厂校招 JAVA 工程师笔试题解析及常见考点分析
本文深入解析互联网大厂校招Java工程师笔试题,涵盖基础知识(数据类型、流程控制)、面向对象编程(类与对象、继承与多态)、数据结构与算法(数组、链表、排序算法)、异常处理、集合框架、Java 8+新特性(Lambda表达式、Stream API)、多线程与并发、IO与NIO、数据库操作(JDBC、ORM框架MyBatis)及Spring框架基础(IoC、DI、AOP)。通过技术方案讲解与实例演示,助你掌握核心考点,提升解题能力。
152 2
|
4月前
|
人工智能 Java
Java参数传递分析
本文详细探讨了Java中参数传递的机制,明确指出Java采用的是值传递而非引用传递。通过基本数据类型(如int)和引用类型(如Map、自定义对象People)的实例测试,证明方法内部对参数的修改不会影响原始变量。即使在涉及赋值返回的操作中,表面上看似引用传递,实际仍是值传递的结果。文中结合代码示例与执行结果,深入解析了值传递的本质及容易引起混淆的情形,帮助读者准确理解Java参数传递的核心概念。
|
传感器 分布式计算 安全
Java 大视界 -- Java 大数据在智能安防入侵检测系统中的多源数据融合与分析技术(171)
本文围绕 Java 大数据在智能安防入侵检测系统中的应用展开,剖析系统现状与挑战,阐释多源数据融合及分析技术,结合案例与代码给出实操方案,提升入侵检测效能。
|
4月前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
83 0