“噔噔噔……”传来一阵敲门声,把我从美梦中惊醒了。
朦胧间听到有人在说话”阿Q,在家不?”
“来了来了”,推门一看,原来是”赵信”兄弟。
「 赵信」:自称常山赵子龙,一把三爪长枪耍的虎虎生风,见人上去就是一枪,人送外号”菊花信”。
TLAB
- 尽管不是所有的对象实例都能够在
TLAB
中成功分配内存(因为它的空间比较小),但JVM
明确是将TLAB
作为内存分配的首选; - 一旦对象在
TLAB
空间分配内存失败时,JVM
就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden
空间中分配内存。
「 参数设置」
-XX:UseTLAB
:设置是否开启TLAB
空间;-XX:TLABWasteTargetPercent
:设置TLAB
空间所占Eden
空间的百分比大小,默认仅占1%
;
堆是分配对象的唯一选择吗?
- 如果经过逃逸分析(
Escape Analysis
)后发现,一个对象并没有逃逸出方法,那么就可能被优化为栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。 - 基于
OpenJDK
深度定制的TaoBaoVM
,它创新的GCIH(GCinvisible heap)
实现了堆外分配。将生命周期较长的Java
对象从堆中移至堆外,并且GC
不能管理GCIH
内部的Java
对象,以此达到降低GC的回收频率和提升GC
的回收效率的目的。
「 举例一」
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method</span><span class="hljs-params">()</span></span>{<br>    User user = <span class="hljs-keyword">new</span> User();<br>    ...<br>    user = <span class="hljs-keyword">null</span>;<br>}
user
对象在方法内部声明,且在内部置为 null
,未被方法外的方法所引用,我们就说 user
对象没有发生逃逸。
它 「 可以」分配到栈上,并随着方法的结束,栈空间也随之移除。
「 举例二」
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> StringBuffer <span class="hljs-title">createStringBuffer</span><span class="hljs-params">(String s1,String s2)</span></span>{<br>    StringBuffer sb = <span class="hljs-keyword">new</span> StringBuffer();<br>    sb.append(s1);<br>    sb.append(s2);<br>    <span class="hljs-keyword">return</span> sb;<br>}
虽然 sb
对象在方法内部被定义,但是它又作为方法的返回对象,可被其它方法调用,我们就说 sb
对象发生了逃逸。
要想不发生逃逸,可以改造为:
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">createStringBuffer</span><span class="hljs-params">(String s1,String s2)</span></span>{<br>    StringBuffer sb = <span class="hljs-keyword">new</span> StringBuffer();<br>    sb.append(s1);<br>    sb.append(s2);<br>    <span class="hljs-keyword">return</span> sb.toString();<br>}
在 JDK 6u23
版本之后, HotSpot
中默认开启了逃逸分析。
-XX:DoEscapeAnalysis
:显式开启逃逸分析-XX:+PrintEscapeAnalysis
:查看逃逸分析的筛选结果
栈上分配
<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StackAllocation</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        <span class="hljs-keyword">long</span> start = System.currentTimeMillis();<br><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10000000; i++) {<br>            alloc();<br>        }<br>       <br>        <span class="hljs-keyword">long</span> end = System.currentTimeMillis();<br>        System.out.println(<span class="hljs-string">"花费的时间为: "</span> + (end - start) + <span class="hljs-string">" ms"</span>);<br>        <br>        <span class="hljs-keyword">try</span> {<br>            Thread.sleep(<span class="hljs-number">1000000</span>);<br>        } <span class="hljs-keyword">catch</span> (InterruptedException e1) {<br>            e1.printStackTrace();<br>        }<br>    }<br><br>    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">alloc</span><span class="hljs-params">()</span> </span>{<br>        <br>        User user = <span class="hljs-keyword">new</span> User();<br>    }<br><br>    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{<br><br>    }<br>}<br><br></ <span>
逃逸分析默认开启,也可以手动开启: -XX:+DoEscapeAnalysis
关闭逃逸分析
同步省略
我们都知道线程同步的代价是相当高的,同步的后果就是降低了并发性和性能。
JVM
为了提高性能,在动态编译同步块的时候, JIT
编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问。
如果符合条件,那么 JIT
编译器在编译这个同步块的时候就会取消对这部分代码的同步。这个取消同步的过程就叫同步省略,也叫锁消除。
「 举例」
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SynchronizedTest</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method</span><span class="hljs-params">()</span> </span>{<br>        Object code = <span class="hljs-keyword">new</span> Object();<br>        <span class="hljs-keyword">synchronized</span>(code) {<br>            System.out.println(code);<br>        }<br>    }<br>    <br>    <br>    <br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">method2</span><span class="hljs-params">()</span> </span>{<br>        Object code = <span class="hljs-keyword">new</span> Object();<br>        System.out.println(code);<br>    }<br>}<br>
❝在解释执行时这里仍然会有锁,但是经过服务端编译器的即时编译之后,这段代码就会忽略所有的同步措施而直接执行。
标量替换
- 标量:不可被进一步分解的量,如
JAVA
的基本数据类型就是标量; - 聚合量:可以被进一步分解的量, 在
JAVA
中对象就是可以被进一步分解的聚合量。
聚合量可以分解成其它标量和聚合量。
标量替换,又名分离对象,即在 JIT
阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过 JIT
优化,就会把这个对象拆解成若干个其中包含的成员变量来替代。
「 举例」
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ScalarTest</span> </span>{<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{<br>        alloc();   <br>    }<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">alloc</span><span class="hljs-params">()</span></span>{<br>        Point point = <span class="hljs-keyword">new</span> Point(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>);<br>    }<br>}<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Point</span></span>{<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> x;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> y;<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Point</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x,<span class="hljs-keyword">int</span> y)</span></span>{<br>        <span class="hljs-keyword">this</span>.x = x;<br>        <span class="hljs-keyword">this</span>.y = y;<br>    }<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">alloc</span><span class="hljs-params">()</span></span>{<br>    <span class="hljs-keyword">int</span> x = <span class="hljs-number">1</span>;<br>    <span class="hljs-keyword">int</span> y = <span class="hljs-number">2</span>;<br>}<br>
❝标量替换默认开启,你也可以通过参数手动设置
-XX:+EliminateAllocations
,开启之后允许将对象打散分配到栈上,GC
减少,执行速度提升。
❞
常见的发生逃逸的场景
「 举例」
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EscapeAnalysis</span> </span>{<br><br>    <span class="hljs-keyword">public</span> EscapeAnalysis obj;<br>    <br>     <br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setObj</span><span class="hljs-params">()</span></span>{<br>        <span class="hljs-keyword">this</span>.obj = <span class="hljs-keyword">new</span> EscapeAnalysis();<br>    }<br>    <br><br>    <br>    <span class="hljs-function"><span class="hljs-keyword">public</span> EscapeAnalysis <span class="hljs-title">getInstance</span><span class="hljs-params">()</span></span>{<br>        <span class="hljs-keyword">return</span> obj == <span class="hljs-keyword">null</span>? <span class="hljs-keyword">new</span> EscapeAnalysis() : obj;<br>    }<br>   <br>   <br>    <br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">useEscapeAnalysis1</span><span class="hljs-params">()</span></span>{<br>        EscapeAnalysis e = getInstance();<br>        <br>    }<br>    <br>     <br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">useEscapeAnalysis</span><span class="hljs-params">()</span></span>{<br>        EscapeAnalysis e = <span class="hljs-keyword">new</span> EscapeAnalysis();<br>    }<br>}<br>
逃逸分析并不成熟
1999
年就已经发表了关于逃逸分析的论文,但 JDK1.6
中才有实现,而且这项技术到如今也不是十分成熟。
其根本原因就是无法保证逃逸分析的性能提升一定能高于它的消耗,因为逃逸分析自身也需要进行一系列复杂的分析,是需要耗时的。
一个极端的例子,就是经过逃逸分析之后,发现所有对象都逃逸了,那这个逃逸分析的过程就白白浪费掉了。
❝细心的小伙伴也应该能发现,我们在抽样器中的截图其实就是在堆中分配的对象。
❞
以上就是今天的所有内容了,如果你有不同的意见或者更好的 idea
,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!
后台留言领取 java
干货资料:学习笔记与大厂面试题
Original: https://www.cnblogs.com/aqsaycode/p/14920526.html
Author: 阿Q说代码
Title: Battle:你会TLAB,我会逃逸分析
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/683657/
转载文章受原作者版权保护。转载请注明原作者出处!