JVM 字节码技术

简介: 本文是 JVM Class 文件介绍的延伸部分,简单地介绍一下字节码增强技术相关的内容

引言

本文是 JVM Class 文件介绍的延伸部分,简单地介绍一下字节码增强技术相关的内容,更多关于 JVM 的文章均收录于<JVM系列文章>

字节码技术

读到这,大家应该已经对 Class 文件字节码的结构有了比较清晰的理解。接下来,我们顺带着提一下字节码增强的相关技术。字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术,一般用来实现面向切面编程 AOP。要想使用字节码增强技术,可以选择的类库有很多。我们接下来将主要介绍一下 ASM 和 Javassist。
bytecode-dynamic-enhance

ASM

对于需要手动操纵字节码的需求,可以使用 ASM,它可以直接生产 .class 字节码文件,也可以在类被加载入 JVM 之前动态修改类行为。ASM 的应用场景有 AOP(Cglib 就是基于 ASM)、热部署、修改其他 jar 包中的类等。当然,涉及到如此底层的步骤,实现起来也比较麻烦。ASM 有两套 API,一套是基于访问者模式Core API。另一套 API 是 Tree API,它将字节码中的各个结构抽象成一个树形结构,通过 Tree API 可以直接修改该树形结构。

Core API 的一大特点是不需要把这个类的整个结构读取进来,就可以用流式的方法来处理字节码文件。好处是非常节约内存,但是编程难度较大。然而出于性能考虑,一般情况下编程都使用 Core API。
ams-core-api
在 Core API 中有以下几个关键类:

  • ClassReader:用于读取已经编译好的 .class 文件。
  • ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。
  • 各种 Visitor 类:如上所述,CoreAPI 根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的 Visitor,比如用于访问方法的 MethodVisitor、用于访问类变量的 FieldVisitor、用于访问注解的 AnnotationVisitor 等。为了实现 AOP,重点要使用的是 MethodVisitor。

Tree API 解析出来的树形结构非常类似于前面提到的 jclasslib 工具展示的结果。它把整个类的结构读取到内存中,缺点是消耗内存多,但是编程比较简单。TreeApi 不同于 CoreAPI,TreeAPI 通过各种 Node 类来映射字节码的各个区域。
jclasslib-demo
现在我们以 Core API 为例,介绍一下 ASM 的用法,假设我们要在如下 Test 类的基础上实现一个简单的 AOP。

package bbm.jvm;

public class Test {
    public void test() {
        System.out.println("test");
    }
}

这里我们先用 ClassReader 从 CLASS_PATH 中读取上述 Test 类的字节码,然后定义一个我们自己的 MyClassVisitor,它套在 ClassWriter 之上,只对 test 进行修改。在其函数前后分别增加一条语句。

package bbm.jvm;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

public class TestASM {
    public static void main(String[] args) throws IOException {
        ClassReader classReader = new ClassReader("bbm.jvm.Test");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        //处理
        ClassVisitor classVisitor = new MyClassVisitor(classWriter);
        classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
        byte[] data = classWriter.toByteArray();
        //输出
        File f = new File("/path/to/save/Test2.class");
        if (!f.exists()) {
            f.createNewFile();
        }
        FileOutputStream fout = new FileOutputStream(f);
        fout.write(data);
        fout.close();
    }

    public static class MyClassVisitor extends ClassVisitor implements Opcodes {
        public MyClassVisitor(ClassVisitor cv) {
            super(ASM5, cv);
        }
        @Override
        public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            //Test 类中有两个方法:无参构造以及 test 方法,这里不增强构造方法
            if (!name.equals("<init>") && mv != null) {
                mv = new MyMethodVisitor(mv);
            }
            return mv;
        }
        class MyMethodVisitor extends MethodVisitor implements Opcodes {
            public MyMethodVisitor(MethodVisitor mv) {
                super(Opcodes.ASM5, mv);
            }

            @Override
            public void visitCode() {
                super.visitCode();
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn("start");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            @Override
            public void visitInsn(int opcode) {
                if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
                    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                    mv.visitLdcInsn("end");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                }
                mv.visitInsn(opcode);
            }
        }
    }
}

执行成功后,就能在 /path/to/save/Test2.class 中找到 Test2 文件,该 Class 文件反编译成 Java 后内容如下。

package bbm.jvm;

public class Test {
    public Test() {
    }

    public void test() {
        System.out.println("start");
        System.out.println("test");
        System.out.println("end");
    }
}

Javassist

ASM 是在指令层次上操作字节码的,从前面的例子中,我们的直观感受是在指令层次上操作字节码的框架实现起来比较繁琐。故除此之外,我们再简单介绍另外一类框架:强调源代码层次操作字节码的框架 Javassist。

利用 Javassist 实现字节码增强时,可以无须关注字节码刻板的结构,其优点就在于编程简单。直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构或者动态生成类。其中最重要的是 ClassPool、CtClass、CtMethod、CtField 这四个类:

  • CtClass(compile-time class):编译时类信息,它是一个 class 文件在代码中的抽象表现形式,可以通过一个类的全限定名来获取一个 CtClass 对象,用来表示这个类文件。
  • ClassPool:从开发视角来看,ClassPool 是一张保存 CtClass 信息的 HashTable,key 为类名,value 为类名对应的 CtClass 对象。当我们需要对某个类进行修改时,就是通过 pool.getCtClass(“className”)方法从 pool 中获取到相应的 CtClass。
  • CtMethod、CtField:这两个比较好理解,对应的是类中的方法和属性。

接下来,我们用 Javassist 来实现一个简单的 AOP:

package bbm.jvm;

import java.io.IOException;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class TestJavassist {
    public static void main(String[] args) throws IOException, NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("bbm.jvm.Test");
        CtMethod m = cc.getDeclaredMethod("test");
        m.insertBefore("{ System.out.println(\"start\"); }");
        m.insertAfter("{ System.out.println(\"end\"); }");
        Class c = cc.toClass();
        cc.writeFile("/path/to/save/");
        Test h = (Test)c.newInstance();
        h.test();
    }
}

执行上述代码,能直接打印出嵌入 AOP 之后的结果:

start
test
end

文章说明

更多有价值的文章均收录于贝贝猫的文章目录

stun

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

创作声明: 本文基于下列所有参考内容进行创作,其中可能涉及复制、修改或者转换,图片均来自网络,如有侵权请联系我,我会第一时间进行删除。

参考内容

[1]《实战 Java 虚拟机》
[2]《深入理解 Java 虚拟机》
[3] GC复制算法和标记-压缩算法的疑问
[4] Java中什么样的对象才能作为gc root,gc roots有哪些呢?
[5] concurrent-mark-and-sweep
[6] 关于 -XX:+CMSScavengeBeforeRemark,是否违背cms的设计初衷?
[7] Java Hotspot G1 GC的一些关键技术
[8] Java 垃圾回收权威指北
[9] [[HotSpot VM] 请教G1算法的原理](https://hllvm-group.iteye.com/group/topic/44381)
[10] [[HotSpot VM] 关于incremental update与SATB的一点理解](https://hllvm-group.iteye.com/group/topic/44529)
[11] Java线程的6种状态及切换
[12] Java 8 动态类型语言Lambda表达式实现原理分析
[13] Java 8 Lambda 揭秘
[14] 字节码增强技术探索
[15] 不可不说的Java“锁”事
[16] 死磕Synchronized底层实现--概论
[17] 死磕Synchronized底层实现--偏向锁
[18] 死磕Synchronized底层实现--轻量级锁
[19] 死磕Synchronized底层实现--重量级锁

相关文章
|
7月前
|
安全 Java
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
44 0
|
6月前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
107 1
|
2月前
|
安全 Java API
🌟探索Java宇宙:深入理解Java技术体系与JVM的奥秘
本文深入探讨了Java技术体系的全貌,从Java语言的概述到其优点,再到Java技术体系的构成,以及JVM的角色。旨在帮助Java开发者全面了解Java生态,提升对Java技术的认知,从而在编程实践中更好地发挥Java的优势。关键词:Java, JVM, 技术体系, 编程语言, 跨平台, 内存管理。
44 2
|
6月前
|
存储 算法 Java
技术笔记:JVM的垃圾回收机制总结(垃圾收集、回收算法、垃圾回收器)
技术笔记:JVM的垃圾回收机制总结(垃圾收集、回收算法、垃圾回收器)
62 1
|
6月前
|
存储 Java 编译器
JVM系列7-虚拟机字节码执行引擎
JVM系列7-虚拟机字节码执行引擎
36 1
|
5月前
|
监控 Java 调度
探索JVM性能调优,调优不仅是技术挑战,更是成长过程。
【7月更文挑战第1天】探索JVM性能调优:** 本文深入JVM内存模型,关注堆内存与方法区、栈的优化,通过调整-Xms, -Xmx及垃圾收集器参数减少GC频率。探讨了Serial到G1等垃圾收集器的选择策略,利用jstat、jmap等工具诊断性能瓶颈。实战案例中,通过问题定位、内存分析解决Full GC问题,强调开发者需理解JVM原理,运用工具在复杂场景下实现高效调优。调优不仅是技术挑战,更是成长过程。
47 0
|
6月前
|
存储 缓存 算法
详解JVM内存优化技术:压缩指针
详解JVM内存优化技术:压缩指针
|
6月前
|
存储 缓存 监控
深入解析JVM内存分配优化技术:TLAB
深入解析JVM内存分配优化技术:TLAB
|
7月前
|
Java 索引
【JVM】字节码文件的组成部分
【JVM】字节码文件的组成部分
60 1
|
7月前
|
存储 Arthas Java
【JVM系列笔记】字节码
本文介绍了Java虚拟机(JVM)的组成,包括类加载子系统、运行时数据区、执行引擎和本地接口。字节码文件由基础信息(如魔数和版本号)、常量池、字段、方法和属性组成。常量池用于存储字符串等共享信息,方法区则包含字节码指令。执行引擎包含解释器、即时编译器和垃圾回收器,负责字节码的解释和优化。文章还提到了字节码工具,如javap、jclasslib和Arthas,用于查看和分析字节码。
91 0
【JVM系列笔记】字节码