Java代码审计学习笔记之JAVA基础系列(一、类的动态加载)

0.Java程序是如何运行的

Java是一款跨平台的开发语言,如下图所示,Java程序在运行时经历了编译->解释->运行这三大过程。编译过程中使用的也就是javac,而解释和运行则依赖于JVM(Java虚拟机)。正是因为有了不同平台下的JVM,因而java程序才能够跨平台运行。

.java文件被编译成.class文件后,JVM负责对Class文件进行解释和运行。.class文件中的内容是一种介于用户语言和机器语言之间的被称为Java bytecode(Java字节码)的内容。编写如下LearnJVM.java文件:

public class LearnJVM {
    public static void main(String argvs[]){
        System.out.println("Hello JVM!");
    }
}

使用javac工具对上述代码进行编译便可生成包含Java字节码的class文件。JDK中提供了反汇编器javap,javap产生的结果是Java汇编语言。

Java汇编指令由操作码(OpCode)和操作数(Operand)组成,JVM在执行的其实就是上面生成的这些java字节码。JVM执行class文件的过程如下图所示:

如上图所示,JVM中由CLass Loader负责将class文件读入内存中,并为运行前进行相应的处理。CLass Loader完成的工作主要包含以下三步:

  • Loading :读取class文件并加载到JVM的内存中
  • Linking:链接过程包含以下三个子步骤
    • Verifying :检查读取的class文件是否符合规范
    • Preparing :准备数据结构用于存储类信息
    • Resolving :将类的常量替换为直接引用
  • Initialization :执行类的初始化程序

ClassLoader类定义于java.lang.ClassLoader中,java中类初始化时调用该类并返回一个java.lang.Class实例。主要包含如下核心方法:

  • loadClass:加载指定的Java类
  • findClass:查找指定的Java类
  • findLoadedClass:查找JVM已经加载过的类
  • defineClass:定义一个Java类
  • resolveClass:链接指定的Java类

2.java类的动态加载

java开发的时候如果需要使用某个类可以对类进行动态加载,需要使用某个类就加载。类似C语言开发过程中动态加载dll的loadLibrary方法。常用的LoadLibrary方法主要包含以下两种:

假如要加载的类为LearnJVM.class,其源码如下:

public class LearnJVM {
    public static void SayHi(){
        System.out.println("Hello Loader!");
    }
    public static void main(String argvs[]){
        System.out.println("Hello JVM!");
    }
}

则我们可以使用如下两种方法动态加载该类,并实例化该类,从而调用其内部方法。

1.反射加载

//反射加载
public class ClassLoaderTest {
    public static void main(String argvs[]) throws ClassNotFoundException{
        ClassLoaderTest myClassLoaderTest = new ClassLoaderTest();
        Class myTest = Class.forName("LearnJVM");
        LearnJVM l = new LearnJVM();
        l.SayHi();
    }
}

2.ClassLoader加载

public class ClassLoaderTest {
    public static void main(String argvs[]) throws ClassNotFoundException{
        ClassLoaderTest myClassLoaderTest = new ClassLoaderTest();
        Class myTest = myClassLoaderTest.getClass().getClassLoader().loadClass("LearnJVM");
        LearnJVM l = new LearnJVM();
        l.SayHi();
    }
}

3.自定义ClassLoader

如下图所示ClassLoader有很多,不同的ClassLoader负责加载不同的类。并且每一个ClassLoader都有一个父加载器不是父类)。

类加载器加载类的时候是通过委托模式进行的,如下图所示:

具体ClassLoader的加载过程可以参考一看你就懂,超详细java中的ClassLoader详解一文。总之一个核心思想就是不同的加载器有不同的加载路径,并且我们可以自己实现一个自定义的加载器从而实现任意路径的Class加载。自定义加载器代码如下:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


public class ClassLoaderTest extends ClassLoader {
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = name;
        if (fileName == "/tmp/LearnJVM.class") {
            try {
                File file = new File(fileName);
                FileInputStream is = new FileInputStream(file);

                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int len = 0;
                try {
                    while ((len = is.read()) != -1) {
                        bos.write(len);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                byte[] data = bos.toByteArray();
                is.close();
                bos.close();

                return defineClass("LearnJVM", data, 0, data.length);//这里写了个硬编码的name

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            return super.findClass(name);
        }


        return super.findClass(name);
    }
    public static void main(String argvs[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        ClassLoaderTest myClassLoaderTest = new ClassLoaderTest();
        Class myTest = myClassLoaderTest.loadClass("/tmp/LearnJVM.class");
        System.out.println(myTest);
        LearnJVM l = new LearnJVM();
        l.SayHi();

    }
}
执行结果:
javac ClassLoaderTest.java
java ClassLoaderTest


class LearnJVM
Hi Loader!

参考