JVM ——之内存增强分析

       

目录

       

JVM 逃逸分析

何为逃逸分析

通过代码演示

        逃逸分析参数设置


JVM 逃逸分析

        JIT——是即时(Just-In-Time)编译器是Java运行时环境的一个组件,它可提高运行时Java应用程序的性能。JVM中没有什么比编译器更能影响性能,而选择编译器是运行Java应用程序时做出的首要决定之一,无论您是Java开发人员还是最终用户。
        随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配,标量替换优化技术会导致一些微妙的变化,之前的一些固定的技术理论,现在显得也不是那么绝对了。     

         在 Java 虚拟机中,对象是在 Java 堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。
        逃逸分析技术到现在还不是很成熟,虽然经过逃逸分析可以做标量替换、栈上分配、锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

何为逃逸分析

        逃逸分析就是一种算法,基于这个算法我们可以有效的减少Java对象在堆中的分配,也就是不在堆中创建过多的对象,而是把这些未逃逸的对象放在了栈内存中,但是这些对象也要满足一些必然的条件。

当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。
        以上就是对逃逸现象的基本解释。
 

通过代码演示
 

        假设我们现在需要拼接字符串并返回一个拼接好的字符串

public StringBuffer append(String s1, String s2) {
 StringBuffer sb = new StringBuffer();
     sb.append(s1);
    sb.append(s2);
     return sb;
}

        这段代码中,由于该方法会返回一个StringBuffer对象,所以外部可以调用这个对象,那么它将不会被分配在栈内存上,这就叫做发生了逃逸。我们可以将其修改为以下这个样子:
 

public String append(String s1, String s2) {
 StringBuffer sb = new StringBuffer();
 sb.append(s1);
 sb.append(s2);
 return sb.toString();
}

        我们可以看到这段代码和上面不同的是,StringBuffer这个对象,并没有外部的引用或指向,只是在方法内部构建完成后,返回的是String类型的对象,所以StringBuffer就是未发生逃逸的对象,它就会被分配在栈内存,而不是堆内存中,当然这只是举个例子,并不是实际中的应用。
        我们需要掌握的是这种思想,当我们编写代码,或者优化代码时,要尽可能的考虑到逃逸问题,因为分配在栈内存中的对象,方法结束后会自动销毁,无需调用GC,可以极大的提高系统的性能。 

        逃逸分析参数设置

         在JDK1.7 版本之后,HotSpot 中默认就已经开启了逃逸分析,如果使用的是较早的版本,开发人员则可以通过:
        选项“-XX:+DoEscapeAnalysis"显式开启逃逸分析。
        通过选项“-XX:+PrintEscapeAnalysis"查看逃逸分析的筛选结果。设置方式为:

 

 

 

前面两个字段是设置最大堆内存和小堆内存 。我们可以跑一段发生逃逸的代码段,其中sb对象被外部调用,发生逃逸,我们看下它在堆内存上的空间。

public class Test {
    public static StringBuffer append(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb;
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
             append("hello", "world");
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
    }
}

 我们可以看到伊甸园区占用了24%   幸存区的form space占了 15%。下面我们让StringBuffer这个对象不发生逃逸。

public class Test {
    public static String append(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
             append("hello", "world");
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为: " + (end - start) + " ms");
    }
}

 这次堆中的伊甸园只占用了10%,有明显的下降,证明一部分对象被存在了栈内存上

 所以我们建议:开发中能在方法内部应用对象的,就尽量控制在内部。

栈上分配:将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引
用不会发生逃逸,对象可能是栈上分配的候选。
同步锁省略:又称之为同步锁消除,如果一个对象被发现只有一个线程被访问到,那么对
于这个对象的操作可以不考虑同步。
分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问
到,那么对象的部分(或全部)可以不存储在内存,而是存储在 CPU 寄存器中。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值