jvm之逃逸分析

逃逸分析是指分析一个变量的作用域,看这个变量会不会逃逸到方法外,如果不会的话,则可以对这个变量进行一些优化。《深入理解java虚拟机》中是这样说的:
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,他可能被外部方法所引用,例如作为调用参数传递到其他的方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如复制非类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化。

这里的优化有:栈上分配、同步消除、标量替换。

由于我看的是第二版的书,当时还是jdk1.7,书上说1.7上的hotspot没有实现栈上分配,我好奇现在常用的1.8有没有实现,所以做了个小实验:

    public static void main(String[] args) {
        Main m =new Main();
        long startTime=System.currentTimeMillis();
        if(true){
            while (true) {
                m.fun();
            }
        }
        long endTime=System.currentTimeMillis();
        System.out.println(endTime-startTime);
    }
    void fun(){
        Object o = new Object();
    }

上边的代码就是不断循环fun函数,由于fun函数中的变量 o 没有逃逸出方法,如果jdk1.8实现了栈上分配的话,堆空间的内存占用就不会一直增加,自然也不需要gc。实际结果如下图:
在这里插入图片描述

可以看出,程序内存占用是在不断上升的,而且也触发了GC,说明jdk1.8也没有实现栈上分配。

那么,标量替换能给效率带来多大的提升呢?

    public static void main(String[] args) {
        Main m =new Main();
        long startTime=System.currentTimeMillis();
        for(int i=0;i<1000000;i++) {
            m.fun();
        }
        long endTime=System.currentTimeMillis();
        System.out.println(endTime-startTime);
    }
    void fun(){
        Person p = new Person();
    }
    class Person{
        int a;
        int b;
        int c;
        int d;
    }

这段代码反复执行fun函数100万次,我们首先开启逃逸分析和标量替换,结果如下:

5

一共运行了5毫秒。
然后关闭标量替换,结果如下:

[GC (Allocation Failure) [PSYoungGen: 8192K->840K(9216K)] 8192K->848K(19456K), 0.0009078 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 9032K->840K(9216K)] 9040K->848K(19456K), 0.0006650 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 9032K->824K(9216K)] 9040K->832K(19456K), 0.0006836 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 9016K->760K(9216K)] 9024K->768K(19456K), 0.0005237 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
10

程序执行了4次minorGC,这也导致执行时间延长了一倍。

总结:jdk1.8目前也没有实现栈上分配对象;标量替换能节省大量的内存空间。这给我们提了个醒,我们在写代码时,如果不是必要,尽量让变量不逃逸出方法。

### JVM 逃逸分析概述 JVM中的逃逸分析是一种编译期优化技术,用于判断对象是否会逃逸出当前线程或方法的作用域。通过这种分析,JDK能够做出一些优化决策来提高性能,比如栈上分配、同步消除等[^4]。 ### 逃逸分析引发内存泄漏的原因 当对象未正确处理其生命周期时,在某些情况下即使启用了逃逸分析也可能间接造成内存泄漏: - **错误的对象共享**:如果程序逻辑设计不当,使得原本应该局部化的对象意外地被其他部分持有引用,则这些对象无法及时回收,从而形成潜在的内存泄露风险。 - **误判为不逃逸的情况**:对于复杂的数据结构操作,尤其是涉及多线程环境下的并发访问模式下,可能存在难以精确判定是否发生逃逸的情形。此时可能会导致不必要的堆内存在长时间得不到释放而累积成内存泄漏现象。 ### 解决方案 针对上述由逃逸分析可能带来的问题,可以从以下几个方面着手解决: #### 合理调整GC策略与参数配置 适当调节垃圾收集器的选择及其相关参数可以帮助更好地管理应用程序内的资源消耗情况。例如启用G1 GC并合理设定初始和最大堆大小(-Xms,-Xmx),这有助于维持稳定的运行状态,防止由于频繁Full GC造成的性能瓶颈以及由此产生的隐含内存泄漏隐患[^1]。 ```bash java -XX:+UseG1GC -Xms512m -Xmx4g MyApplication ``` #### 审查代码逻辑确保无异常引用保留 仔细审查源码中涉及到对象创建及传递的部分,特别是那些跨作用范围使用的实例变量或者静态成员字段。确认它们不会无意间延长目标对象的生命周朗,进而阻碍正常的垃圾回收过程。 #### 利用工具辅助诊断排查 借助专业的性能剖析工具如VisualVM, MAT(Memory Analyzer Tool)等对正在执行的应用进程实施监控跟踪,定位具体引起内存占用过高的区域,并据此采取针对性措施加以改进。 #### 正确理解并运用逃逸分析特性 虽然现代版本Java已经默认开启此功能,但在特定场景下调优仍有必要深入了解该机制的工作原理。必要时可通过显式指定`-XX:-DoEscapeAnalysis`关闭它来进行对比测试,观察实际效果变化后再做决定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值