摄影师个人网站制作,游乐场网站开发,哪一个军事网站做的比较好,wordpress博首先#xff0c;为解释这个问题#xff0c;需要的基本知识如下#xff08;如果对以下概念不太熟悉#xff0c; 可以先Google下#xff09;#xff1a;
1.JVM内存结构#xff0c;传送门 2.即时编译#xff08;JIT#xff09;#xff0c;传送门
逃逸分析
在编译期间…首先为解释这个问题需要的基本知识如下如果对以下概念不太熟悉 可以先Google下
1.JVM内存结构传送门 2.即时编译JIT传送门
逃逸分析
在编译期间JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力其中一种重要的技术叫做逃逸分析。
逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中 同步负载 和 内存堆分配压力 的 跨函数全局数据流分析算法。通过逃逸分析Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
逃逸分析的基本行为就是分析对象动态作用域当一个对象在方法中被定义后它可能被外部方法所引用例如作为调用参数传递到其他地方中称为方法逃逸。
例如
public static StringBuffer newStringBuffer(String s1, String s2) {StringBuffer sb new StringBuffer();sb.append(s1);sb.append(s2);return sb;
}StringBuffer sb是一个方法内部变量上述代码中直接将sb返回这样这个StringBuffer有可能被其他方法所改变这样它的作用域就不只是在方法内部虽然它是一个局部变量称其逃逸到了方法外部。甚至还有可能被外部线程访问到譬如赋值给类变量或可以在其他线程中访问的实例变量称为线程逃逸。
上述代码如果想要StringBuffer sb不逃出方法可以这样写
public static String newStringBuffer(String s1, String s2) {StringBuffer sb new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString();
}不直接返回 StringBuffer那么StringBuffer将不会逃逸出方法。
使用逃逸分析编译器可以对代码做如下优化
一、同步省略如果一个对象被发现只能从一个线程被访问到那么对于这个对象的操作可以不考虑同步。
二、将堆分配转化为栈分配如果一个对象在子程序中被分配如果指向该对象的指针永远不会逃逸对象可能是栈分配的候选而不是堆分配。
三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到那么对象的部分或全部可以不存储在内存而是存储在CPU寄存器中。
本文主要来介绍逃逸分析的第二个用途将堆分配转化为栈分配
其实以上三种优化中栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点这里就不展开介绍了。如果大家感兴趣我后面专门出一篇文章全面介绍下逃逸分析。
在Java代码运行时通过JVM参数可指定是否开启逃逸分析
-XX:DoEscapeAnalysis 表示开启逃逸分析
-XX:-DoEscapeAnalysis 表示关闭逃逸分析
从jdk 1.7开始已经默认开始逃逸分析如需关闭需要指定-XX:-DoEscapeAnalysis
对象的栈上内存分配
我们知道在一般情况下对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果来决定是否可以将对象的内存分配从堆转化为栈。
我们来看以下代码
public static void main(String[] args) {long a1 System.currentTimeMillis();for (int i 0; i 1000000; i) {alloc();}// 查看执行时间long a2 System.currentTimeMillis();System.out.println(cost (a2 - a1) ms);// 为了方便查看堆内存中对象个数线程sleeptry {Thread.sleep(100000);} catch (InterruptedException e1) {e1.printStackTrace();}
}private static void alloc() {User user new User();
}static class User {}其实代码内容很简单就是使用for循环在代码中创建100万个User对象。
我们在alloc方法中定义了User对象但是并没有在方法外部引用他。也就是说这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后就可以对其内存分配进行优化。
我们指定以下JVM参数并运行
-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:PrintGCDetails -XX:HeapDumpOnOutOfMemoryError在程序打印出 cost XX ms 后代码运行结束之前我们使用[jmap][1]命令来查看下当前堆内存中有多少个User对象
➜ ~ jps
2809 StackAllocTest
2810 Jps
➜ ~ jmap -histo 2809num #instances #bytes class name
----------------------------------------------1: 524 87282184 [I2: 1000000 16000000 StackAllocTest$User3: 6806 2093136 [B4: 8006 1320872 [C5: 4188 100512 java.lang.String6: 581 66304 java.lang.Class从上面的jmap执行结果中我们可以看到堆中共创建了100万个StackAllocTest$User实例。
在关闭逃避分析的情况下-XX:-DoEscapeAnalysis虽然在alloc方法中创建的User对象并没有逃逸到方法外部但是还是被分配在堆内存中。也就说如果没有JIT编译器优化没有逃逸分析技术正常情况下就应该是这样的。即所有对象都分配到堆内存中。
接下来我们开启逃逸分析再来执行下以上代码。
-Xmx4G -Xms4G -XX:DoEscapeAnalysis -XX:PrintGCDetails -XX:HeapDumpOnOutOfMemoryError在程序打印出 cost XX ms 后代码运行结束之前我们使用jmap命令来查看下当前堆内存中有多少个User对象
➜ ~ jps
709
2858 Launcher
2859 StackAllocTest
2860 Jps
➜ ~ jmap -histo 2859num #instances #bytes class name
----------------------------------------------1: 524 101944280 [I2: 6806 2093136 [B3: 83619 1337904 StackAllocTest$User4: 8006 1320872 [C5: 4188 100512 java.lang.String6: 581 66304 java.lang.Class从以上打印结果中可以发现开启了逃逸分析之后-XX:DoEscapeAnalysis在堆内存中只有8万多个StackAllocTest$User对象。也就是说在经过JIT优化之后堆内存中分配的对象数量从100万降到了8万。
除了以上通过jmap验证对象个数的方法以外读者还可以尝试将堆内存调小然后执行以上代码根据GC的次数来分析也能发现开启了逃逸分析之后在运行期间GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配所以GC次数有了明显的减少。
总结
所以如果以后再有人问你是不是所有的对象和数组都会在堆内存分配空间
那么你可以告诉他不一定随着JIT编译器的发展在编译期间如果JIT经过逃逸分析发现有些对象没有逃逸出方法那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样在开启逃逸分析之后也并不是所有User对象都没有在堆上分配。
转自Hollis