概述: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种方法):
- 自定义ClassLoader来加载修改后的字节码;
- 替换掉原来的字节码:在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、字节码指令简介
待更新.................
### 若对你有帮助的话,欢迎点赞!评论!转发!谢谢!
上一篇:编译器与运行期代码优化
下一篇:虚拟机类加载机制