最近看了周志明的深入java虚拟机,看到堆字节码解析这一章节,觉得特别的枯燥无味,于是我花了一段时间解析了一个class文件,由于方法体和属性这两项篇幅太长没做解析,想看这两个解析的略过。
class文件具有与语言无关性的特点,很多语言都可以编译成class文件,让jvm执行,class文件结构如下:
1.魔数
2.版本
3.常量池
4.访问符
5.类、超类、接口
6.字段
7.方法
8.属性
class文件的组成元素如下表:
类型 |
名称 |
数量 |
u4 |
magic |
1 |
u2 |
minor_version |
1 |
u2 |
major_version |
1 |
u2 |
constant_pool_count |
1 |
cp_info |
constant_pool |
constant_pool_count - 1 |
u2 |
access_flags |
1 |
u2 |
this_class |
1 |
u2 |
super_class |
1 |
u2 |
interfaces_count |
1 |
u2 |
interfaces |
interfaces_count |
u2 |
fields_count |
1 |
field_info |
fields |
fields_count |
u2 |
methods_count |
1 |
method_info |
methods |
methods_count |
u2 |
attribute_count |
1 |
attribute_info |
attributes |
attributes_count |
1.常量池的结构
constant_pool_count u2
constant_pool cp_info
CONSTANT_Utf8 1 UTF-8编码的Unicode字符串
CONSTANT_Integer 3 int类型的字面值
CONSTANT_Float 4 float类型的字面值
CONSTANT_Long 5 long类型的字面值
CONSTANT_Double 6 double类型的字面值
CONSTANT_Class 7 对一个类或接口的符号引用
CONSTANT_String 8 String类型字面值的引用
CONSTANT_Fieldref 9 对一个字段的符号引用
CONSTANT_Methodref 10 对一个类中方法的符号引用
CONSTANT_InterfaceMethodref 11 对一个接口中方法的符号引用
CONSTANT_NameAndType 12 对一个字段或方法的部分符号引用
依次是常量池里对应的常量类型,编号,描述
字符常量,如一个类的名称,方法名称
CONSTANT_Utf8
tag 1
length u2
bytes[length]
整数常量
CONSTANT_Integer
tag 3
byte u4
String 类型的字面常量如:
public static final String name="xuehan";
CONSTANT_String
tag 8
string_index u2 (指向utf8的索引)
CONSTANT_NameAndType 对一个字段或方法的部分符号引用
tag 12
name_index u2 (名字,指向utf8)
descriptor_index u2 (描述符类型,指向utf8)
access flag u2:类的标示符
Flag NameValueInterpretation
ACC_PUBLIC0x0001public
ACC_FINAL0x0010final,不能被继承.
ACC_SUPER0x0020是否允许使用invokespecial指令,JDK1.2后,该值为true
ACC_INTERFACE0x0200是否是接口
ACC_ABSTRACT0x0400抽象类
ACC_SYNTHETIC0x1000该类不是由用户代码生成,运行时生成的,没有源码
ACC_ANNOTATION0x2000是否为注解
ACC_ENUM0x4000是否是枚举
this_class u2
指向常量池的Class
super_class u2
指向常量池的Class
interface_count u2
接口数量
interfaces
interface_count 个 interface u2
每个interface是指向CONSTANT_Class的索引
。。。。。以上还有很多我就不一一列出来了,下面解析一个class文件
需要解析的类
public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
解析的代码:
package com.jvm.day7; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import util.BytesUtils; /** * 分析clas文件 * @Author:xuehan * @Date:2016年3月27日上午10:26:54 */ public class ClassAnalyze { public void analyze(String filePath) throws Exception{ ByteBuffer buf = getBuffer(filePath); System.out.println("魔数==>" + BytesUtils.toHexString(getbytes(buf, 4))); buf = clearRead(buf, 4); System.out.println("版本号==>" + BytesUtils.toHexString(getbytes(buf, 4))); buf = clearRead(buf, 4); System.out.println("常量池大小==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); System.out.println("第一个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); // 读出第一个常量第一个index buf = clearRead(buf, 1); System.out.println("Methodref_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); // 读出第一个常量第二个index buf = clearRead(buf, 2); System.out.println("Methodref_info第二个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); // 读第二个常量 System.out.println("第二个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf, 1); System.out.println("fieldref_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); // 读出第一个常量第二个index buf = clearRead(buf, 2); System.out.println("fieldref_info第二个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); // 读第三个常量 System.out.println("第三个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf,1); System.out.println("class_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); // 读第四个常量 System.out.println("第四个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf,1); System.out.println("class_info第一个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); // 开始读取字符串常量 for(int i = 1; i < 13 ; i ++){ System.out.println("第" + i + "个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf,1); String hexStr = BytesUtils.toHexString(getbytes(buf, 2)); System.out.println("第" + i +" utf8_info length十六进制是==>" +hexStr); int len = HexToInteger(hexStr); System.out.println("第" + i +" utf8_info length十进制是==>" +len); buf = clearRead(buf, 2); System.out.println("第" + i + "utf8_info bytes是==>" + BytesUtils.toHexString(getbytes(buf, len)) + "字面常量是==>" + new String((getbytes(buf, len)))); buf = clearRead(buf, len); } System.out.println("已经读取16个常量,下面开始读取第17个常量#########################################"); // 读取第17个常量 System.out.println("第17个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf,1); System.out.println("class_info第1个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); System.out.println("class_info第2个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); // 读取第18个常量 System.out.println("第18个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf,1); System.out.println("class_info第1个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); System.out.println("class_info第2个index是==>" + BytesUtils.toHexString(getbytes(buf, 2))); buf = clearRead(buf, 2); // 读取第19和第20个常量 for(int i = 19; i < 21 ; i ++){ System.out.println("第" + i + "个常量的tag==>" + BytesUtils.toHexString(getbytes(buf, 1) )); buf = clearRead(buf,1); String hexStr = BytesUtils.toHexString(getbytes(buf, 2)); System.out.println("第" + i +" utf8_info length十六进制是==>" +hexStr); int len = HexToInteger(hexStr); System.out.println("第" + i +" utf8_info length十进制是==>" +len); buf = clearRead(buf, 2); System.out.println("第" + i + "utf8_info bytes是==>" + BytesUtils.toHexString(getbytes(buf, len)) + "字面常量是==>" + new String((getbytes(buf, len)))); buf = clearRead(buf, len); } // 常量池读取完毕读取acces_flags System.out.println("acces_flags==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取this_class System.out.println("this_class==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取super_class System.out.println("super_class==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取interfaces_cout,如果cout大于0就要继续读取interfaces System.out.println("interfaces_cout==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取fileds_count System.out.println("fileds_count==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取fileds acces_flags System.out.println("fileds acces_flags==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取fileds index System.out.println("fileds index==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取fileds descriptor index System.out.println("fileds descriptor index==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取attribute_count System.out.println("attribute_count==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); // 读取methods_count System.out.println("methods_count==>" + BytesUtils.toHexString(getbytes(buf, 2) )); buf = clearRead(buf,2); } public static void main(String[] args) throws Exception { ClassAnalyze ca = new ClassAnalyze(); ca.analyze("D:\\BaiduYunDownload\\data\\jvmtest\\User.class"); //ca.getBuffer("D:\\BaiduYunDownload\\data\\jvmtest\\User.class"); } public ByteBuffer getBuffer(String filePath) throws Exception{ RandomAccessFile raf = new RandomAccessFile(filePath, "rw"); FileChannel channel = raf.getChannel(); ByteBuffer bb = ByteBuffer.allocate(2048); channel.read(bb); raf.close(); byte[] bbs = new byte[4]; bb.get(bbs); return bb; } public byte[] getbytes(ByteBuffer bb, int len){ byte[] bytes =new byte[len]; for(int i =0; i < bytes.length; i ++){ bytes[i] = bb.get(i); } return bytes; } public int HexToInteger(String hexStr){ char[] chars = hexStr.toCharArray(); int secondInt = 0; int thirdInt = 0; if(chars[2] == 'a'){ secondInt = 10; }else if(chars[2] == 'b'){ secondInt = 11; }else if(chars[2] == 'c'){ secondInt = 12; }else if(chars[2] == 'd'){ secondInt = 13; }else if(chars[2] == 'e'){ secondInt = 14; }else if(chars[2] == 'f'){ secondInt = 15; }else{ secondInt = Integer.parseInt("" + chars[2]); } secondInt = 16 * secondInt; if(chars[3] == 'a'){ thirdInt = 10; }else if(chars[3] == 'b'){ thirdInt = 11; }else if(chars[3] == 'c'){ thirdInt = 12; }else if(chars[3] == 'd'){ thirdInt = 13; }else if(chars[3] == 'e'){ thirdInt = 14; }else if(chars[3] == 'f'){ thirdInt = 15; }else{ thirdInt = Integer.parseInt("" + chars[3]); } return thirdInt + secondInt; } public ByteBuffer clearRead(ByteBuffer bb, int len){ System.out.println("清理之前==>" + BytesUtils.toHexString(bb.array())); ByteBuffer bbnew = ByteBuffer.allocate(bb.capacity()); for(int i = len ; i < bb.capacity(); i ++){ bbnew.put(bb.get(i)); } System.out.println("清理完毕==>" + BytesUtils.toHexString(bbnew.array())); return bbnew; } }
魔数==>cafebabe 版本号==>00000033 常量池大小==>0015 第一个常量的tag==>0a Methodref_info第一个index是==>0004 Methodref_info第二个index是==>0011 第二个常量的tag==>09 fieldref_info第一个index是==>0003 fieldref_info第二个index是==>0012 第三个常量的tag==>07 class_info第一个index是==>0013 第四个常量的tag==>07 class_info第一个index是==>0014 第1个常量的tag==>01 第1 utf8_info length十六进制是==>0004 第1 utf8_info length十进制是==>4 第1utf8_info bytes是==>6e616d65字面常量是==>name 第2个常量的tag==>01 第2 utf8_info length十六进制是==>0012 第2 utf8_info length十进制是==>18 第2utf8_info bytes是==>4c6a6176612f6c616e672f537472696e673b字面常量是==>Ljava/lang/String; 第3个常量的tag==>01 第3 utf8_info length十六进制是==>0006 第3 utf8_info length十进制是==>6 第3utf8_info bytes是==>3c696e69743e字面常量是==><init> 第4个常量的tag==>01 第4 utf8_info length十六进制是==>0003 第4 utf8_info length十进制是==>3 第4utf8_info bytes是==>282956字面常量是==>()V 第5个常量的tag==>01 第5 utf8_info length十六进制是==>0004 第5 utf8_info length十进制是==>4 第5utf8_info bytes是==>436f6465字面常量是==>Code 第6个常量的tag==>01 第6 utf8_info length十六进制是==>000f 第6 utf8_info length十进制是==>15 第6utf8_info bytes是==>4c696e654e756d6265725461626c65字面常量是==>LineNumberTable 第7个常量的tag==>01 第7 utf8_info length十六进制是==>0007 第7 utf8_info length十进制是==>7 第7utf8_info bytes是==>6765744e616d65字面常量是==>getName 第8个常量的tag==>01 第8 utf8_info length十六进制是==>0014 第8 utf8_info length十进制是==>20 第8utf8_info bytes是==>28294c6a6176612f6c616e672f537472696e673b字面常量是==>()Ljava/lang/String; 第9个常量的tag==>01 第9 utf8_info length十六进制是==>0007 第9 utf8_info length十进制是==>7 第9utf8_info bytes是==>7365744e616d65字面常量是==>setName 第10个常量的tag==>01 第10 utf8_info length十六进制是==>0015 第10 utf8_info length十进制是==>21 第10utf8_info bytes是==>284c6a6176612f6c616e672f537472696e673b2956字面常量是==>(Ljava/lang/String;)V 第11个常量的tag==>01 第11 utf8_info length十六进制是==>000a 第11 utf8_info length十进制是==>10 第11utf8_info bytes是==>536f7572636546696c65字面常量是==>SourceFile 第12个常量的tag==>01 第12 utf8_info length十六进制是==>0009 第12 utf8_info length十进制是==>9 第12utf8_info bytes是==>557365722e6a617661字面常量是==>User.java 已经读取16个常量,下面开始读取第17个常量######################################### 第17个常量的tag==>0c class_info第1个index是==>0007 class_info第2个index是==>0008 第18个常量的tag==>0c class_info第1个index是==>0005 class_info第2个index是==>0006 第19个常量的tag==>01 第19 utf8_info length十六进制是==>0004 第19 utf8_info length十进制是==>4 第19utf8_info bytes是==>55736572字面常量是==>User 第20个常量的tag==>01 第20 utf8_info length十六进制是==>0010 第20 utf8_info length十进制是==>16 第20utf8_info bytes是==>6a6176612f6c616e672f4f626a656374字面常量是==>java/lang/Object acces_flags==>0021 this_class==>0003 super_class==>0004 interfaces_cout==>0000 fileds_count==>0001 fileds acces_flags==>0002 fileds index==>0005 fileds descriptor index==>0006 attribute_count==>0000 methods_count==>0003
完成的二进制编码:
相关推荐
解析class文件是JVM运行Java程序的第一步,这个过程涉及到许多底层机制。 1. **Class文件结构**:.class文件的结构遵循一种特定的二进制格式,包括魔数、版本信息、常量池、访问标志、类索引、父类索引、接口索引...
class文件是Java源代码经过JVM(Java Virtual Machine)编译后的二进制格式,它包含了类的结构、方法、变量等信息。本篇文章将详细探讨如何解析这些class文件,以及在Android开发中可能遇到的相关问题。 1. **Java...
- **类加载器**:负责加载.class文件,确保程序运行所需的类能够被找到并加载到内存中。 - **运行数据区**:包括堆、方法区、虚拟机栈、本地方法栈和程序计数器等几个部分。 - **堆**:存储对象实例,是所有线程...
【描述】"class文件反编译工具"的功能在于解析Java虚拟机(JVM)可执行的二进制类文件,将其转换回人类可读的Java源代码形式。这种技术在软件开发、错误排查、学习已有的开源项目或分析第三方库时非常有用。JD-GUI...
解析是 JVM 的第四步,负责将字节码文件转换成机器代码。 #### 10.5 执行 执行是 JVM 的第五步,负责执行机器代码。 ### 11. HotSpot 虚拟机对象探秘 HotSpot 虚拟机对象探秘是指 HotSpot 虚拟机如何创建和管理 ...
JVM使用类加载器加载`.class`文件,然后解析字节码并执行。这个过程包括加载、验证、准备、解析和初始化等步骤。一旦类加载完成,JVM就可以执行字节码,实现跨平台的“Write Once, Run Anywhere”(WORA)特性。 5...
Java Class文件解析是Java开发中的一个重要概念,它涉及到Java虚拟机(JVM)如何理解和执行程序的核心机制。这篇博文的链接虽然无法直接访问,但从标题和标签我们可以推测,内容可能涵盖了对Java字节码的深入理解和...
一方面,通过严格的结构定义,使得JVM可以准确无误地解析Class文件;另一方面,通过使用可变长度的数据结构(如常量池、字段表集合等),使得Class文件能够在保持紧凑的同时容纳更多的信息。 通过深入理解Class文件...
本文将深入探讨JVM类加载过程、JVM加载Class文件的原理以及Java内存分配。 1. **JVM类加载过程**: - **加载**:这是类加载的第一步,涉及三个关键操作。首先,通过类的全限定名获取二进制流;接着,将静态存储...
这些版本号用于确保JVM能够正确解析Class文件的内容,支持不同版本的Java语言特性。 3. **Constant Pool (常量池)** - 常量池从文件的第五个字节开始,由`constant_pool_count`和`constant_pool`两部分组成。 - ...
第8节Java的发展历史续00:02:27分钟 | 第9节Java技术体系00:08:46分钟 | 第10节jdk8的新特性00:07:31分钟 | 第11节lanmbda表达式简介00:07:02分钟 | 第12节Java虚拟机-classic vm00:06:06分钟 | 第13节Java...
1. **魔数(Magic Number)**:Class文件的第一部分是四个字节的魔数,用来标识文件类型。标准的Class文件的魔数为`CAFEBABE`。 2. **次要版本号(Minor Version)**和**主要版本号(Major Version)**:这两个两...
常量池对于解析class文件中的符号引用是必不可少的。 访问标志紧随常量池之后,它用来表示类或接口的访问权限,例如public、abstract、final等。这是对类级别访问控制的表示,直接告诉JVM类的特性。 紧接着访问...
2. **版本号**:包括次版本号和主版本号,用于确定JVM应该按照哪个版本的字节码规范来解析Class文件。 3. **常量池**:存储各种常量,如字符串、类和接口的引用、方法名和描述符等,是Class文件中占用空间最大的...
《自己动手写Java虚拟机及class文件解析分析工具(java8运行)》是一份深入探讨Java虚拟机(JVM)工作原理以及如何解析与分析Java类文件(.class)的资源。通过使用Go语言实现一个简化的JVM,这份资料旨在帮助读者...
它需要解析`.class`文件,提取类信息,然后构建出类的内部表示。一旦类加载完成,就可以执行类中的方法。在这个过程中,mini-jvm需要处理类的依赖关系,确保正确地初始化和加载类。 七、异常处理与垃圾回收 尽管...
- **第8项**: `CONSTANT_NameAndType_info`,表示方法名“print”和描述符“()Ljava/lang/String;”。 - **第9项**: `CONSTANT_Methodref_info`,表示父类`Object`中的构造方法“()V”。 - **第10项至23项**: ...
### JVM性能调优——JVM内存管理与GC回收详解 #### 概览 在现代软件开发领域,Java凭借其强大的跨平台能力和丰富的生态系统成为企业级应用的首选语言之一。然而,随着应用程序复杂度的提高以及业务需求的变化,...
第8讲 Java的发展历史续 00:02:27 第9讲 Java技术体系 00:08:46 第10讲 jdk8的新特性 00:07:31 第11讲 lanmbda表达式简介 00:07:02 第12讲 Java虚拟机-classic vm 00:06:06 第13讲 Java虚拟机-...
以下是对JVM部分关键配置参数的详细解析,这些参数能够帮助我们优化应用程序的执行效率,管理内存,以及进行性能调优。 #### 执行模式配置 1. **-Xmixed**: 混合模式执行(默认)。此参数指示JVM使用混合模式执行...