专栏首页bugstack虫洞栈用Java实现JVM第二章《搜索class文件》
原创

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

小傅哥 | https://bugstack.cn 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例、源码分析等。 你用剑🗡、我用刀🔪,好的代码都很烧,望你不吝出招!

一、案例简述

本章节主要了解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;

/**
 * https://bugstack.cn
 * 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;

/**
 * https://bugstack.cn
 * 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;

/**
 * https://bugstack.cn
 * 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.*;

/**
 * https://bugstack.cn
 * 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;

/**
 * https://bugstack.cn
 * 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;

/**
 * https://bugstack.cn
 * 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;

/**
 * https://bugstack.cn
 * 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;

/**
 * https://bugstack.cn
 * 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();
        }
    }

}

五、测试结果

classpath:org.itstack.demo.jvm.classpath.Classpath@4bf558aa class:E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-02\target\test-classes\org\itstack\demo\test\HelloWorld args:null
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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

    相对于小傅哥之前编写的字节码编程; ASM、Javassist 系列,Byte Buddy 玩法上更加高级,你可以完全不需要了解一个类和方法块是如何通过 指令码...

    小傅哥
  • 字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

    相对于小傅哥之前编写的字节码编程; ASM、Javassist 系列,Byte Buddy 玩法上更加高级,你可以完全不需要了解一个类和方法块是如何通过 指令码...

    小傅哥
  • 《Netty+JavaFx实战:仿桌面版微信聊天》代码开源、上云部署、视频讲解!

    视频:https://mp.weixin.qq.com/s/OmXCY4fTfDpkvjlg5ME0ZA

    小傅哥
  • 关于查询转换的一些简单分析(二) (r3笔记第68天)

    关于查询转换的作用,在之前的一篇博文里面也大概说了视图合并和子查询嵌套的部分。http://blog.itpub.net/23718752/viewspace-...

    jeanron100
  • 微信电脑版不断崩溃

    之前不知道为什么,突然发生微信不断崩溃的状况,更新微信没用尝试网上的各种方法都没用,不过索性最后解决了。想看解决方案的话看最后了,中间记录的是崩溃日志。

    @坤的
  • 为什么 FastJson 火不起来,国外人都不使用?

    首先抄录一段来自官网的介绍:FastJson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可...

    业余草
  • 使用Python-Requests实现ODL对OVS的流表下发

    目标 通过ODL,在OVS上添加如下的一个group # ovs-vsctl add-br br0 # ovs-vsctl set bridge br0 pr...

    SDNLAB
  • 原 使用pg_basebackup搭建PostgreSQL流复制环境

    王果壳
  • iOS直播(基础篇)-rtmpdefine NALU_TYPE_SLICE 1define NALU_TYPE_DPA 2define NALU_TYPE_DPB 3define NALU_TYPE_

    大壮
  • AkShare-宏观经济-中国LPR数据

    LPR数据对大家都有非常重大的影响,尤其是有房贷的各位小伙伴,特提供 LPR 数据接口,欢迎大家使用。

    AkShare

扫码关注云+社区

领取腾讯云代金券