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

2019/05/02

案例简述

本章节主要了解Java虚拟机从哪里寻找class文件并且读取class内字节码

环境准备

  1. jdk 1.8.0
  2. IntelliJ IDEA Community Edition 2018.3.1 x64
  3. Notepad++ (插件安装HEX-Editor,用于查看class字节)

配置信息

  1. 调试配置
    1. 配置位置:Run/Debug Configurations -> program arguments
    2. 配置内容:-Xjre “C:\Program Files\Java\jdk1.8.0_161\jre” E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-02\target\test-classes\org\itstack\demo\test\HelloWorld

代码示例

itstack-demo-jvm-02
├── pom.xml
└── src
    └── main
        └── java
            └── org.itstack.demo.jvm
                 ├── classpath
                    ├── impl
                       ├── CompositeEntry.java
                       ├── DirEntry.java 
                       ├── WildcardEntry.java 
                       └── ZipEntry.java    
                    ├── Classpath.java
                    └── Entry.java    
                 ├── Cmd.java
                 └── Main.java
    └── test
         └── java
             └── org.itstack.demo.test
                 └── HelloWorld.java

pom.xml

<!-- 命令行参数解析器 -->
<dependency>
  <groupId>com.beust</groupId>
  <artifactId>jcommander</artifactId>
  <version>1.72</version>
</dependency>

CompositeEntry.java

package org.itstack.demo.jvm.classpath.impl;

import org.itstack.demo.jvm.classpath.Entry;

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

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 */
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(Entry.create(path));
        }
    }

    @Override
    public byte[] readClass(String className) throws IOException {
        for (Entry entry : entryList) {
            try {
                return entry.readClass(className);
            } catch (Exception ignored) {
                //ignored
            }
        }
        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);
    }
    
}

DirEntry.java

package org.itstack.demo.jvm.classpath.impl;

import org.itstack.demo.jvm.classpath.Entry;

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

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 * 目录形式的类路径
 */
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();
    }
}

WildcardEntry.java

package org.itstack.demo.jvm.classpath.impl;

import org.itstack.demo.jvm.classpath.Entry;

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;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 * 通配符类路径,继承CompositeEntry
 */
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 "";
        }
    }

}

ZipEntry.java

package org.itstack.demo.jvm.classpath.impl;

import org.itstack.demo.jvm.classpath.Entry;

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

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 * zip/zar文件形式类路径
 */
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();
    }

}

Classpath.java

package org.itstack.demo.jvm.classpath;

import org.itstack.demo.jvm.classpath.impl.WildcardEntry;

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

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 * 类路径
 */
public class Classpath {

    private Entry bootstrapClasspath;  //启动类路径
    private Entry extensionClasspath;  //扩展类路径
    private Entry userClasspath;       //用户类路径

    public Classpath(String jreOption, String cpOption) {
        //启动类&扩展类 "C:\Program Files\Java\jdk1.8.0_161\jre"
        bootstrapAndExtensionClasspath(jreOption);
        //用户类 E:\..\org\itstack\demo\test\HelloWorld
        parseUserClasspath(cpOption);
    }

    private void bootstrapAndExtensionClasspath(String jreOption) {
        
        String jreDir = getJreDir(jreOption);

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

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

    }

    private static String getJreDir(String jreOption) {
        if (jreOption != null && Files.exists(Paths.get(jreOption))) {
            return jreOption;
        }
        if (Files.exists(Paths.get("./jre"))) {
            return "./jre";
        }
        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) {
        if (cpOption == null) {
            cpOption = ".";
        }
        userClasspath = Entry.create(cpOption);
    }

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

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

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

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

}

Entry.java

package org.itstack.demo.jvm.classpath;

import org.itstack.demo.jvm.classpath.impl.CompositeEntry;
import org.itstack.demo.jvm.classpath.impl.DirEntry;
import org.itstack.demo.jvm.classpath.impl.WildcardEntry;
import org.itstack.demo.jvm.classpath.impl.ZipEntry;

import java.io.File;
import java.io.IOException;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 * 类路径接口
 */
public interface Entry {

    byte[] readClass(String className) throws IOException;

    static Entry create(String path) {

        //File.pathSeparator;路径分隔符(win\linux)
        if (path.contains(File.pathSeparator)) {
            return new CompositeEntry(path);
        }

        if (path.endsWith("*")) {
            return new WildcardEntry(path);
        }

        if (path.endsWith(".jar") || path.endsWith(".JAR") ||
                path.endsWith(".zip") || path.endsWith(".ZIP")) {
            return new ZipEntry(path);
        }

        return new DirEntry(path);
    }

}

Cmd.java

package org.itstack.demo.jvm;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;

import java.util.List;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 */
public class Cmd {

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

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

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

    @Parameter(names = "-Xjre", description = "path to jre", order = 4)
    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 args = new Cmd();
        JCommander cmd = JCommander.newBuilder().addObject(args).build();
        cmd.parse(argv);
        args.ok = true;
        return args;
    }
    
}

Main.java

package org.itstack.demo.jvm;

import org.itstack.demo.jvm.classpath.Classpath;

import java.util.Arrays;

/**
 * http://www.itstack.org
 * create by fuzhengwei on 2019/4/24
 * program arguments:-Xjre "C:\Program Files\Java\jdk1.8.0_161\jre" E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-02\target\test-classes\org\itstack\demo\test\HelloWorld
 */
public class Main {

    public static void main(String[] args) {
        Cmd cmd = Cmd.parse(args);
        if (!cmd.ok || cmd.helpFlag) {
            System.out.println("Usage: <main class> [-options] class [args...]");
            return;
        }
        if (cmd.versionFlag) {
            //注意案例测试都是基于1.8,另外jdk1.9以后使用模块化没有rt.jar
            System.out.println("java version \"1.8.0\"");
            return;
        }
        startJVM(cmd);
    }

    private static void startJVM(Cmd cmd) {
        Classpath cp = new Classpath(cmd.jre, cmd.classpath);
        System.out.printf("classpath:%s class:%s args:%s\n", cp, cmd.getMainClass(), cmd.getAppArgs());
        //获取className
        String className = cmd.getMainClass().replace(".", "/");
        try {
            byte[] classData = cp.readClass(className);
            System.out.println("classData:");
            for (byte b : classData) {
                //16进制输出
                System.out.print(String.format("%02x", b & 0xff) + " ");
            }
        } catch (Exception e) {
            System.out.println("Could not find or load main class " + cmd.getMainClass());
            e.printStackTrace();
        }
    }

}

测试结果

classpathorg.itstack.demo.jvm.classpath.Classpath@4bf558aa classE:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-02\target\test-classes\org\itstack\demo\test\HelloWorld argsnull
classData
ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09 
00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07 
00 1b 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 22 4c 6f 72 67 2f 69 74 
73 74 61 63 6b 2f 64 65 6d 6f 2f 74 65 73 74 2f 
48 65 6c 6c 6f 57 6f 72 6c 64 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 53 6f 75 72 63 
65 46 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 
6c 64 2e 6a 61 76 61 0c 00 07 00 08 07 00 1c 0c 
00 1d 00 1e 01 00 0d 48 65 6c 6c 6f 2c 20 77 6f 
72 6c 64 21 07 00 1f 0c 00 20 00 21 01 00 20 6f 
72 67 2f 69 74 73 74 61 63 6b 2f 64 65 6d 6f 2f 
74 65 73 74 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 
00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 
63 74 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 03 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 01 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 06 00 08 00 07 00 0b 00 00 00 0c 00 01 00 
00 00 09 00 10 00 11 00 00 00 01 00 12 00 00 00 
02 00 13 

结果验证 Notepad++ 打开HelloWorld.class,在’插件’工具中选HEX-Editor设置为View in Hex 默认为8-bit


上一篇:用Java实现JVM第一章《命令行工具》

下一篇:用Java实现JVM第三章《解析class文件》

微信搜索「bugstack虫洞栈」公众号,关注后回复「用Java实现jvm源码」获取本文源码&更多原创专题案例!

(转载本站文章请注明作者和出处 微信公众号:bugstack虫洞栈 | 作者:付政委

Show Disqus Comments

Post Directory

扫码或搜索:bugstack虫洞栈
发送 290992
即可立即永久解锁本站全部文章