`
ezerg
  • 浏览: 274183 次
  • 性别: Icon_minigender_1
  • 来自: 石家庄
社区版块
存档分类
最新评论

简要说明 Java 中 .class 文件的内部结构

阅读更多
了解 .class 的文件结构,有助于加深对 Java 语言的理解和程序的优化。特别是深入了解之后,可以从原理上理解 Java 语言的很多底层的技术。
针对上一次利用 ASM 修改字节码的内容,以下的内容可能更难理解一些,也需要一些虚拟机字节码方面的知识。

Java 编译后的 .class 文件主要分为以下五个部分(个人理解):
1、魔数和版本号信息
2、常量池信息,包括池中常量的数量和每个常量的描述
3、类信息,包括本身类、父类和接口描述
4、类中声明的属性和方法信息,包括属性和方法的数量和描述,方法中还包括实际执行的虚拟机字节码
5、类本身属性的信息,例如 SourceFile 属性显示类的源文件名称

以下面的 Java 源文件为例,按照上面五个部分说明一下编译后的 .class 文件的格式。
public class PrintString {
	
	private String str = "";
	
	public void setString(String s) {
		this.str = s;
	}
	
	public String getString() {
		return this.str;
	}
	
    public void print(){  
    		System.out.println(getString());
    }
    
}  

注意:以下使用的数字如无特别说明均为 十六进制。
第一部分占用 8 个字节:
00000000h: CA FE BA BE 00 00 00 32                         ; 漱壕...2

前面 4 个字节是魔数,所有 .class 文件都是一样的,为了虚拟机更方便的识别是否为类文件
后面 4 个字节分别是主版本号和次版本号,每个版本 JDK 的编译出的类会有所不同

第二部分占用的字节数取决的常量池的数量和常量的类型:
00000008h: 00 29 07 00 02 01 00 10 74 65 73 74 2F 50 72 69 ; .)......test/Pri
00000018h: 6E 74 53 74 72 69 6E 67 07 00 04 01 00 10 6A 61 ; ntString......ja
00000028h: 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 ; va/lang/Object..
00000038h: 03 73 74 72 01 00 12 4C 6A 61 76 61 2F 6C 61 6E ; .str...Ljava/lan
00000048h: 67 2F 53 74 72 69 6E 67 3B 01 00 06 3C 69 6E 69 ; g/String;...<ini
00000058h: 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 0A ; t>...()V...Code.
00000068h: 00 03 00 0B 0C 00 07 00 08 08 00 0D 01 00 00 09 ; ................
00000078h: 00 01 00 0F 0C 00 05 00 06 01 00 0F 4C 69 6E 65 ; ............Line
00000088h: 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F ; NumberTable...Lo
00000098h: 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 ; calVariableTable
000000a8h: 01 00 04 74 68 69 73 01 00 12 4C 74 65 73 74 2F ; ...this...Ltest/
000000b8h: 50 72 69 6E 74 53 74 72 69 6E 67 3B 01 00 09 73 ; PrintString;...s
000000c8h: 65 74 53 74 72 69 6E 67 01 00 15 28 4C 6A 61 76 ; etString...(Ljav
000000d8h: 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 ; a/lang/String;)V
000000e8h: 01 00 01 73 01 00 09 67 65 74 53 74 72 69 6E 67 ; ...s...getString
000000f8h: 01 00 14 28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F ; ...()Ljava/lang/
00000108h: 53 74 72 69 6E 67 3B 01 00 05 70 72 69 6E 74 09 ; String;...print.
00000118h: 00 1B 00 1D 07 00 1C 01 00 10 6A 61 76 61 2F 6C ; ..........java/l
00000128h: 61 6E 67 2F 53 79 73 74 65 6D 0C 00 1E 00 1F 01 ; ang/System......
00000138h: 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F 69 6F ; ..out...Ljava/io
00000148h: 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 0A 00 01 ; /PrintStream;...
00000158h: 00 21 0C 00 17 00 18 0A 00 23 00 25 07 00 24 01 ; .!.......#.%..$.
00000168h: 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 ; ..java/io/PrintS
00000178h: 74 72 65 61 6D 0C 00 26 00 15 01 00 07 70 72 69 ; tream..&.....pri
00000188h: 6E 74 6C 6E 01 00 0A 53 6F 75 72 63 65 46 69 6C ; ntln...SourceFil
00000198h: 65 01 00 10 50 72 69 6E 74 53 74 72 69 6E 67 2E ; e...PrintString.
000001a8h: 6A 61 76 61                                     ; java

前面两个字节 0029 表示常量池的大小 40 个,常量池的内容会被后面所有部分使用到。
第1个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=0002,查看 2 个常量
第2个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0010 说明占用后面的 16 个字节。内容为 test/PrintString
第3个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=0004,查看 4 号常量
第4个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0010 说明占用后面的 16 个字节。内容为 java/lang/Object
第5个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0003 说明占用后面的 3 个字节。内容为 str
第6个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0012 说明占用后面的 18 个字节。内容为 Ljava/lang/String;
第7个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0006 说明占用后面的 6 个字节。内容为 <init>
第8个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0003 说明占用后面的 4 个字节。内容为 ()V
第9个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0004 说明占用后面的 4 个字节。内容为 Code
第10个常量 0A 是一个 constant_methodref 类型,后面的 4 个字节属于它,class_index=0003,name_and_type_index=000B。内容为 java/lang/Object 的 init 方法
第11个常量 0C 是一个 constant_nameAndType 类型,后面的 4 个字节属于它,name_index=0007,descriptor_index=0008,内容为<init>()V
第12个常量 08 是一个 constant_String  类型,后面的 2 个字节属于它,000D 查看13号常量池
第13个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 00 说明占用后面的 0 个字节。内容为空
第14个常量 09 是一个 constant_Fieldref 类型,后面的 4 个字节表示它占用的字节数,class_index=0001 , name_and_type_index =000F。内容为 test/PrintString 的 str
第15个常量 0C 是一个 constant_nameAndType 类型,后面的 4 个字节属于它,name_index=0005,descriptor_index=0006,。内容为 Ljava/lang/String; str
第16个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,000F 说明占用后面的 15 个字节。内容为 LineNumberTable
第17个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0012 说明占用后面的 18个字节。内容为 LocalVariableTable
第18个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0004 说明占用后面的 4个字节。内容为 this
第19个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0012 说明占用后面的 18个字节。内容为 Ltest/PrintString;
第20个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0009 说明占用后面的 9个字节。内容为 setString;)
第21个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0015 说明占用后面的 21个字节。内容为 (Ljava/lang/String;)V
第22个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0001 说明占用后面的 1个字节。内容为 s
第23个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0009 说明占用后面的 9个字节。内容为 getString
第24个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0014 说明占用后面的 20个字节。内容为 ()Ljava/lang/String;
第25个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0005 说明占用后面的 20个字节。内容为 print
第26个常量 09 是一个 constant_Fieldref  类型,根据它的定义后面四个字节属于它,class_index=001B  查看27号常量, name_and_type_index =  001D 查看29号常量
第27个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=001C,查看28号常量
第28个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0010 说明占用后面的 16个字节。内容为 java/lang/System
第29个常量 0C 是一个 constant_nameAndType 类型,根据它的定义后面的4个字节属于它,name_index=001E 查看30号常量,descriptor_index=001F 查看31号常量
第30个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 03 说明占用后面的 3个字节。内容为 out
第31个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 15 说明占用后面的 21个字节。内容为Ljava/io/PrintStream;
第32个常量 0A 是一个 constant_methodref 类型,后面的 4 个字节属于它,class_index=0001,name_and_type_index=0021
第33个常量 0C 是一个 constant_nameAndType 类型,根据它的定义后面的4个字节属于它,name_index=0017 查看23号常量,descriptor_index=0018 查看24号常量
第34个常量 0A 是一个 constant_methodref 类型,后面的 4 个字节属于它,class_index=0023 查看35号常量,name_and_type_index=0025 查看37号常量
第35个常量 07 是一个 constant_Class 类型,后面的 2 个字节属于它,name_index=00 24,查看36号常量
第36个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 13 说明占用后面的 19个字节。内容为 java/io/PrintStream
第37个常量 0C 是一个 constant_nameAndType 类型,根据它的定义后面的4个字节属于它,name_index=0026 查看38号常量,descriptor_index=0015 查看21号常量
第38个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,0007 说明占用后面的 7个字节。内容为 println
第39个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,000A 说明占用后面的 10个字节。内容为 SourceFile
第40个常量 01 是一个 constant_UTF8 类型,后面的 2 个字节表示它占用的字节数,00 10 说明占用后面的 16个字节。内容为 PrintString.java

第三部分占用 8 个字节:
000001ach: 00 21 00 01 00 03 00 00                         ; .!......

前面 2 个字节 0021 表示类本身的修饰符
中间 2 个字节 0001 表示类本身的常量表索引,内容为 test/PrintString
下面 2 个字节 0003 表示父类的常量表索引,内容为 java/lang/Object
最后 2 个字节 0000 表示类实现的接口数量,此类未实现接口

第四部分是类文件是比较复杂的部分,它分为两个部分:属性区和方法区。
先看属性区:
000001b4h: 00 01 00 02 00 05 00 06 00 00                   ; ..........

前面 2 个字节 0001 表示类声明了一个属性
下面 2 个字节 0002 表示后面 2 个字节是属性的描述
下面 2 个字节 0005 表示属性名称的常量池索引,5号常量的信息 str
下面 2 个字节 0006 表示属性描述的常量池索引,6号常量的信息 Ljava/lang/String;
最后 2 个字节 0000 表示属性本身的属性的数量

再看方法区:
000001beh: 00 04 00 01 00 07 00 08 00 01 00 09 00 00 00 3D ; ...............=
000001ceh: 00 02 00 01 00 00 00 0B 2A B7 00 0A 2A 12 0C B5 ; ........*?.*..?
000001deh: 00 0E B1 00 00 00 02 00 10 00 00 00 0E 00 03 00 ; ..?............
000001eeh: 00 00 03 00 04 00 05 00 0A 00 03 00 11 00 00 00 ; ................
000001feh: 0C 00 01 00 00 00 0B 00 12 00 13 00 00 
// 此处为第一个方法和第二个方法的交界
                                                                                                  00 01 00 ; ................
0000020eh: 14 00 15 00 01 00 09 00 00 00 3E 00 02 00 02 00 ; ..........>.....
0000021eh: 00 00 06 2A 2B B5 00 0E B1 00 00 00 02 00 10 00 ; ...*+?.?......
0000022eh: 00 00 0A 00 02 00 00 00 08 00 05 00 09 00 11 00 ; ................
0000023eh: 00 00 16 00 02 00 00 00 06 00 12 00 13 00 00 00 ; ................
0000024eh: 00 00 06 00 16 00 06 00 01 00 01 00 17 00 18 00 ; ................
0000025eh: 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A ; ....../........*
0000026eh: B4 00 0E B0 00 00 00 02 00 10 00 00 00 06 00 01 ; ?.?...........
0000027eh: 00 00 00 0C 00 11 00 00 00 0C 00 01 00 00 00 05 ; ................
0000028eh: 00 12 00 13 00 00 00 01 00 19 00 08 00 01 00 09 ; ................
0000029eh: 00 00 00 39 00 02 00 01 00 00 00 0B B2 00 1A 2A ; ...9........?.*
000002aeh: B6 00 20 B6 00 22 B1 00 00 00 02 00 10 00 00 00 ; ? ?"?........
000002beh: 0A 00 02 00 00 00 10 00 0A 00 11 00 11 00 00 00 ; ................
000002ceh: 0C 00 01 00 00 00 0B 00 12 00 13 00 00          ; .............

前面 2 个字节 0004 表示类声明四个方法(注意:包含一个隐含的构造方法 init)
下面 2 个字节 0001 表示是一个ACC_PUBLIC 的方法
下面 2 个字节 0007 表示 name_index,表示常量池中第7个常量为<init>,
下面 2 个字节 0008 表示desciptor_index,表示常量池第8个常量为 ()V
下面 2 个字节 0001 表示attribute_count,表示有1个attribute
下面 2 个字节 0009 表示code_attribute,查看第9号常量池为Code
下面4 个字节 0000003D 表示后面的 61 个字节属于这个属性
下面 2 个字节 0002 表示 max_stack 该方法执行的时候操作数栈最大的长度,这里表示操作数栈的长度为1
下面 2 个字节 0001 表示 max_locals 该方法局部变量所需要的空间的长度
下面 4 个字节 00 00 00 0B 表示code_length 即后面的 11 个字节为虚拟机字节码内容
代码的 11 个字节:2A B7 00 0A 2A 12 0C B5 00 0E B1
2A :aload_0 表示将第一个引用类型本地变量推送至栈顶
B7 :invokespecial   调用超类方法,后面的 000A,查看10号常量池 表示调用超类的init方法
2A :aload_0 表示将第一个引用类型本地变量推送至栈顶
12  :ldc 表示将一个常量池压入操作栈,后面的 0C 便是这个操作数,查看第13号常量池,为空
B5 :putfield 将操作栈的值赋给类变量,后面2个字节 000E 代表类变量的常量池索引,为 Ljava/lang/String; str
B1 : return ;返回void
这里我们可以知道类变量的初始化位置。
代码的 11 个字节以后
下面 2 个字节 0000 表示exception_table_length=0;也就是说没有异常处理
下面 2 个字节 0002 表示attributes_count=2,接下来有两个attribute_info 的结构
下面 2 个字节 0010 表示  attribute_name_index,查看10号常量池,为 LineNumberTable
下面 4 个字节 00 00 00 0E 表示attribute_length=14
下面 14 个字节是 LineNumberTable 的属性
下面 2 个字节 0011 表示  attribute_name_index,查看10号常量池,为 LocalVariableTable
下面 4 个字节 00 00 00 0C 表示attribute_length=12
下面 12 个字节是 LocalVariableTable 的属性
以上只是第一个方法的说明,后面的方法不再详细说明。

第五部分通常不是很重要,大概了解一下:
000002dbh: 00 01 00 27 00 00 00 02 00 28                   ; ...'.....(

前面 2 个字节 0001 表示接下去有一个 attributes_info 结构,该结构占用 8 个字节
下面 2 个字节 0027 表示属性的名称的常量池索引,39 号常量的信息(属性的名称)为 SourceFile
下面 4 个字节 00000002 表示属性的长度,因为格式固定,所长度为 2
最后 2 个字节 0028 表示 sourcefile_index 的常量池索引, 40 号常量的信息(源文件的名称)为 PrintString.java

以上只是对一个结构简单的类作了一个简要说明,实际情况会复杂很多。详细内容大家可以参考《深入 Java 虚拟机(第二版)》


0
0
分享到:
评论

相关推荐

    JAVA命令大全.pdf

    5. javap:这个工具用于反编译.class文件,查看Java类文件的内部结构。它可以显示类的方法、字段等信息。例如,使用命令`javap -l MyClass`可以查看类MyClass的详细信息。 6. jdb.exe:Java调试器,用于调试Java...

    将jar包转成.java的源码的工具

    标题中的“将jar包转成.java的源码的工具”是指一种可以反编译Java字节码(.class文件)并将其转换为源代码(.java文件)的软件工具。在Java开发中,有时我们需要查看或理解已编译的jar包内部的工作原理,这种工具就...

    java的class文件反编译

    Java的Class文件是Java源代码经过编译器处理后的二进制表示,它包含了程序的结构和指令,但不直接可读。理解如何反编译Class文件对于学习、调试和分析Java程序至关重要。本文将深入探讨Java Class文件反编译的相关...

    JAVA上课笔记class_18

    根据提供的文件信息,我们可以整理出以下关键知识点,主要聚焦于Java编程语言的基础学习与实践,以及构建工具Ant的使用。 ### Java基础知识 #### 单元测试基础 在Java开发过程中,单元测试是确保代码质量的重要...

    Java OO 试题.doc

    1. **字节码**: Java源代码(.java文件)通过编译器编译成字节码(.class文件),这是一种中间语言,独立于特定平台,可以在任何支持Java的平台上运行,由Java虚拟机(JVM)解释执行。 2. **NoClassDefFoundError**: 这个...

    JAVA反编译工具

    Java反编译工具是开发人员用来查看已编译的Java字节码(.class文件)源代码的软件。这些工具对于理解第三方库的工作原理、学习API的内部实现、调试问题或者在没有源代码的情况下进行逆向工程至关重要。本文将详细...

    JAVA语言编程机制及实现研究.pdf

    在配置过程中可能会出现错误,如“javac”不是内部或外部命令,也不是可运行的程序或批处理文件,这是因为 path 的路径配置不正确。 二、JAVA 程序的运行机制和运行过程 JAVA 程序是通过编写、编译、运行三个步骤...

    java面试题

    53. 描述一下JVM加载class文件的原理机制? 30 54. socket编程 30 54.1. 什么是TCP/IP、UDP? 30 54.2. Socket在哪里呢? 31 54.3. Socket是什么呢? 32 54.4. socket的实现步骤 37 55. Servlet 38 55.1. Servlet工作...

    java 编码规范文档

    ### Java编码规范文档知识点 #### 1. 概述 - **内容**:本文档主要涵盖了Java编程语言的编码规范,包括命名规则、注释规范、排版规范以及数据库相关的命名与格式化等内容。 - **编写目的**:制定一套统一的编码规范...

    JC-java编程工具

    Java的Javac编译器负责这一过程,将.java文件转化为.class文件。 3. **调试工具**:调试是软件开发中的重要环节。JC工具可能提供断点设置、单步执行、变量查看、调用堆栈分析等功能,帮助开发者找出和修复代码中的...

    Java面试宝典-经典

    75、描述一下JVM加载class文件的原理机制? 52 76、heap和stack有什么区别。 52 77、GC是什么? 为什么要有GC? 52 78、垃圾回收的优点和原理。并考虑2种回收机制。 52 79、垃圾回收器的基本原理是什么?垃圾回收器可以...

    java面试题大全(2012版)

    75、描述一下JVM加载class文件的原理机制? 52 76、heap和stack有什么区别。 52 77、GC是什么? 为什么要有GC? 52 78、垃圾回收的优点和原理。并考虑2种回收机制。 52 79、垃圾回收器的基本原理是什么?垃圾回收器可以...

    Java关键字分类解释

    - **class**: 在Java中,`class`关键字用于声明一个类。类是面向对象编程的基本单元,它定义了一组属性(成员变量)和方法(成员函数),这些共同构成了一个特定类型的对象。例如: ```java public class Person ...

    最新Java面试宝典pdf版

    75、描述一下JVM加载class文件的原理机制? 52 76、heap和stack有什么区别。 52 77、GC是什么? 为什么要有GC? 52 78、垃圾回收的优点和原理。并考虑2种回收机制。 52 79、垃圾回收器的基本原理是什么?垃圾回收器可以...

    java面试宝典2012

    75、描述一下JVM加载class文件的原理机制? 56 76、heap和stack有什么区别。 57 77、GC是什么? 为什么要有GC? 57 78、垃圾回收的优点和原理。并考虑2种回收机制。 57 79、垃圾回收器的基本原理是什么?垃圾回收器可以...

    JAVA面试宝典2010

    75、描述一下JVM加载class文件的原理机制? 52 76、heap和stack有什么区别。 52 77、GC是什么? 为什么要有GC? 52 78、垃圾回收的优点和原理。并考虑2种回收机制。 52 79、垃圾回收器的基本原理是什么?垃圾回收器可以...

    Java面试宝典2012新版

    75、描述一下JVM加载class文件的原理机制? 52 76、heap和stack有什么区别。 52 77、GC是什么? 为什么要有GC? 52 78、垃圾回收的优点和原理。并考虑2种回收机制。 52 79、垃圾回收器的基本原理是什么?垃圾回收器可以...

    Java面试宝典2012版

    75、描述一下JVM加载class文件的原理机制? 52 76、heap和stack有什么区别。 52 77、GC是什么? 为什么要有GC? 52 78、垃圾回收的优点和原理。并考虑2种回收机制。 52 79、垃圾回收器的基本原理是什么?垃圾回收器...

Global site tag (gtag.js) - Google Analytics