java.lang.class 字节码_玩转Java字节码(上)

Java字节码(Byte-code)是指Java源代码编译而成的,供JVM虚拟机执行的代码。用文本编辑器打开将是一团乱码,用十六进制编辑器打开能勉强看懂头部一些规范,例如魔数,主次版本。而用/bin/javap“反编译”之后可以得到一个人类可读的代码段,类似于用Wireshark来分析cap数据包。

要看懂这个文件必须要知道字节码规范,主要有以下两个表格内容构成。其中表1是Class文件的组成部分,各段依次排列,排列紧密,无多余的分隔符。其中u1~u8表示占用字节数,1表示1个字节,2表示2个字节,4字节,8字节。例如魔数:仅有1个,占用4个字节,位于文件的前4个字节。紧接着4个字节是minor_version,major_version各占用2个字节。接下来2个字节是常量池数量统计,意味着一个Class不能有超过65535个常量池(maybe)。接下来是常量池,常量池的格式由表2来定义,下一段再来分析。依次类推可以将整个Class文件分析出来。

e74e5c27a9b19e63f6976359da684d6b.png

常量池中的每一个常量项通常有2~3个项目组成。例如:CONSTANT_Utf8_info这一项,第1个字节是表示定义(tag),值为1,紧接着2个字节表示该项将占用的长度(length),意味着接下来的几个字节长度将是字符串的实际内容。

再来分析下CONSTANT_Integer_info项,从下表可以看出头一个字节tag值为3,接下来4个字节安高位在前编码表示int值,所以在Java中int数据类型的最大值是去掉一个符号位的0x7FFFFFFF。Utf8,Integer,Float,Long,Double这5个都是类似的,直接以值的形式存储。其余常量项都是存储着引用值(index),分别指向这5个值或其它地方,这里不一一介绍了。

等等java不是八大基本数据类型吗,这里怎么只有提到了4个,没错,小于int的都当作int处理了。也就是boolean,byte,char,short都“拉长”至int级别来对待。例如:boolean a = “true” 对应的字节码指令是:iconst_1,putfield a,将int型常量值1进栈赋值给变量a。

介于编译器可能会做一些性能优化,例如int值超过2字节(32768)才会加入到常量项,小于2字节由sipush指令在运行时分配。

200c6d852507ad6b584d0bfa845fb72e.png

分析这玩意实在有些无聊,既然本文是以玩转为主题,自然就说点有趣的。首先一个简单的Java代码如下:

package classloader;

/**

* Created by dorole.com on 2016/6/13.

*/

public class Hello {

public String sayHello(String name) {

System.out.println(this.getClass().getName() + " -> " + name);

return name;

}

}

这段代码自然再简单不过了,随便一个文本编辑器敲进去,javac编译,找个main方法调用完事。那能不能不编译直接调用?当然没问题,先不谈这个,先来看看编译后究竟是个什么样子。我们用WinHex来打开编译好的Class文件,截图如下:

65ede45d4bae013fc865f9f823fd58bd.png

黑色标记的是全部的常量项tag值(一个个标出来,该是有多蛋疼~),自己可以对照表2慢慢分析。当然也可以借助javap来“反编译”下,输出更为直观的表格:

Constant pool:

#1 = Utf8 classloader.Hello

#2 = Class #1 // "classloader.Hello"

#3 = Utf8 java/lang/Object

#4 = Class #3 // java/lang/Object

#5 = Utf8

#6 = Utf8 ()V

#7 = NameAndType #5:#6 // "":()V

#8 = Methodref #4.#7 // java/lang/Object."":()V

#9 = Utf8 sayHello

#10 = Utf8 (Ljava/lang/String;)Ljava/lang/String;

#11 = Utf8 java/lang/System

#12 = Class #11 // java/lang/System

#13 = Utf8 out

#14 = Utf8 Ljava/io/PrintStream;

#15 = NameAndType #13:#14 // out:Ljava/io/PrintStream;

#16 = Fieldref #12.#15 // java/lang/System.out:Ljava/io/PrintStream;

#17 = Utf8 java/lang/StringBuilder

#18 = Class #17 // java/lang/StringBuilder

#19 = Methodref #18.#7 // java/lang/StringBuilder."":()V

#20 = Utf8 getClass

#21 = Utf8 ()Ljava/lang/Class;

#22 = NameAndType #20:#21 // getClass:()Ljava/lang/Class;

#23 = Methodref #4.#22 // java/lang/Object.getClass:()Ljava/lang/Class;

#24 = Utf8 java/lang/Class

#25 = Class #24 // java/lang/Class

#26 = Utf8 getName

#27 = Utf8 ()Ljava/lang/String;

#28 = NameAndType #26:#27 // getName:()Ljava/lang/String;

#29 = Methodref #25.#28 // java/lang/Class.getName:()Ljava/lang/String;

#30 = Utf8 append

#31 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;

#32 = NameAndType #30:#31 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#33 = Methodref #18.#32 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#34 = Utf8 ->

#35 = String #34 // ->

#36 = Utf8 toString

#37 = NameAndType #36:#27 // toString:()Ljava/lang/String;

#38 = Methodref #18.#37 // java/lang/StringBuilder.toString:()Ljava/lang/String;

#39 = Utf8 java/io/PrintStream

#40 = Class #39 // java/io/PrintStream

#41 = Utf8 println

#42 = Utf8 (Ljava/lang/String;)V

#43 = NameAndType #41:#42 // println:(Ljava/lang/String;)V

#44 = Methodref #40.#43 // java/io/PrintStream.println:(Ljava/lang/String;)V

#45 = Utf8 Code

这里我仅放上常量池部分,这下很好对了把。Class文件就是这样一个紧凑结构,不愧是为嵌入式而打造的一门语言。回到之前的问题,有没有可能自己来生成字节码?答案是肯定的,下回分解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值