这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
简述
主要讲解从哪个路径加载class文件装换成二进制文件。
类加载器
启动类加载器: 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