《深入理解java虚拟机》笔记——简析java类文件结构
一直不太搞得明确jvm究竟是如何进行类载入的,在看资料的过程中迷迷糊糊。在理解类载入之前,首先看看java的类文件结构究竟是如何的,都包含了哪些内容。
最直接的參考当然是官方文档:The Java® Virtual Machine Specification
我写了一个最简单的java程序。依据这个程序来分析一下.class文件里究竟都存了些什么。
java程序:
class Par {
public int x = 5;
public void f(){
System.out.println("par static");
}
}
class Sub extends Par{
public int x = 6;
public void f() {
System.out.println("Sub static");
}
}
public class Test {
public static void main(String[] args) {
Par par = new Sub();
System.out.println(par.x);
par.f();
}
}
生成的.class文件内容
cafe babe 0000 0033 0027 0a00 0900 1207
0013 0a00 0200 1209 0014 0015 0900 1600
170a 0018 0019 0a00 1600 1a07 001b 0700
1c01 0006 3c69 6e69 743e 0100 0328 2956
0100 0443 6f64 6501 000f 4c69 6e65 4e75
6d62 6572 5461 626c 6501 0004 6d61 696e
0100 1628 5b4c 6a61 7661 2f6c 616e 672f
5374 7269 6e67 3b29 5601 000a 536f 7572
6365 4669 6c65 0100 0954 6573 742e 6a61
7661 0c00 0a00 0b01 0016 636f 6d2f 7468
696e 6b69 6e67 696e 6a61 7661 2f53 7562
0700 1d0c 001e 001f 0700 200c 0021 0022
0700 230c 0024 0025 0c00 2600 0b01 0017
636f 6d2f 7468 696e 6b69 6e67 696e 6a61
7661 2f54 6573 7401 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 106a 6176
612f 6c61 6e67 2f53 7973 7465 6d01 0003
6f75 7401 0015 4c6a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 3b01 0016 636f
6d2f 7468 696e 6b69 6e67 696e 6a61 7661
2f50 6172 0100 0178 0100 0149 0100 136a
6176 612f 696f 2f50 7269 6e74 5374 7265
616d 0100 0770 7269 6e74 6c6e 0100 0428
4929 5601 0001 6600 2100 0800 0900 0000
0000 0200 0100 0a00 0b00 0100 0c00 0000
1d00 0100 0100 0000 052a b700 01b1 0000
0001 000d 0000 0006 0001 0000 0018 0009
000e 000f 0001 000c 0000 003b 0002 0002
0000 0017 bb00 0259 b700 034c b200 042b
b400 05b6 0006 2bb6 0007 b100 0000 0100
0d00 0000 1200 0400 0000 1b00 0800 1c00
1200 1d00 1600 1e00 0100 1000 0000 0200
11
使用javap反汇编一下生成的.class文件(javap -v com.thinkinginjava.Test)
Classfile /E:/Workspaces/MyEclipse/websocketTest/src/com/thinkinginjava/Test.cla
ss
Last modified 2016-4-2; size 513 bytes
MD5 checksum 32ca9f73cf634398be1085454c6b21c3
Compiled from "Test.java"
public class com.thinkinginjava.Test
SourceFile: "Test.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#18 // java/lang/Object."<init>":()V
#2 = Class #19 // com/thinkinginjava/Sub
#3 = Methodref #2.#18 // com/thinkinginjava/Sub."<init>":()V
#4 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Fieldref #22.#23 // com/thinkinginjava/Par.x:I
#6 = Methodref #24.#25 // java/io/PrintStream.println:(I)V
#7 = Methodref #22.#26 // com/thinkinginjava/Par.f:()V
#8 = Class #27 // com/thinkinginjava/Test
#9 = Class #28 // java/lang/Object
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 SourceFile
#17 = Utf8 Test.java
#18 = NameAndType #10:#11 // "<init>":()V
#19 = Utf8 com/thinkinginjava/Sub
#20 = Class #29 // java/lang/System
#21 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#22 = Class #32 // com/thinkinginjava/Par
#23 = NameAndType #33:#34 // x:I
#24 = Class #35 // java/io/PrintStream
#25 = NameAndType #36:#37 // println:(I)V
#26 = NameAndType #38:#11 // f:()V
#27 = Utf8 com/thinkinginjava/Test
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 com/thinkinginjava/Par
#33 = Utf8 x
#34 = Utf8 I
#35 = Utf8 java/io/PrintStream
#36 = Utf8 println
#37 = Utf8 (I)V
#38 = Utf8 f
{
public com.thinkinginjava.Test();
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 24: 0
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/thinkinginjava/Sub
3: dup
4: invokespecial #3 // Method com/thinkinginjava/Sub."<init>":()V
7: astore_1
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: getfield #5 // Field com/thinkinginjava/Par.x:I
15: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
18: aload_1
19: invokevirtual #7 // Method com/thinkinginjava/Par.f:()V
22: return
LineNumberTable:
line 27: 0
line 28: 8
line 29: 18
line 30: 22
}
依据java虚拟机规范,首先介绍一下Class 文件格式
“Class文件格式採用一种相似于C语言结构体的伪结构来存储数据,这样的伪结构仅仅有两种数据类型:无符号数和表。
”
无符号数属于主要的数据结构,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数;
表是由多个无符号数或者其它表作为数据项构成的复合数据类型。表都习惯性的以“_info”结尾。
Class文件结构
ClassFile {
u4 magic;//魔数
u2 minor_version;//次版本号号
u2 major_version;//主版本号号
u2 constant_pool_count;//常量池容量计数
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;//訪问标志
u2 this_class;//类索引
u2 super_class;//父类索引
u2 interfaces_count;//接口索引数
u2 interfaces[interfaces_count];//接口索引集合
u2 fields_count;//字段数
field_info fields[fields_count];
u2 methods_count;//方法数
method_info methods[methods_count];
u2 attributes_count;//属性数
attribute_info attributes[attributes_count];
}
一个个来分析:
魔数、主次版本号号
我本地的是JDK 1.7.0的,所以是主次版本号号是 00 00 00 33,不同的jdk版本号生成的版本号号不同。
常量池
在主次版本号号后面的就是常量池入口,由于常量池的数量是不固定的。所以在常量池的入口须要放置一项u2类型的数据,代表常量池容量计数值
可见当前的计数值为0x0027 ,即39。这代表常量池中有38项常量。索引號为1-38,Class文件仅仅有常量池的容器技术是从1開始的
从之前的反汇编代码中也能够看到,常量池中确实有38项
常量池中主要有3类常量:
- 类和接口的全限定名
- 字段的名称和描写叙述符
方法的名称和描写叙述符
常量池中的项目类型例如以下:
每一种常量都有它的结构。比如:
CONSTANT_Class_info {
u1 tag; //标志。占1个字节 07
u2 name_index;//指向全限定名常量项的索引
}
CONSTANT_Utf8_info {
u1 tag;//标志 01
u2 length;//UTF-8编码的字符串占用的字节数
u1 bytes[length];//长度为length的UTF-8编码的字符串
}
。。。
。每一项可详细參考官方文档
举几个样例:
以#1为例, #1 = Methodref #9.#18
The CONSTANT_MethodHandle_info Structure
CONSTANT_MethodHandle_info {
u1 tag; //标志 10
u1 reference_kind; //指向声明方法的类描写叙述符的CONSTANT_Class_info的索引项
u2 reference_index;//指向名称及类型描写叙述符CONSTANT_NameAndType的索引项
}
tag:0a
reference_kind:0x0009
reference_index:0x0012
以#10为例 #10 = Utf8 < init >
The CONSTANT_Utf8_info Structure
CONSTANT_Utf8_info {
u1 tag;//标志 01
u2 length;//UTF-8编码的字符串占用的字节数
u1 bytes[length];//长度为length的UTF-8编码的字符串
}
tag: 01
length: 6
bytes[length]:详细相应的编码 3c 69 6e 69 74 3e
能够看到。常量区总共同拥有38项。在.class文件里的表演示样例如以下图所看到的。依照上面的分析就可以分析出每一个常量相应的是哪些16进制位
訪问标记
分析完常量池,在常量池结束后。紧跟着的两个字节代表訪问标志。这个标志用于识别一些类或者接口层次的訪问信息。
(直接截图了。。
。
)
从代码中能够分析出。我写的java程序的訪问标志位0x0021。即ACC_PUBLIC、ACC_SUPER标志位真。其余标志为假,因此access_flags的值为0x0001 | 0x0020 = 0x0021(或运算就可以算出是哪几个标志为真,哪几个为假),例如以下图所看到的。
类索引、父类索引与接口索引集合
类索引、父类索引和接口索引集合都按顺序排列在訪问标记之后。
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据。接口索引集合是一组u2类型的集合。
- 类索引用于确定这个类的全限定名
父类索引用于确定这个类的父类的全限定名
对于接口索引集合,入口第一项u2类型的数据为接口计数器(interface_count),表示索引表的容量。,没有实现不论什么接口。则计数为0,后面接口的索引表不再占用不论什么字节。
例如以下图所看到的,类索引相应0x0008,即com/thinkinginjava/Test类。父类索引为Object类。0x0009即指向java/lang/Object
字段表集合
字段表用于标书接口或者类中声明的变量。
字段包含类级变量以及实例级变量,但不包含在方法内部声明的局部变量。
java中描写叙述一个字段包含的信息有:
- 字段的作用域(public,protected,private)
- 实例变量还是类变量(static)
- 可变性(final)
- 并发可见性(volatile)
- 可否序列化(transient)
- 字段数据类型(基本类型。对象。数组)
字段名称
关于字段表能够參考这篇博文看看
由于我的程序中Test类中未定义字段,所以结果显示为0x0000,例如以下图
方法表集合
紧接着字段表的是方法表集合,理解了字段表看方法表就相对简单了
方法表的结构与字段表一样。依次包含了訪问标志(access_flags)、名称索引(name_index)、描写叙述符索引(descriptor_index)、属性表集合(attributes)几项
官方文档
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法訪问标志:
分析一下字节码:
- 方法表集合的计数器值为0x0002,代表集合中有两个方法(一个是编译去加入的实例构造器<init>和源代码中的main方法)
- 第一个方法的訪问标志为0x0001,即仅仅有ACC_PUBLIC标志为真
- 名称索引值为0x000a,查找常量池可得方法名为<init>
- 描写叙述符索引值为0x000b,相应常量为”()V”
- 属性表计数器值为0x0001。表示有一个属性。相应的属性名称索引为0x000c,相应常量为“Code”。说明此属性时方法的字节码描写叙述。
属性表集合
官方文档
属性表在之前出现过多次了,在Class文件、字段表、方法表中都能够携带自己的属性表集合。用于描写叙述某些场景专有的信息。
对于每一个属性,它的名称须要从一个常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是全然自己定义的,仅仅须要通过一个u4的长度属性去说明属性值所占用的位数就可以。
属性表结构例如以下所看到的:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
举几个样例:
1.Code属性
java方法体中的代码经过javac编译处理后,终于会变成字节码指令存储在Code属性内。Code属性出如今方法表的属性集合之中,但并不是全部的方法表都必须存在这个属性。譬如接口或者抽象类中的方法就不存在Code属性。假设Code属性存在,那么它的结构例如以下所看到的:
Code_attribute {
u2 attribute_name_index; //指向CONSTANT_Utf8_info型常量的索引
u4 attribute_length; //属性值的长度
u2 max_stack; //操作数栈深度的最大值
u2 max_locals; //局部变量表所需的存储空间
//code_length和code用来存储java源程序编译后生成的字节码指令
u4 code_length; //字节码长度
u1 code[code_length]; //存储字节码指令的一系列字节流
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
分析一下字节码:
- 由上面方法表的分析,第一个<init>方法相应的属性名为Code,其值为0x000c
- 紧接着为attribute_length,表示属性值的长度,其值为0x0000001d
- 接着为max_stack。表示操作数栈深度的最大值,其值为0x0001
- 接着为max_locals, 表示局部变量表所需的存储空间,其值为0x0001
- 接着为code_length。占4个字节。表示字节码长度,其值为0x00000005
- 紧接着读入尾随的5个字节,依据字节码指令表翻译出相应的字节码指令。翻译“2a b7 00 01 b1”过程:
1)读入2a。对用指令aload_0
2)读入b7,相应指令invokespecial
3)读入00 01,这是invokespecial的參数,为常量池中0x0001相应的常量
4)读入b1,相应的指令为return
- 接着为exception_table_length,此处为0,没有异常
- 接着为attribute_count。代表这个Code中有1个属性
然后就是这个属性的描写叙述啦,索引號为0x000d,找到相应的常量池索引號。为LineNumberTable,so,这个属性是LineNumberTable。那么看一下LineNumberTable属性结构:
LineNumberTable属性LineNumberTable_attribute {
u2 attribute_name_index; //属性名索引
u4 attribute_length; //属性长度
u2 line_number_table_length;
{ u2 start_pc; //字节码行号
u2 line_number; //java源代码行号
} line_number_table[line_number_table_length];
}
从class文件里能够看出,
attribute_name_index为0x000d,
attribute_length为0x00000006。
line_number_table_length为0x0001。
start_pc为0x0001
line_number为0x0018
正好相应了LineNumberTable:
line 24: 0
到此为止,一个<init>方法分析完了。包含了它包含的属性。
之前说了。该程序共同拥有2个方法。另一个为main函数。对它的分析跟上面一样,不做分析了
有些博客也能够參考一下,写的比較细
http://blog.csdn.net/u010349169/article/category/2620885
《深入理解java虚拟机》笔记——简析java类文件结构的更多相关文章
- 《深入理解Java虚拟机》-----第6章 类文件结构——Java高级开发必须懂的
代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步. 6.1 概述 记得在第一节计算机程序课上我的老师就讲过:“计算机只认识0和1,所以我们写的程序需要经编译器翻 ...
- 《深入理解java虚拟机》第六章 类文件结构
第六章 类文件结构 6.2 无关性的基石 各种不同平台的虚拟机与所有的平台都统一使用的程序存储格式--字节码(ByteCode)是构成平台无关性的基石.java虚拟机不和包括java在内的任何语言 ...
- 深入理解Java虚拟机笔记
1. Java虚拟机所管理的内存 2. 对象创建过程 3. GC收集 4. HotSpot算法的实现 5. 垃圾收集器 6. 对象分配内存与回收细节 7. 类文件结构 8. 虚拟机类加载机制 9.类加 ...
- 深入理解java虚拟机笔记Chapter2
java虚拟机运行时数据区 首先获取一个直观的认识: 程序计数器 线程私有.各条线程之间计数器互不影响,独立存储. 当前线程所执行的字节码行号指示器.字节码解释器工作时通过改变这个计数器值选取下一条需 ...
- Java虚拟机笔记(四):垃圾收集器
前言 前一篇文章介绍了内存的垃圾收集算法,现在介绍下内存回收的具体实现--垃圾收集器. 由于Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的垃圾收集 ...
- Java虚拟机笔记(三):垃圾收集算法
一.标记-清除(Mark-Sweep)算法 标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想. 标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对 ...
- Java虚拟机笔记(二):GC垃圾回收和对象的引用
为什么要了解GC 我们都知道Java开发者在开发过程中是不需要关心对象的回收的,因为Java虚拟机的原因,它会自动回收那些失效的垃圾对象.那我们为什么还要去了解GC和内存分配呢? 答案很简单:当我们需 ...
- Java虚拟机笔记(一):类加载机制
一.概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 二.类加载的生命周期 类从被加载到 ...
- JDK框架简析--java.lang包中的基础类库、基础数据类型
题记 JDK.Java Development Kit. 我们必须先认识到,JDK不过,不过一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含 ...
随机推荐
- rem 单位
rem(font size of the root element)是指相对于根元素的字体大小的单位. 主流的一些web app的适配解决方案: 流式布局: 流式布局在页面布局的时候都是通过百分比 ...
- 基于python3.x,使用Tornado中的torndb模块操作数据库
目前Tornado中的torndb模块是不支持python3.x,所以需要修改部分torndb源码即可正常使用 1.开发环境介绍 操作系统:win8(64位),python版本:python3.6(3 ...
- python+selenium安装
1.下载Python 请到官网自行下载安装https://www.python.org/downloads/ 在安装的时候,注意一定要勾上这个选项,可以免去我们配置系统变量的麻烦,如果你忘了,没关系, ...
- 多线程+socket实现多人聊天室
最近在学习多线程的时候打算做一个简单的多线程socke聊天的程序,结果发现网上的代码都没有完整的实现功能,所以自己实现了一个demo: demo功能大致就是,有一个服务端负责信息转发,多个客户端发送消 ...
- NetFlow学习笔记
NetFlow学习笔记 标签: netflow 由于工作需要,对NetFlow做了一些学习和调研,并总结成文档以供学习分享. 背景:随着系统的升级与漏洞的修补,入侵主机进而进行破坏的病毒攻击方式在攻击 ...
- canvas三环加载进度条
之前做了一个三个圆形叠加在一起的加载,用的是定位和cile来操作,但是加载的头部不能是圆形.后来用canvas做了一个,但是这个加载的进度不好调整,原理很简单,就是让一个圆,按照圆形轨迹进行运动就可以 ...
- 加密代理和Retrofit解密Converter
最近在研究安卓的Retrofit框架,服务器的数据全部用加密算法加密了,发现无法使用"com.squareup.retrofit2:converter-gson:2.1.0"Jar ...
- Android OpenGL ES 开发(一): OpenGL ES 介绍
简介OpenGL ES 谈到OpenGL ES,首先我们应该先去了解一下Android的基本架构,基本架构下图: 在这里我们可以找到Libraries里面有我们目前要接触的库,即OpenGL ES. ...
- Numpy数组对象的操作-索引机制、切片和迭代方法
前几篇博文我写了数组创建和数据运算,现在我们就来看一下数组对象的操作方法.使用索引和切片的方法选择元素,还有如何数组的迭代方法. 一.索引机制 1.一维数组 In [1]: a = np.arange ...
- unity中Ray、RaycastHit 、Raycast(小白之路)
1.Ray Ray(Vector3 origin, Vector3 direction) Ray:在程序中可以理解为射线,就是以某个位置(origin)朝某个方向(direction)的一条射线,是一 ...