JVM 调优篇8 实战案例5- 逃逸分析

JVM逃逸分析及其作用案例

一 逃逸分析

1.1 概念

  • 逃逸分析的基本行为就是分析对象动态作用域:
  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
  • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中。逃逸分析(Escape Analysis)-优快云博客

1.2 作用

如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

1.3 案例

在JDK 6u23版本之后,HotSpot中默认就已经开启了逃逸分析。

public class EscapeAnalysis {

    public EscapeAnalysis obj;

    /*
    方法返回EscapeAnalysis对象,发生逃逸
     */
    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();
        
    }
}

二  逃逸分析作用案例

2.1 栈上分配

2.1.1 作用

1.栈上分配,将堆分配转化为栈分配。如果经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。可以减少垃圾回收时间和次数。

2.1.2  不使用逃逸分析

1.代码  

不开启逃逸分析参数:-Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

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");
        // 为了方便查看堆内存中对象个数,线程sleep
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }


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

    static class User {

    }
}

查看结果:

2.1.3  使用逃逸分析

开启逃逸分析参数:-Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

2.2 标量替换

标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。

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

标量替换参数设置:

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

注意:只用打开逃逸分析,-XX:+DoEscapeAnalysis ;-XX:+EliminateAllocations才起作用

案例:

public static void main(String[] args) {
    alloc();
  }  
  private static void alloc() {
    Point point = new Point(1,2);
    System.out.println("point.x="+point.x+"; point.y="+point.y);
  }
  class Point{
    private int x;
    private int y;
       public void  point(int k,int m){
                  this.x=k;
                   this.y=m;
            }
  }

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

  以上代码,经过标量替换后,就会变成:

private static void alloc() {
    int x = 1;
    int y = 2;
    System.out.println("point.x="+x+"; point.y="+y);
  }

Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个标量了。

好处:就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。

标量替换为栈上分配提供了很好的基础。

2.3 锁消除

即时编译器(Just-in-time Compilation,JIT)可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果同步块所使用的锁对象通过这种分析被证实只能够被一个线程访问,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这个取消同步的过程就叫同步省略,也叫锁消除。

public void f() {
  Object hollis = new Object();
  synchronized(hollis) {
    System.out.println(hollis);
  }
}

 代码中对hollis这个对象进行加锁,但是hollis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉。优化成:

public void f() {
  Object hollis = new Object();
  System.out.println(hollis);
}

在使用synchronized的时候,如果JIT经过逃逸分析之后发现并无线程安全问题的话,就会做锁消除。

  -XX:+EliminateLocks开启锁消除(jdk1.8默认开启,其它版本未测试) 
  -XX:-EliminateLocks 关闭锁消除 
  锁消除基于分析逃逸基础之上,开启锁消除必须开启逃逸分析
基础-0-Java虚拟机导学课程 11:33 基础-1-初识JVM 22:27 基础-2-Java虚拟机的组成 04:47 基础-3-字节码文件的组成-以正确的姿势打开字节码文件 10:41 基础(补)-3.5-字节码文件的组成-基础信息 15:54 基础-4-字节码文件的组成-常量池和方法 25:51 基础-5-字节码文件常见工具的使用1 11:43 基础-6-字节码文件常见工具的使用2 22:20 基础-7-类的生命周期加载阶段 22:09 基础-8-类的生命周期2连接阶段 19:58 基础-9-类的生命周期3初始化阶段 26:27 基础-10-类加载器的分类 13:56 基础-11-启动类加载器 13:36 基础-12-扩展和应用程序类加载器 16:26 基础-13-双亲委派机制 18:43 基础-14-打破类的双亲委派机制-自定义类加载器 25:16 基础-15-打破双亲委派机制2-线程上下文类加载器 20:17 基础-16-打破双亲委派机制3-osgi和类的热部署 11:53 基础-17-JDK9之后的类加载器 09:05 基础-18-运行时数据区-程序计数器 15:42 基础-19--局部变量表 19:20 基础-20--操作数栈和帧数据 12:08 基础-21--内存溢出 15:28 基础-22-堆内存 25:56 基础-23-方法区的实现 16:25 基础-24-方法区-字符串常量池 20:40 基础-25-直接内存 12:39 基础-26-自动垃圾回收 11:32 基础-27-方法区的回收 11:32 基础-28-引用计数法 15:41 基础-29-可达性分析法 20:25 基础-30-软引用 24:40 基础-31-弱虚终结器引用 12:08 基础-32-垃圾回收算法的评价标准 13:31 基础-33-垃圾回收算法1 10:05 基础-34-垃圾回收算法-分代GC 20:19 基础-35-垃圾回收器1 15:54 基础-36-垃圾回收器2 11:44 基础-37-垃圾回收器3 15:51 基础-38-g1垃圾回收器 26:23 实战-1-内存泄漏和内存溢出 21:25 实战-2-解决内存泄漏-监控-top命令 12:16 实战-3-解决内存泄漏-监控-visualvm 12:50 实战-4-解决内存泄漏-监控-arthas tunnel 15:18 实战-5-解决内存泄漏-监控-prometheus-grafana 17:53 实战-6-解决内存泄漏-堆内存状况对比 08:39 实战-7-解决内存泄漏-内存泄漏产生的几大原因 16:01 实战-8-内存泄漏产生的原因2 13:30 实战-9-内存泄漏产生的原因3 10:43 实战-10-内存泄漏产生的原因4 10:04 实战-11-内存泄漏产生原因2-并发请求问题 17:30 实战-12-导出堆内存快照并使用MAT分析 08:38 实战-13-MAT内存泄漏检测原理 17:23 实战-14-服务器导出内存快照和MAT使用小技巧 13:31 实战-15-实战1-查询大数据量导致的内存溢出 26:24 实战-16-实战2-mybatis导致的内存溢出 10:34 实战-17-实战3-k8s容器环境导出大文件内存溢出 26:13 实战-18-系统不处理业务时也占用大量的内存 14:13 实战-19-文章审核接口的内存问题 18:28 实战-20-btrace和arthas在线定位问题 20:15 实战-21-GC的核心目标 11:23 实战-22-GC的常用工具 12:05 实战-23-GC的常见工具2 14:25 实战-24-常见的GC模式 13:38 实战-25-基础JVM参数的设置 28:31 实战-26-垃圾回收器的选择 18:04 实战-27-垃圾回收参数 07:56 实战-28-实战-GC和内存 30:43 实战-29-性能问题的现象和解决思路 10:49 实战-30-定位进程CPU占用率高的问题 18:52 实战-31-接口响应时间很长问题的定位 14:44 实战-32-火焰图定位接口响应时间长的问题 12:03 实战-33-死锁问题的检测 14:37 实战-34-基准测试框架JMH的使用 28:24 实战-35-实战-性能 26:36 高级-01-GraalVM介绍 12:13 高级-02-GraalVM的两种运行模式 15:43 高级-03-使用SpringBoot3构建GraalVM应用 15:08 高级-04-将GraalVM应用部署到函数计算 25:13 高级-05-将GraalVM应用部署到Serverless 09:14 高级-06-参数化和故障诊断 22:31 高级-07-垃圾回收器的技术演进 13:09 高级-08-ShenandoahGC 22:50 高级-09-ZGC 14:35 高级-10-实战案例-内存不足时的垃圾回收测试 09:47 高级-11-JavaAgent技术 12:16 高级-12-JavaAgent环境搭建 15:24 高级-13-查看内存的使用情况 18:48 高级-14-生成内存快照 13:47 高级-15-获取类加载器的信息 16:26 高级-16-打印类的源码 18:00 高级-17-使用ASM增强方法 29:45 高级-18-使用ByteBuddy打印方法执行的参数和耗时 21:55 高级-19-APM系统和数据采集 24:30 原理-01-栈上的数据存储 15:05 原理-02-boolean在栈上的存储方式 22:48 原理-03-对象在堆上的存储1 17:27 原理-04-对象在堆上的存储2 25:14 原理-05-方法用的原理1-静态绑定 19:26 原理-06-方法用的原理2-动态绑定 15:25 原理-07-异常捕获的原理 12:00 原理-08-JIT即时编译器 14:49 原理-09-JIT即时编译器化手段1-方法内联 16:49 原理-10-JIT即时编译器化手段2-逃逸分析 09:03 原理-11-g1垃圾回收器原理-年轻代回收 27:57 原理-12-g1垃圾回收器原理-混合回收 17:24 原理-13-ZGC原理 26:27 原理-14-ShenandoahGC原理 09:39 面试-01-什么是JVM 16:38 面试-02-字节码文件的组成 15:02 面试-03-什么是运行时数据区 20:09 面试-04-哪些区域会出现内存溢出 11:56 面试-05-JDK6-8内存区域上的不同 14:36 面试-06-类的生命周期 17:17 面试-07-什么是类加载器 17:05 面试-08-什么是双亲委派机制 12:15 面试-09-如何打破双亲委派机制 18:10 面试-10-tomcat的自定义类加载器 31:18 面试-11-如何判断堆上的对象有没有被引用 10:05 面试-12-JVM中都有哪些引用类型 16:58 面试-13-theadlocal中为什么要使用弱引用 12:16 面试-14-有哪些垃圾回收算法 24:54 面试-15-有哪些常用的垃圾回收器 18:55 面试-16-如何解决内存泄漏问题 23:52 面试-17-常见的JVM参数 11:11 这是目前我学习的视频集合,要不要全看,或者少了什么,有哪些重要内容需要进行学习汇总或刷题或通过小实例验证
最新发布
10-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值