写在前边的话
最近在看《深入理解Java虚拟机》这本书,学习Java虚拟机底层实现原理。通过写博客的方式记录自己的学习过程以及对知识的理解。如有总结不正确的地方,欢迎大家指出!
首先学习Java虚拟机的话,肯定是要跟字节码打交道的。那么如何查看字节码文件呢?本篇文章就来总结一下查看字节码的方式。
方式一 使用jdk自带的反解析工具查看Java字节码
一、javap命令简述
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。
二、使用方法
javap
其中classes就是你要反编译的class文件。
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
一般常用的是-v -l -c三个选项。
javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。
javap -l 会输出行号和本地变量表信息。
javap -c 会对当前class字节码进行反编译生成汇编代码。
三、例子
rp@MacBook-Pro java % javap -v HelloWorld
Classfile /Users/rp/Documents/java/HelloWorld.class
Last modified 2020-11-24; size 720 bytes
MD5 checksum dae445d00ec5ef52e4c5100e05007f53
Compiled from "HelloWorld.java"
public class HelloWorld
minor version: 0
major version: 59
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #10 // java/lang/System
#9 = NameAndType #11:#12 // out:Ljava/io/PrintStream;
#10 = Utf8 java/lang/System
#11 = Utf8 out
#12 = Utf8 Ljava/io/PrintStream;
#13 = String #14 // Hello World!
#14 = Utf8 Hello World!
#15 = Methodref #16.#17 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Class #18 // java/io/PrintStream
#17 = NameAndType #19:#20 // println:(Ljava/lang/String;)V
#18 = Utf8 java/io/PrintStream
#19 = Utf8 println
#20 = Utf8 (Ljava/lang/String;)V
#21 = String #22 // HelloWorld
#22 = Utf8 HelloWorld
#23 = Methodref #16.#24 // java/io/PrintStream.println:(Z)V
#24 = NameAndType #19:#25 // println:(Z)V
#25 = Utf8 (Z)V
#26 = Methodref #27.#28 // java/lang/String.equals:(Ljava/lang/Object;)Z
#27 = Class #29 // java/lang/String
#28 = NameAndType #30:#31 // equals:(Ljava/lang/Object;)Z
#29 = Utf8 java/lang/String
#30 = Utf8 equals
#31 = Utf8 (Ljava/lang/Object;)Z
#32 = Class #22 // HelloWorld
#33 = Utf8 Code
#34 = Utf8 LineNumberTable
#35 = Utf8 main
#36 = Utf8 ([Ljava/lang/String;)V
#37 = Utf8 StackMapTable
#38 = Class #39 // "[Ljava/lang/String;"
#39 = Utf8 [Ljava/lang/String;
#40 = Utf8 SourceFile
#41 = Utf8 HelloWorld.java
{
public HelloWorld();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String Hello World!
5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc #21 // String HelloWorld
10: astore_1
11: ldc #21 // String HelloWorld
13: astore_2
14: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: aload_2
19: if_acmpne 26
22: iconst_1
23: goto 27
26: iconst_0
27: invokevirtual #23 // Method java/io/PrintStream.println:(Z)V
30: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_2
35: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
38: invokevirtual #23 // Method java/io/PrintStream.println:(Z)V
41: ldc #21 // String HelloWorld
43: astore_3
44: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
47: aload_1
48: aload_3
49: if_acmpne 56
52: iconst_1
53: goto 57
56: iconst_0
57: invokevirtual #23 // Method java/io/PrintStream.println:(Z)V
60: return
LineNumberTable:
line 4: 0
line 5: 8
line 6: 11
line 7: 14
line 8: 30
line 9: 41
line 10: 44
line 11: 60
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 26
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 255 /* full_frame */
offset_delta = 28
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "HelloWorld.java"
方式二 使用jclasslib插件查看Java字节码
一、IDEA下载与显示jclasslib Bytecode viewer插件:
1、打开IDEA的Settings设置,找到Plugins,然后点击右侧Browse repositories…按钮。
2、弹出框里搜索jclasslib插件,点击右侧Install按钮,下载完成后点击Restart Intellij IDEA按钮。
3、重启IDEA后,在View中选择Show Bytecode With jclasslib。
4、此时右侧会显示出jclasslib插件导航栏,点击展开后即可查看.class内容。
二、如何使用jclasslib插件查看Java字节码
1、以我的TransferServiceImpl类为例,进行查看Java字节码信息。
package com.fuyf.stu.service.impl;
import com.fuyf.stu.annotation.Autowired;
import com.fuyf.stu.annotation.Service;
import com.fuyf.stu.annotation.Transactional;
import com.fuyf.stu.dao.TransferDao;
import com.fuyf.stu.pojo.Account;
import com.fuyf.stu.service.TransferService;
import java.sql.SQLException;
@Service("transferService")
public class TransferServiceImpl implements TransferService {
private TransferDao transferDao;
@Autowired
public void setTransferDao(TransferDao transferDao){
this.transferDao = transferDao;
}
/**
* 转账功能实现
* @param from
* @param to
* @param amount
* @throws ClassNotFoundException
* @throws SQLException
*/
@Override
@Transactional
public void transfer(Account from,Account to,Double amount) throws SQLException, ClassNotFoundException {
//转出账户取得金额
Double amountFrom = transferDao.queryAmountByCardNaAndName(from);
//转入账户取得金额
Double amountTo = transferDao.queryAmountByCardNaAndName(to);
//更新转出账户金额
from.setMoney(amountFrom-amount);
transferDao.updateAmount(from);
int i = 1 / 0;
//更新转入账户金额
to.setMoney(amountTo+amount);
transferDao.updateAmount(to);
}
}
2、展开jclasslib插件后,选择TransferServiceImpl.java文件中的方法->transfer->Code,即可在右侧查看Bytecode栏的Java字节码信息。
至此,本篇介绍已经完了。既然知道了字节码查看工具,那么class文件中都有什么内容呢?感兴趣的童鞋可以查看《深入理解Java虚拟机学习之二class文件内容详解》。