前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写Java类解析器-01.class文件的基本结构

手写Java类解析器-01.class文件的基本结构

作者头像
付威
发布2020-04-16 17:10:47
5770
发布2020-04-16 17:10:47
举报

java的运行过程

在运行一段java代码的时候需要经过编译,验证,加载运行,具体如下图:

类加载过程
类加载过程

这个系列的文章是为了探讨Java字节码是什么样的结构,如何能够准确的表达我们代码的含义。

为了探讨我们的源代码和Java字节码的关系,我们先写一段代码,尽量多用上java的关键字和特殊的方法,以便我们测试和对比:

代码语言:javascript
复制
package org.rz;
public class AppMain {
	private String userName;
	private int age;
	private short sex;
	private long userCode;
	private double income;
	private float ablity;
	private final static String userId="000000001";
	private final long indentityCardNo=34122202019029L;
	
	public String getUserName() {
		return userName;
	}
	
	public void setUserName(String userName) {
		this.userName = userName;
	}
	
	public int getAge() {
		return age;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public short getSex() {
		return sex;
	}
	
	public void setSex(short sex) {
		this.sex = sex;
	}
	
	public long getUserCode() {
		return userCode;
	}
	
	public void setUserCode(long userCode) {
		this.userCode = userCode;
	}
	
	public double getIncome() {
		return income;
	}
	
	public float getAblity() {
		return ablity;
	}
	
	public void setAblity(float ablity) {
		this.ablity = ablity;
	}
	
	public static String getUserId() {
		return userId;
	}
	
	public long getIndentityCardNo() {
		return indentityCardNo;
	}
	public String getUserInfo() {
		return "AppMain{" + "userName='" + userName + '\'' + ", age=" + age + ", sex=" + sex + ", userCode=" + userCode + ", income=" + income + ", ablity=" + ablity + ", indentityCardNo=" + indentityCardNo + '}';
	}
	
	private static AppMain appmain;
	public static AppMain getInstance() {
		synchronized (AppMain.class) {
			if (appmain == null) {
				appmain = new AppMain();
			}
			return appmain;
		}
	}
	
	public synchronized boolean changeName(String name) {
		if (StringUtils.isEmpty(name)) {
			return false;
		}
		if (StringUtils.isEmpty(this.getUserName())) {
			this.setUserName(name);
		}
		return true;
	}
	
	private void testMain(){
		System.out.println("init ok");
	}
}

编译后得到AppMain.class文件,用文本工具打开后,结果如下图:

AppClass文件
AppClass文件

对于这个文本,我们可以尝试使用JDK中的类加载工具加载看下效果。


JDK中如何解析class文件

在原生的JDK中有对java字节码的读取的工具类com.sun.tools,具体使用如下:

代码语言:javascript
复制
File file=new File("/Users/fuwei/work/rzframe/rz-lib/src/test/resources/AppMain.class");
try {
    ClassFile read = ClassFile.read(file);
    System.out.println(read.toString());
} catch (IOException e) {
    e.printStackTrace();
} catch (ConstantPoolException e) {
    e.printStackTrace();
}

加载结果调试监控如下:

类加载过程
类加载过程

class文件的基本结构

根据JVM的虚拟机规范(SE8)提供的资料,字节码对应的结构体如下:

代码语言:javascript
复制
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];
}

其中u2u4分别代表占用的字节,u2代表占用两个字节,u4代表占用两个字节 对应的结构图如下:

class文件结构
class文件结构

在我们了解了class的结构之后,就可以开始试着解析class文件。

解析过程

  1. 读取类文件 private DataInputStream dataInputStream; public ClassReadCursor(String filePath) { try { byte[] bytes = Files.readAllBytes(Paths.get(filePath)); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); this.dataInputStream = new DataInputStream(new ByteArrayInputStream(byteBuffer.array())); } catch (IOException e) { e.printStackTrace(); } } 在上面的操作中,我们把类的文件成功的转换成流文件DataInputStream,我们是类文件是按照顺序读取的,所以可以定义的游标的对象cursor来读取,对cursor可以封装几个读取的方法: public void readFully(byte[] byteArr) throws IOException { this.dataInputStream.readFully(byteArr); } public int readUnsignedByte() throws IOException { return this.dataInputStream.readUnsignedByte(); } public int readUnsignedShort() throws IOException { return this.dataInputStream.readUnsignedShort(); } public int readInt() throws IOException { return this.dataInputStream.readInt(); } public long readLong() throws IOException { return this.dataInputStream.readLong(); } public float readFloat() throws IOException { return this.dataInputStream.readFloat(); } public double readDouble() throws IOException { return this.dataInputStream.readDouble(); } public String readUTF() throws IOException { return this.dataInputStream.readUTF(); } }

  1. 获得魔数 Java中的魔数是一个固定的值cafe babe,一共占用4个字节,我们可以通过简单的方式把魔数取出来: byte[] byteArr=new byte[4]; cursor.readFully(byteArr); System.out.println(StringUtils.binaryToHexString(byteArr)); 对应16进制转字符的方法: public static String binaryToHexString(byte[] bytes) { String hexStr = "0123456789ABCDEF"; StringBuilder result = new StringBuilder(); String hex = ""; for (int i = 0; i < bytes.length; i++) { //字节高4位 hex = String.valueOf(hexStr.charAt((bytes[i] & 0xF0) >> 4)); //字节低4位 hex += String.valueOf(hexStr.charAt(bytes[i] & 0x0F)); result.append(hex); } return result.toString(); }

  1. 获得jdk版本 minor_version = cursor.readUnsignedShort(); major_version = cursor.readUnsignedShort(); 得到的结果分别是: minor_version=0, major_version=52
class文件结构
class文件结构

根据版本号插叙对应的jdk的版本可以看出,笔者的jdk的版本是1.8

(本文完)

作者:付威 博客地址:http://blog.laofu.online 如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作 如有任何知识产权、版权问题或理论错误,还请指正。 本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • java的运行过程
  • JDK中如何解析class文件
  • class文件的基本结构
  • 解析过程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档