`
ezerg
  • 浏览: 276350 次
  • 性别: 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编程语言的编码规范,包括命名规则、注释规范、排版规范以及数据库相关的命名与格式化等内容。 - **编写目的**:制定一套统一的编码规范...

    基于Java语言的openapi-java-sdk-v2设计源码,适用于外部下游对接

    在文件结构方面,该SDK包含了一个.gitignore文件,该文件用于定义不希望被Git版本控制系统跟踪的文件和目录,如临时文件、编译产生的.class文件等,确保项目的代码库保持整洁。此外,LICENSE文件表明了该SDK遵循的...

    JC-java编程工具

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

    基于Springboot框架的Java开发校园论坛系统设计源码

    该项目的文件列表中还包含了readme.txt文件,这是一个说明文档,通常用于向用户或开发者提供项目的简要介绍、安装步骤、使用说明和可能遇到的问题及解决方案等信息。readme文件对于了解和使用项目至关重要,它能够...

    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、垃圾回收器的基本原理是什么?垃圾回收器可以...

    基于Springboot框架的Java Server Pages宠物系统设计源码

    由于源码包中还包含了诸如adopt.iml和.target等文件,可以推断出项目是使用IntelliJ IDEA这样的集成开发环境进行开发的,.iml文件用于IDE内部项目配置,而.target文件则可能用于构建输出的配置。在开发和部署宠物...

    java面试宝典2012

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

Global site tag (gtag.js) - Google Analytics