用Java实现JVM第二章《搜索class文件》

222 阅读8分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

简述

image-20210814220459635.png

主要讲解从哪个路径加载class文件装换成二进制文件。

类加载器

java_jvm_classload_3.png

启动类加载器: Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

扩展类加载器: Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

应用程序类加载器: Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派机制

1.当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2.当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3.如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4.若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

优势

系统类防止内存中出现多份同样的字节码

保证Java程序安全稳定运行

代码示例

Entry接口

import java.io.IOException;

/**
 * @Author blackcat
 * @create 2021/8/11 14:23
 * @version: 1.0
 * @description:读取class的接口
 */
public interface Entry {
    //class文件的相对路径,比如要读取java.lang.Object类,传入的参数应该是java/lang/Object.class
    byte[] readClass(String className) throws IOException;
}

DirEntry

import com.black.cat.jvm.classpath.Entry;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @Author blackcat
 * @create 2021/8/11 14:46
 * @version: 1.0
 * @description:绝对路径(目录形式)
 */
public class DirEntry implements Entry {

    private Path absolutePath;

    public DirEntry(String path){
        //获取绝对路径
        this.absolutePath = Paths.get(path).toAbsolutePath();
    }

    @Override
    public byte[] readClass(String className) throws IOException {
        return Files.readAllBytes(absolutePath.resolve(className));
    }

    @Override
    public String toString() {
        return this.absolutePath.toString();
    }

}

ZipEntry

import com.black.cat.jvm.classpath.Entry;

import java.io.IOException;
import java.nio.file.*;

/**
 * @Author blackcat
 * @create 2021/8/11 14:52
 * @version: 1.0
 * @description:zip/zar、jar文件形式类路径
 */
public class ZipEntry implements Entry {

    private Path absolutePath;

    public ZipEntry(String path) {
        //获取绝对路径
        this.absolutePath = Paths.get(path).toAbsolutePath();
    }

    @Override
    public byte[] readClass(String className) throws IOException {
        try (FileSystem zipFs = FileSystems.newFileSystem(absolutePath, null)) {
            return Files.readAllBytes(zipFs.getPath(className));
        }
    }

    @Override
    public String toString() {
        return this.absolutePath.toString();
    }
}


CompositeEntry

import com.black.cat.jvm.classpath.Entry;
import com.black.cat.jvm.classpath.EntryFactory;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author blackcat
 * @create 2021/8/11 14:54
 * @version: 1.0
 * @description:组合
 */
@Slf4j
public class CompositeEntry implements Entry {

    private final List<Entry> entryList = new ArrayList<>();


    public CompositeEntry(String pathList) {
        String[] paths = pathList.split(File.pathSeparator);
        for (String path : paths) {
            entryList.add(EntryFactory.create(path));
        }
    }


    @Override
    public byte[] readClass(String className) throws IOException {
        for (Entry entry : entryList) {
            try {
                return entry.readClass(className);
            } catch (Exception ignored) {
                //ignored
                log.info(entry.toString());
            }
        }
        throw new IOException("class not found " + className);
    }

    @Override
    public String toString() {
        String[] strs = new String[entryList.size()];
        for (int i = 0; i < entryList.size(); i++) {
            strs[i] = entryList.get(i).toString();
        }
        return String.join(File.pathSeparator, strs);
    }
}

WildcardEntry

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;

/**
 * @Author blackcat
 * @create 2021/8/11 14:55
 * @version: 1.0
 * @description:通配符
 */
public class WildcardEntry extends CompositeEntry {

    public WildcardEntry(String path) {
        super(toPathList(path));
    }

    private static String toPathList(String wildcardPath) {
        String baseDir = wildcardPath.replace("*", ""); // remove *
        try {
            return Files.walk(Paths.get(baseDir))
                    .filter(Files::isRegularFile)
                    .map(Path::toString)
                    .filter(p -> p.endsWith(".jar") || p.endsWith(".JAR"))
                    .collect(Collectors.joining(File.pathSeparator));
        } catch (IOException e) {
            return "";
        }
    }
}

EntryFactory

import com.black.cat.jvm.classpath.impl.CompositeEntry;
import com.black.cat.jvm.classpath.impl.DirEntry;
import com.black.cat.jvm.classpath.impl.WildcardEntry;
import com.black.cat.jvm.classpath.impl.ZipEntry;

import java.io.File;

/**
 * @Author blackcat
 * @create 2021/8/11 14:29
 * @version: 1.0
 * @description:Entry工厂
 */
public class EntryFactory {

    public static Entry create(String path) {
        //组合多种形式      UNIX下<code>':'</code>,WIN 下<code>';'</code>
        if (path.contains(File.pathSeparator)) {
            return new CompositeEntry(path);
        }

        //通配符
        if (path.endsWith("*")) {
            return new WildcardEntry(path);
        }

        //jar或zip
        if (path.endsWith(".jar") || path.endsWith(".JAR") ||
                path.endsWith(".zip") || path.endsWith(".ZIP")) {
            return new ZipEntry(path);
        }
        //目录形式
        return new DirEntry(path);
    }
}

Classpath

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * @Author blackcat
 * @create 2021/8/11 14:23
 * @version: 1.0
 * @description:类路径加载
 */
@Slf4j
public class Classpath {

    private Entry bootstrapClasspath;  //启动类路径

    private Entry extensionClasspath;  //扩展类路径

    private Entry userClasspath;       //用户类路径

    public Classpath(String jreOption, String cpOption) {
        //启动类&扩展类
        bootstrapAndExtensionClasspath(jreOption);
        //用户类
        parseUserClasspath(cpOption);
    }

    private void bootstrapAndExtensionClasspath(String jreOption) {
        //获取jre 目录
        String jreDir = getJreDir(jreOption);

        //..jre/lib/*
        String jreLibPath = Paths.get(jreDir, "lib") + File.separator + "*";
        bootstrapClasspath = EntryFactory.create(jreLibPath);

        //..jre/lib/ext/*
        String jreExtPath = Paths.get(jreDir, "lib", "ext") + File.separator + "*";
        extensionClasspath = EntryFactory.create(jreExtPath);

    }

    private static String getJreDir(String jreOption) {
        //优先使用用户-Xjre的参数
        if (jreOption != null && Files.exists(Paths.get(jreOption))) {
            return jreOption;
        }
        //如果没有输入-Xjre的参数,则在当前目录下寻找jre
        if (Files.exists(Paths.get("./jre"))) {
            return "./jre";
        }
        //还是找不到,使用JAVA_HOME环境变量
        String jh = System.getenv("JAVA_HOME");
        if (jh != null) {
            return Paths.get(jh, "jre").toString();
        }
        throw new RuntimeException("Can not find JRE folder!");
    }


    private void parseUserClasspath(String cpOption) {
        //如果没有配置"-cp", "-classpath" 则把当前目录作为用户目录路径
        if (cpOption == null) {
            cpOption = ".";
        }
        userClasspath = EntryFactory.create(cpOption);
    }

    public byte[] readClass(String className) throws Exception {
        className = className + ".class";

        //[readClass]启动类路径
        try {
            return bootstrapClasspath.readClass(className);
        } catch (Exception ignored) {
            //ignored
            log.info("bootstrapClasspath ignore");
        }

        //[readClass]扩展类路径
        try {
            return extensionClasspath.readClass(className);
        } catch (Exception ignored) {
            //ignored
            log.info("extensionClasspath ignore");
        }

        //[readClass]用户类路径
        return userClasspath.readClass(className);
    }
}

测试

/**
 * @Author blackcat
 * @create 2021/8/11 13:42
 * @version: 1.0
 * @description:命令行相关参数
 */
public class Cmd {

    @Parameter(names = {"-?", "-help"}, description = "print help message", help = true)
    boolean helpFlag = false;

    @Parameter(names = "-version", description = "print version and exit")
    boolean versionFlag = false;

    @Parameter(names = {"-cp", "-classpath"}, description = "classpath")
    String classpath;

    @Parameter(names = "-Xjre", description = "path to jre")
    String jre;

    @Parameter(description = "main class and args")
    List<String> mainClassAndArgs;

    boolean ok;

    String getMainClass() {
        return mainClassAndArgs != null && !mainClassAndArgs.isEmpty()
                ? mainClassAndArgs.get(0)
                : null;
    }

    List<String> getAppArgs() {
        return mainClassAndArgs != null && mainClassAndArgs.size() > 1
                ? mainClassAndArgs.subList(1, mainClassAndArgs.size())
                : null;
    }

    static Cmd parse(String[] argv) {
        Cmd cmd = new Cmd();
        try {
            JCommander.newBuilder().addObject(cmd).args(argv).build();
            cmd.ok = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cmd;
    }
}


**
 * @Author blackcat
 * @create 2021/8/11 13:43
 * @version: 1.0
 * @description:命令行工具
 */
public class Main {

    public static void main(String[] args) {
        String[] argv = {"-classpath","D:\\develop\\code\\jjvm\\jvm-02\\target\\classes","com.black.cat.jvm.MainTest"};
        Cmd cmd = Cmd.parse(argv);
        if (!cmd.ok || cmd.helpFlag) {
            System.out.println("Usage: <main class> [-options] class [args...]");
            return;
        }
        if (cmd.versionFlag) {
            System.out.println("java version \"1.8.0\"");
            return;
        }
        startJVM(cmd);
    }

    private static void startJVM(Cmd cmd) {
        System.out.printf("classpath:%s class:%s args:%s\n", cmd.classpath, cmd.getMainClass(), cmd.getAppArgs());

        Classpath classpath = new Classpath(null, cmd.classpath);
        try {
            //java.lang.Object
            String className = cmd.getMainClass().replace(".", "/");
            byte[] classData = classpath.readClass(className);
            System.out.println("classData:");
            for (byte b : classData) {
                //16进制输出
                System.out.print(String.format("%02x", b & 0xff) + " ");
            }
            System.out.println();
        } catch (Exception e) {
            System.out.println("Could not find or load main class ");
            e.printStackTrace();
        }
    }
}


public class MainTest {
    public static void main(String[] args) throws IOException {
        System.out.println("Hello World");
    }
}

结果

classData:
ca fe ba be 00 00 00 34 00 25 0a 00 06 00 16 09 00 17 00 18 08 00 19 0a 00 1a 00 1b 07 00 1c 07 00 1d 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 00 04 74 68 69 73 01 00 1c 4c 63 6f 6d 2f 62 6c 61 63 6b 2f 63 61 74 2f 6a 76 6d 2f 4d 61 69 6e 54 65 73 74 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 0a 45 78 63 65 70 74 69 6f 6e 73 07 00 1e 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0d 4d 61 69 6e 54 65 73 74 2e 6a 61 76 61 0c 00 07 00 08 07 00 1f 0c 00 20 00 21 01 00 0b 48 65 6c 6c 6f 20 57 6f 72 6c 64 07 00 22 0c 00 23 00 24 01 00 1a 63 6f 6d 2f 62 6c 61 63 6b 2f 63 61 74 2f 6a 76 6d 2f 4d 61 69 6e 54 65 73 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 01 00 13 6a 61 76 61 2f 69 6f 2f 49 4f 45 78 63 65 70 74 69 6f 6e 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00 0b 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00 0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 17 00 08 00 1f 00 0b 00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00 00 12 00 00 00 04 00 01 00 13 00 01 00 14 00 00 00 02 00 15 

gitee地址

gitee.com/feicc/jjvm/…