learn.lianglianglee.com/专栏/深入浅出 Java 虚拟机-完/21 动手实践:不为人熟知的字节码指令.md.html
2022-05-11 19:04:14 +08:00

1161 lines
30 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>21 动手实践:不为人熟知的字节码指令.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/00 开篇词JVM一块难啃的骨头.md.html">00 开篇词JVM一块难啃的骨头.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/01 一探究竟:为什么需要 JVM它处在什么位置.md.html">01 一探究竟:为什么需要 JVM它处在什么位置.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/02 大厂面试题:你不得不掌握的 JVM 内存管理.md.html">02 大厂面试题:你不得不掌握的 JVM 内存管理.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/03 大厂面试题:从覆盖 JDK 的类开始掌握类的加载机制.md.html">03 大厂面试题:从覆盖 JDK 的类开始掌握类的加载机制.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/04 动手实践:从栈帧看字节码是如何在 JVM 中进行流转的.md.html">04 动手实践:从栈帧看字节码是如何在 JVM 中进行流转的.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/05 大厂面试题:得心应手应对 OOM 的疑难杂症.md.html">05 大厂面试题:得心应手应对 OOM 的疑难杂症.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/06 深入剖析:垃圾回收你真的了解吗?(上).md.html">06 深入剖析:垃圾回收你真的了解吗?(上).md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/07 深入剖析:垃圾回收你真的了解吗?(下).md.html">07 深入剖析:垃圾回收你真的了解吗?(下).md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?.md.html">08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/09 案例实战:亿级流量高并发下如何进行估算和调优.md.html">09 案例实战:亿级流量高并发下如何进行估算和调优.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/10 第09讲案例实战面对突如其来的 GC 问题如何下手解决.md.html">10 第09讲案例实战面对突如其来的 GC 问题如何下手解决.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/11 第10讲动手实践自己模拟 JVM 内存溢出场景.md.html">11 第10讲动手实践自己模拟 JVM 内存溢出场景.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/12 第11讲动手实践遇到问题不要慌轻松搞定内存泄漏.md.html">12 第11讲动手实践遇到问题不要慌轻松搞定内存泄漏.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/13 工具进阶:如何利用 MAT 找到问题发生的根本原因.md.html">13 工具进阶:如何利用 MAT 找到问题发生的根本原因.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/14 动手实践:让面试官刮目相看的堆外内存排查.md.html">14 动手实践:让面试官刮目相看的堆外内存排查.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/15 预警与解决:深入浅出 GC 监控与调优.md.html">15 预警与解决:深入浅出 GC 监控与调优.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/16 案例分析:一个高死亡率的报表系统的优化之路.md.html">16 案例分析:一个高死亡率的报表系统的优化之路.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/17 案例分析:分库分表后,我的应用崩溃了.md.html">17 案例分析:分库分表后,我的应用崩溃了.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/18 动手实践:从字节码看方法调用的底层实现.md.html">18 动手实践:从字节码看方法调用的底层实现.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/19 大厂面试题:不要搞混 JMM 与 JVM.md.html">19 大厂面试题:不要搞混 JMM 与 JVM.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/20 动手实践:从字节码看并发编程的底层实现.md.html">20 动手实践:从字节码看并发编程的底层实现.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/深入浅出 Java 虚拟机-完/21 动手实践:不为人熟知的字节码指令.md.html">21 动手实践:不为人熟知的字节码指令.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html">22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/23 动手实践JIT 参数配置如何影响程序运行?.md.html">23 动手实践JIT 参数配置如何影响程序运行?.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/24 案例分析:大型项目如何进行性能瓶颈调优?.md.html">24 案例分析:大型项目如何进行性能瓶颈调优?.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/25 未来JVM 的历史与展望.md.html">25 未来JVM 的历史与展望.md.html</a>
</li>
<li>
<a href="/专栏/深入浅出 Java 虚拟机-完/26 福利:常见 JVM 面试题补充.md.html">26 福利:常见 JVM 面试题补充.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>21 动手实践:不为人熟知的字节码指令</h1>
<p>本课时我们主要分享一个实践案例:不为人熟知的字节码指令。</p>
<p>下面将通过介绍 Java 语言中的一些常见特性,来看一下字节码的应用,由于 Java 特性非常多这里我们仅介绍一些经常遇到的特性。javap 是手中的利器,复杂的概念都可以在这里现出原形,并且能让你对此产生深刻的印象。</p>
<p>本课时代码比较多,相关代码示例都可以在仓库中找到,建议实际操作一下。</p>
<h2>异常处理</h2>
<p>在上一课时中,细心的你可能注意到了,在 synchronized 生成的字节码中,其实包含两条 monitorexit 指令,是为了保证所有的异常条件,都能够退出。</p>
<p>这就涉及到了 Java 字节码的异常处理机制,如下图所示。</p>
<p><img src="assets/CgpOIF5x0xWABeHPAAAgs1YbFw4651.jpg" alt="img" /></p>
<p>如果你熟悉 Java 语言那么对上面的异常继承体系一定不会陌生其中Error 和 RuntimeException 是非检查型异常Unchecked Exception也就是不需要 catch 语句去捕获的异常;而其他异常,则需要程序员手动去处理。</p>
<h3>异常表</h3>
<p>在发生异常的时候Java 就可以通过 Java 执行栈,来构造异常栈。回想一下第 02 课时中的栈帧,获取这个异常栈只需要遍历一下它们就可以了。</p>
<p>但是这种操作比起常规操作要昂贵的多。Java 的 Log 日志框架,通常会把所有错误信息打印到日志中,在异常非常多的情况下,会显著影响性能。</p>
<p>我们还是看一下上一课时生成的字节码:</p>
<pre><code>void doLock();
descriptor: ()V
flags:
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #8 // String lock
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
</code></pre>
<p>可以看到,编译后的字节码,带有一个叫 Exception table 的异常表,里面的每一行数据,都是一个异常处理器:</p>
<ul>
<li><strong>from</strong> 指定字节码索引的开始位置</li>
<li><strong>to</strong> 指定字节码索引的结束位置</li>
<li><strong>target</strong> 异常处理的起始位置</li>
<li><strong>type</strong> 异常类型</li>
</ul>
<p>也就是说,只要在 from 和 to 之间发生了异常,就会跳转到 target 所指定的位置。</p>
<h3>finally</h3>
<p>通常我们在做一些文件读取的时候,都会在 finally 代码块中关闭流,以避免内存的溢出。关于这个场景,我们再分析一下下面这段代码的异常表。</p>
<pre><code>import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class A {
public void read() {
InputStream in = null;
try {
in = new FileInputStream(&quot;A.java&quot;);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
</code></pre>
<p>上面的代码,捕获了一个 FileNotFoundException 异常,然后在 finally 中捕获了 IOException 异常。当我们分析字节码的时候却发现了一个有意思的地方IOException 足足出现了三次。</p>
<pre><code>Exception table:
from to target type
17 21 24 Class java/io/IOException
2 12 32 Class java/io/FileNotFoundException
42 46 49 Class java/io/IOException
2 12 57 any
32 37 57 any
63 67 70 Class java/io/IOException
</code></pre>
<p>Java 编译器使用了一种比较<strong></strong>的方式来组织 finally 的字节码,它分别在 try、catch 的正常执行路径上,复制一份 finally 代码,追加在 正常执行逻辑的后面;同时,再复制一份到其他异常执行逻辑的出口处。</p>
<p>这也是下面这段方法不报错的原因,都可以在字节码中找到答案。</p>
<pre><code>//B.java
public int read() {
try {
int a = 1 / 0;
return a;
} finally {
return 1;
}
}
</code></pre>
<p>下面是上面程序的字节码,可以看到,异常之后,直接跳转到序号 8 了。</p>
<pre><code>stack=2, locals=4, args_size=1
0: iconst_1
1: iconst_0
2: idiv
3: istore_1
4: iload_1
5: istore_2
6: iconst_1
7: ireturn
8: astore_3
9: iconst_1
10: ireturn
Exception table:
from to target type
0 6 8 any
</code></pre>
<h2>装箱拆箱</h2>
<p>在刚开始学习 Java 语言的你可能会被自动装箱和拆箱搞得晕头转向。Java 中有 8 种基本类型,但鉴于 Java 面向对象的特点,它们同样有着对应的 8 个包装类型,比如 int 和 Integer包装类型的值可以为 null很多时候它们都能够相互赋值。</p>
<p>我们使用下面的代码从字节码层面上来观察一下:</p>
<pre><code>public class Box {
public Integer cal() {
Integer a = 1000;
int b = a * 10;
return b;
}
}
</code></pre>
<p>上面是一段简单的代码,首先使用包装类型,构造了一个值为 1000 的数字,然后乘以 10 后返回,但是中间的计算过程,使用了普通类型 int。</p>
<pre><code>public java.lang.Integer read();
descriptor: ()Ljava/lang/Integer;
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: sipush 1000
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: aload_1
8: invokevirtual #3 // Method java/lang/Integer.intValue:()I
11: bipush 10
13: imul
14: istore_2
15: iload_2
16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: areturn
</code></pre>
<p>通过观察字节码,我们发现赋值操作使用的是 Integer.valueOf 方法,在进行乘法运算的时候,调用了 Integer.intValue 方法来获取基本类型的值。在方法返回的时候,再次使用了 Integer.valueOf 方法对结果进行了包装。</p>
<p>这就是 Java 中的自动装箱拆箱的底层实现。</p>
<p>但这里有一个 Java 层面的陷阱问题,我们继续跟踪 Integer.valueOf 方法。</p>
<pre><code>@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i &gt;= IntegerCache.low &amp;&amp; i &lt;= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
</code></pre>
<p>这个 IntegerCache缓存了 low 和 high 之间的 Integer 对象,可以通过 -XX:AutoBoxCacheMax 来修改上限。</p>
<p>下面是一道经典的面试题,请考虑一下运行代码后,会输出什么结果?</p>
<pre><code>public class BoxCacheError{
public static void main(String[] args) {
Integer n1 = 123;
Integer n2 = 123;
Integer n3 = 128;
Integer n4 = 128;
System.out.println(n1 == n2);
System.out.println(n3 == n4);
}
</code></pre>
<p>当我使用 <strong>java BoxCacheError</strong> 执行时,是 true,false当我加上参数 <strong>java -XX:AutoBoxCacheMax=256 BoxCacheError</strong> 执行时,结果是 true,ture原因就在于此。</p>
<h2>数组访问</h2>
<p>我们都知道,在访问一个数组长度的时候,直接使用它的属性 <strong>.length</strong> 就能获取,而在 Java 中却无法找到对于数组的定义。</p>
<p>比如 int[] 这种类型,通过 getClassgetClass 是 Object 类中的方法)可以获取它的具体类型是 <strong>[I</strong></p>
<p>其实,数组是 JVM 内置的一种对象类型,这个对象同样是继承的 Object 类。</p>
<p>我们使用下面一段代码来观察一下数组的生成和访问。</p>
<pre><code>public class ArrayDemo {
int getValue() {
int[] arr = new int[]{
1111, 2222, 3333, 4444
};
return arr[2];
}
int getLength(int[] arr) {
return arr.length;
}
}
</code></pre>
<p>首先看一下 getValue 方法的字节码。</p>
<pre><code>int getValue();
descriptor: ()I
flags:
Code:
stack=4, locals=2, args_size=1
0: iconst_4
1: newarray int
3: dup
4: iconst_0
5: sipush 1111
8: iastorae
9: dup
10: iconst_1
11: sipush 2222
14: iastore
15: dup
16: iconst_2
17: sipush 3333
20: iastore
21: dup
22: iconst_3
23: sipush 4444
26: iastore
27: astore_1
28: aload_1
29: iconst_2
30: iaload
31: ireturn
</code></pre>
<p>可以看到,新建数组的代码,被编译成了 newarray 指令。数组里的初始内容,被顺序编译成了一系列指令放入:</p>
<ul>
<li><strong>sipush</strong> 将一个短整型常量值推送至栈顶;</li>
<li><strong>iastore</strong> 将栈顶 int 型数值存入指定数组的指定索引位置。</li>
</ul>
<blockquote>
<p>为了支持多种类型从操作数栈存储到数组有更多的指令bastore、castore、sastore、iastore、lastore、fastore、dastore、aastore。</p>
</blockquote>
<p>数组元素的访问,是通过第 28 ~ 30 行代码来实现的:</p>
<ul>
<li>
<p><strong>aload_1</strong> 将第二个引用类型本地变量推送至栈顶,这里是生成的数组;</p>
</li>
<li>
<p><strong>iconst_2</strong> 将 int 型 2 推送至栈顶;</p>
</li>
<li>
<p><strong>iaload</strong> 将 int 型数组指定索引的值推送至栈顶。</p>
</li>
</ul>
<p>值得注意的是,在这段代码运行期间,有可能会产生 ArrayIndexOutOfBoundsException但由于它是一种非捕获型异常我们不必为这种异常提供异常处理器。</p>
<p>我们再看一下 getLength 的字节码,字节码如下:</p>
<pre><code>int getLength(int[]);
descriptor: ([I)I
flags:
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: arraylength
2: ireturn
</code></pre>
<p>可以看到,获取数组的长度,是由字节码指令 arraylength 来完成的。</p>
<h2>foreach</h2>
<p>无论是 Java 的数组,还是 List都可以使用 foreach 语句进行遍历,比较典型的代码如下:</p>
<pre><code>import java.util.List;
public class ForDemo {
void loop(int[] arr) {
for (int i : arr) {
System.out.println(i);
}
}
void loop(List&lt;Integer&gt; arr) {
for (int i : arr) {
System.out.println(i);
}
}
</code></pre>
<p>虽然在语言层面它们的表现形式是一致的,但实际实现的方法并不同。我们先看一下遍历数组的字节码:</p>
<pre><code>void loop(int[]);
descriptor: ([I)V
flags:
Code:
stack=2, locals=6, args_size=2
0: aload_1
1: astore_2
2: aload_2
3: arraylength
4: istore_3
5: iconst_0
6: istore 4
8: iload 4
10: iload_3
11: if_icmpge 34
14: aload_2
15: iload 4
17: iaload
18: istore 5
20: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
23: iload 5
25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
28: iinc 4, 1
31: goto 8
34: return
</code></pre>
<p>可以很容易看到,它将代码解释成了传统的变量方式,即 <strong>for(int i;i&lt;length;i++)</strong> 的形式。</p>
<p>而 List 的字节码如下:</p>
<pre><code>void loop(java.util.List&lt;java.lang.Integer&gt;);
Code:
0: aload_1
1: invokeinterface #4, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
6: astore_2-
7: aload_2
8: invokeinterface #5, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
13: ifeq 39
16: aload_2
17: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
22: checkcast #7 // class java/lang/Integer
25: invokevirtual #8 // Method java/lang/Integer.intValue:()I
28: istore_3
29: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
32: iload_3
33: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
36: goto 7
39: return
</code></pre>
<p>它实际是把 list 对象进行迭代并遍历的,在循环中,使用了 Iterator.next() 方法。</p>
<p>使用 jd-gui 等反编译工具,可以看到实际生成的代码:</p>
<pre><code>void loop(List&lt;Integer&gt; paramList) {
for (Iterator&lt;Integer&gt; iterator = paramList.iterator(); iterator.hasNext(); ) {
int i = ((Integer)iterator.next()).intValue();
System.out.println(i);
}
}
</code></pre>
<h2>注解</h2>
<p>注解在 Java 中得到了广泛的应用Spring 框架更是由于注解的存在而起死回生。注解在开发中的作用就是做数据约束和标准定义,可以将其理解成代码的规范标准,并帮助我们写出方便、快捷、简洁的代码。
那么注解信息是存放在哪里的呢?我们使用两个 Java 文件来看一下其中的一种情况。
<strong>MyAnnotation.java</strong></p>
<pre><code>public @interface MyAnnotation {
}
</code></pre>
<p><strong>AnnotationDemo</strong></p>
<pre><code>@MyAnnotation
public class AnnotationDemo {
@MyAnnotation
public void test(@MyAnnotation int a){
}
}
</code></pre>
<p>下面我们来看一下字节码信息。</p>
<pre><code>{
public AnnotationDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object.&quot;&lt;init&gt;&quot;:()V
4: return
LineNumberTable:
line 2: 0
public void test(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 6: 0
RuntimeInvisibleAnnotations:
0: #11()
RuntimeInvisibleParameterAnnotations:
0:
0: #11()
}
SourceFile: &quot;AnnotationDemo.java&quot;
RuntimeInvisibleAnnotations:
0: #11()
</code></pre>
<p>可以看到,无论是类的注解,还是方法注解,都是由一个叫做 RuntimeInvisibleAnnotations 的结构来存储的,而参数的存储,是由 RuntimeInvisibleParameterAnotations 来保证的。</p>
<h2>小结</h2>
<p>本课时我们简单介绍了一下工作中常见的一些问题并从字节码层面分析了它的原理包括异常的处理、finally 块的执行顺序;以及隐藏的装箱拆箱和 foreach 语法糖的底层实现。</p>
<p>由于 Java 的特性非常多,这里不再一一列出,但都可以使用这种简单的方式,一窥究竟。可以认为本课时属于抛砖引玉,给出了一种学习思路。</p>
<p>另外,也可以对其中的性能和复杂度进行思考。可以注意到,在隐藏的装箱拆箱操作中,会造成很多冗余的字节码指令生成。那么,这个东西会耗性能吗?答案是肯定的,但是也不必纠结于此。</p>
<p>你所看到的字节码指令可能洋洋洒洒几千行看起来很吓人但执行速度几乎都是纳秒级别的。Java 的无数框架,包括 JDK也不会为了优化这种性能对代码进行限制。了解其原理但不要舍本逐末比如减少一次 Java 线程的上下文切换,就比你优化几千个装箱拆箱动作,来的更快捷一些。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/深入浅出 Java 虚拟机-完/20 动手实践:从字节码看并发编程的底层实现.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/深入浅出 Java 虚拟机-完/22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997a74cd673cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>