JVM(6):类文件结构

概述:JVM可以理解的代码叫 字节码(.class文件),它不面向任何特定处理器只面向虚拟机。字节码并不针对一种特定机器,所以Java程序无需重新编译就可在不同操作系统运行(跨平台)

一、Class类文件结构

根据JVM规范,类文件由单个ClassFile结构组成:

ClassFile {
    u4             magic; //Class 文件的标志
    u2             minor_version;//Class 的小版本号
    u2             major_version;//Class 的大版本号
    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//Class 的访问标记
    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口
    u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有个字段
    u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法
    u2             attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合
}

 Class文件字节码结构组织示意图

类文件字节码结构组织示意图

1.1、魔数与Class文件的版本(前4个字节)

u4             magic; //Class 文件的标志

(1)什么是魔数:每个Class文件的头四个字节称为魔数(Magic Number)

(2)作用:确定这个文件是否为一个能被虚拟机接收的Class文件

1.2、 Class 文件版本(4-8字节)

  u2             minor_version;//Class 的小版本号
  u2             major_version;//Class 的大版本号

紧接着魔数的四个字节是Class文件的版本号:第五个和第六个是次版本号,第七和第八是主版本号

低版本虚拟机可以执行低版本编译器生成的Class文件,反之不行。

1.3、常量池

 u2             constant_pool_count;//常量池的数量
 cp_info        constant_pool[constant_pool_count-1];//常量池

常量池主要存放两大常量:

(1)字面常量:字符串、生命为final的常量值。

(2)符号常量:类和接口全限定名、字段名称和描述符、方法名称和描述符

常量池计数器:计数器从1开始计数的,0空闲。

常量池表:常量池中每一项常量都是一个表(14种),表的开始第一位是一个u1类型的标志位-tag来标识常量的类型,代表当前这个常量属于哪种常量类型

1.4、访问标志(2字节)

作用:用于识别一些类或者接口层次的访问信息,比如:这个Class是类还是接口、是否为public或者abstract类型,如果是类的话是否声明为final等。

1.5、当前类索引、父类索引、接口索引集合

    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个雷可以实现多个接口
  • 类索引作用:   用于确定这个类的全限定名;
  • 父类索引作用:   用于确定这个类的父类的全限定名,因为Java单继承,所以父类索引只有一个。除了 java.lang.Object 之外,所有的 java 类都有父类;
  • 接口索引集合作用:  用来描述这个类实现了哪些接口,这些被实现的接口将按implents后的接口顺序从左到右排列在接口索引集合中。

1.6、字段表集合

    u2             fields_count;//Class 文件的字段的个数
    field_info     fields[fields_count];//一个类会可以有个字段

作用:用于描述接口或者类中声明的变量(字段包括林级变量以及实例变量,不包括方法内部声明的局部变量)

字段表的结构

1.7、方法表集合

    u2             methods_count;//Class 文件的方法的数量
    method_info    methods[methods_count];//一个类可以有个多个方法

(methods_count 表示方法的数量,而 method_info 表示的方法表)

方法表的结构

方发表结构:包括了访问标志、名称索引、描述符索引、属性表集合

1.8、属性表集合

   u2             attributes_count;//此类的属性表中的属性数
   attribute_info attributes[attributes_count];//属性表集合

作用:在Class文件,字段表,方法表中都可以携带自己的属性表集合,用于描述某些场景的专有信息

二、字节码技术

掌握字节码技术可以自研框架,我有看过我们公司自研的框架源码(据说是几个华为跳槽来养老的人写的),他们主要是封装最近流行的SpringBoot框架,以及集成了很多流行技术。框架里边大量用到了字节码技术,还有各种设计模式,所以感觉掌握字节码技术是架构师必备技能!

2.1、字节码技术简介

字节码技术应用场景:AOP技术、Lombok去除重复代码插件、动态修改Class文件等。

字节码技术优势Java字节码增强指的是在Java字节码生成之后,对其进行修改,增强其功能。这种方式相当于对应用程序的二进制文件进行修改,可以减少代码冗余、提高性能。

实现字节码增强主要步骤

(1)修改字节码:

在内存中获取到原来的字节码,然后通过一些工具(如:ASM、Javaasist)来修改它的byte[]数组,得到一个新的byte数组;

(2)使修改后的字节码生效(2种方法):

  1. 自定义ClassLoader来加载修改后的字节码;
  2. 替换掉原来的字节码:在JVM加载用户的Class时,拦截,返回修改后的字节码;或者在运行时,使用Instrumentation.redefineClasses方法来替换掉原来的字节码

2.2、常见的字节码操作类库

(1)BCEL

BCEL是Java classworking 广泛使用的一种框架,它可以让您深入jvm汇编语言进行类库操作的细节。BCEL与javassist有不同的处理字节码方法,BCEL在实际的jvm指令层次上进行操作(BCEL拥有丰富的jvm指令集支持) 而javassist所强调的是源代码级别的工作。

(2)ASM

是一个轻量级Java字节码操作框架,直接涉及到JVM底层的操作和指令。高性能、高质量。

(3)CGLB

生成类库,基于ASM实现

(4)javassist

开源的分析、编辑、创建Java字节码的类库。性能较ASM差跟CGLB差不多,使用简单,比较流行

2.3、Javassist类库使用

(1)Javassist优势:

  • 比反射开销小,性能高;
  • 运行时操作字节码可以让我们实现如下功能:动态生成 新的类、动态改变某个类的结构 ( 添加 / 删除 / 修改    新的属性 / 方法 )。

(2)使用Javassist创建类 代码实现

	public static void main(String[] args)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException,
			SecurityException, IllegalArgumentException, InvocationTargetException {
		Class<?> clazz = Class.forName("com.itmayiedu.Test0005");
		Object newInstance = clazz.newInstance();
		Method method = clazz.getDeclaredMethod("sum", int.class, int.class);
		Object invoke = method.invoke(newInstance, 1, 1);
	}

	public void sum(int a, int b) {
		System.out.println("sum:" + a + b);
	}

	public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
		ClassPool pool = ClassPool.getDefault();
		// 创建class文件
		CtClass userClass = pool.makeClass("com.itmayiedu.entity.User");
		// 创建id属性
		CtField idField = CtField.make("private Integer id;", userClass);
		// 创建name属性
		CtField nameField = CtField.make("private Integer name;", userClass);
		// 添加属性
		userClass.addField(idField);
		// 添加属性
		userClass.addField(nameField);
		// 创建方法
		CtMethod getIdMethod = CtMethod.make("public Integer getId() {return id;}", userClass);
		// 创建方法
		CtMethod setIdMethod = CtMethod.make("public void setId(Integer id) { this.id = id; }", userClass);
		// 添加方法
		userClass.addMethod(getIdMethod);
		// 添加方法
		userClass.addMethod(setIdMethod);
		// 添加构造器
		CtConstructor ctConstructor = new CtConstructor(new CtClass[] { CtClass.intType, pool.get("java.lang.String") },
				userClass);
		// 创建Body
		ctConstructor.setBody("	{this.id = id;this.name = name;}");
		userClass.addConstructor(ctConstructor);
		userClass.writeFile("F:/test");// 将构造好的类写入到F:\test 目录下
	}

(2)使用Javassist修改类文件信息

public static void main(String[] args)
			throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException,
			NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, IOException {
		ClassPool pool = ClassPool.getDefault();
		// 需要加载类信息
		CtClass userClass = pool.get("com.itmayiedu.User");
		// 需要添加的方法
		CtMethod m = new CtMethod(CtClass.intType, "add", new CtClass[] { CtClass.intType, CtClass.intType },
				userClass);
		// 方法权限
		m.setModifiers(Modifier.PUBLIC);
		// 方法体内容
		m.setBody("{System.out.println(\"Test003\"); return $1+$2;}");
		userClass.addMethod(m);
		userClass.writeFile("F:/test");// 将构造好的类写入到F:\test 目录下
		// 使用反射技术执行方法
		Class clazz = userClass.toClass();
		Object obj = clazz.newInstance(); // 通过调用User 无参构造函数
		Method method = clazz.getDeclaredMethod("add", int.class, int.class);
		Object result = method.invoke(obj, 200, 300);
		System.out.println(result);
	}

 

 2.4、字节码指令简介

待更新.................

### 若对你有帮助的话,欢迎点赞!评论!转发!谢谢!

上一篇:编译器与运行期代码优化

下一篇:虚拟机类加载机制

  参考资料:深入理解Java虚拟机(第2版) : JVM高级特性与最佳实

发布了52 篇原创文章 · 获赞 116 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/RuiKe1400360107/article/details/103525058