当前位置: 当前位置:首页 > 知识 > JIT内存逃逸分析 正文

JIT内存逃逸分析

2024-05-06 01:14:52 来源:口口声声网 作者:时尚 点击:609次

JIT内存逃逸分析

文章目录

  • 什么是存逃JIT?
  • JIT内存逃逸分析
    • 1. 内存逃逸分类
    • 2. 逃逸分析的作用
    • 3. 同步锁消除
    • 4. 标量替换
    • 5. 栈上分配
      • 1. Java对象的分配流程
      • 2. 栈上分配思路
      • 3. 开启栈上分配
      • 4. 分析内存
    • 6. 查看JVM所有配置的参数值

什么是JIT?

JIT Compiler(Just-in-timeCompiler) 即时编译。
最早的逸分Java建置方案是由一套转译程式(interpreter),将每个Java指令都转译成对等的存逃微处理器指令,并根据转译后的逸分指令先后次序依序执行,由于一个Java指令可能被转译成十几或数十几个对等的存逃微处理器指令,这种模式执行的逸分速度相当缓慢。
详细解析见百度百科 JIT编译器

JIT内存逃逸分析

Escape Analysis 存逃内存逃逸分析是一种代码分析手段,通过动态分析创建对象的逸分使用范围。

1. 内存逃逸分类

如果一个方法创建的存逃对象,除了方法内使用到之外,逸分还被方法外使用到,存逃那么在方法执行结束后,逸分由于该对象依然被引用到,存逃那么GC就可能无法立即回收,逸分此时该对象就出现了逃逸。存逃
内存逃逸分为方法逃逸和线程逃逸

  • 方法逃逸
    当一个对象被定义后,以参数的形式传递给其它方法
  • 线程逃逸
    类变量或者示例变量或者方法返回的对象,可能被其它线程引用或者访问到
public class EscapeAnalysis {    public static Object obj1;    public Object obj2;    public void globalVariableEscape() {        obj1 = new Object();  // 静态变量,外部线程可见,会发生逃逸    }    public void instanceObjectEscape() {        obj2 = new Object();  // 赋值给堆中实例字段,外部线程可见,会发生逃逸    }    public Object returnObjectEscape() {        return new Object();   // 返回实例,外部线程可见,会发生逃逸    }    public void noEscape() {        Object noEscape = new Object();   // 仅创建线程可见,对象无逃逸    }}

2. 逃逸分析的作用

筛选出没有发生逃逸的对象,为其优化手段,例如栈上分配,标量替换,同步消除等提供依据。
只有server模式下才能启用逃逸分析
在这里插入图片描述

JVM相关参数

  • 开启逃逸分析:-XX:+DoEscapeAnalysis
  • 关闭逃逸分析:-XX:-DoEscapeAnalysis
  • 显示逃逸分析:-XX:+PrintEscapeAnalysis

3. 同步锁消除

同步锁时非常消耗性能的,所以编译器确定一个对象没有发生逃逸时,它会移除该对象的同步锁。
JDK1.8 默认开启了同步锁,但是建立在开启逃逸分析的基础上。

-XX:+EliminateLocks #开启同步锁消除(JVM默认状态)-XX:-EliminateLocks #关闭同步锁消除

我们用一个例子来演示

@Test    public void testLock(){        long t1 = System.currentTimeMillis();        for (int i = 0; i < 100_000_000; i++) {            locketMethod();        }        long t2 = System.currentTimeMillis();        System.out.println("耗时:"+(t2-t1));    }    public static void locketMethod(){        EscapeAnalysis escapeAnalysis = new EscapeAnalysis();        synchronized(escapeAnalysis) {            escapeAnalysis.obj2="abcdefg";        }    }

上面这个EscapeAnalysis没有发生逃逸,JVM默认开启了同步锁消除。我们做几组试验对比
(1) 手动注释掉synchronized锁,直接运行,未设置JVM参数
在这里插入图片描述
(2) 保留synchronized锁,直接运行,未设置JVM参数
在这里插入图片描述
(3) 设置JVM参数,关闭锁消除,再次运行

java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateLocks

在这里插入图片描述
在这里插入图片描述
(4) 设置JVM参数,开启锁消除,再次运行

java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateLocks

在这里插入图片描述
通过上述4组参照试验对比,可以得出,JDK1.8默认开启了锁消除,如果关闭锁消除,那么锁将非常消耗性能。

4. 标量替换

标量:不能被进一步分解的变量,如基础数据类型和对象的引用。
聚合量:能够被分解的变量,比如对象。

如果一个对象没有发生逃逸,可以将成员变量拆分成标量,就可以不用创建它,在栈或者寄存器中直接创建成员标量,这就叫标量替换。节省内存,提升了应用程序的性能。
JDK1.8默认开启了标量替换,也同样建立在逃逸分析的基础上

JVM相关参数

  • 开启标量替换:-XX:+EliminateAllocations #JVM默认状态
  • 关闭标量替换:-XX:-EliminateAllocations
  • 显示逃逸分析:-XX:+PrintEliminateAllocations
@Test    public void testScalarReplace(){        long t1 = System.currentTimeMillis();        for (int i = 0; i < 100_000_000; i++) {            scalarReplace();        }        long t2 = System.currentTimeMillis();        System.out.println("耗时:"+(t2-t1));    }    public static void scalarReplace(){        User user=new User();        user.setId(1);        user.setName("hello");    }

上面这个User没有发生逃逸,JVM默认开启了标量替换。我们做几组试验对比。
(1) 直接运行,未设置JVM参数
在这里插入图片描述
(2) 设置JVM参数,关闭标量替换

java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateAllocations

在这里插入图片描述

(3) 设置JVM参数,开启标量替换

java -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateAllocations

在这里插入图片描述

通过上述3组参照试验对比,可以得出,JDK1.8默认开启了标量替换,如果关闭标量替换,那么直接进行对象分配将非常影响性能。

5. 栈上分配

将原本应当分配在堆上的对象分配到栈内存上,这样可以减少堆内存的使用,从而减少GC的频率。

1. Java对象的分配流程

首先,我们需要先了解决Java对象是如何分配内存的。
在这里插入图片描述

  • 如果开启栈上分配,JVM会先进行栈上分配
  • 如果没有开启栈上分配或不符合条件,则会进入TLAB分配
  • 如果TLAB分配不成功或者不符合,则判断是否可以进入年老代分配
  • 如果不能进入年老代,则进入eden分配
    并不是所有对象都分配在堆上,除了堆意外,还可以将对象分配到栈和TLAB中。(大多数的对象都分配在堆中)

2. 栈上分配思路

栈上分配是JVM提供的一项优化技术。
其思路是:

  • 对于线程私有的对象(不能被其它线程访问的对象),可以将它们分配到栈内存上,而不是堆内存中,也就是将聚变量进行标量替换的方案。
  • 分配到栈上的优势是可以在方法结束后自动销毁,不需要GC介入,提供系统性能
  • 对于大量零散的对象,栈上分配提供了一种很好的对象分配策略,栈上分配速度块,可以有效避免GC回收带来的负面影响。
  • 问题:由于栈内存比较小,因而大对象不能也不适合进行栈上分配。

3. 开启栈上分配

栈上分配是基于逃逸分析和标量替换的,所以必须开启逃逸分析和标量替换,当然JDK1.8是默认都是开启的。
逃逸分析和标量替换前面已经介绍了。
在这里插入图片描述

4. 分析内存

栈上分配时基于逃逸分析+标量替换,因此我们只要关闭逃逸分析或者标量替换任一一项,即可关闭栈上分配。
我们继续使用前面标量替换的demo。

  • 关闭栈上分配,运行测试用例
java -Xmx15m -Xms15m -XX:+PrintFlagsFinal -XX:+PrintGCDetails -XX:-UseTLAB -XX:-DoEscapeAnalysis #关闭栈上分配

在这里插入图片描述

  • 开启栈上分配,运行测试用例
java -Xmx15m -Xms15m -XX:+PrintFlagsFinal -XX:+PrintGCDetails -XX:-UseTLAB -XX:+DoEscapeAnalysis #开启栈上分配

在这里插入图片描述
通过对比,我们可以看到

  • 开启栈上分配,将未逃逸的对象分配在栈内存中,明显运行效率更高。
  • 关闭栈上分配后,GC频繁进行垃圾回收。

6. 查看JVM所有配置的参数值

java -XX:+PrintFlagsFinal  #输出打印所有参数jvm参数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

作者:休闲
------分隔线----------------------------
头条新闻
图片新闻
新闻排行榜