前言:
现在你们对“0x1c语言”大致比较注意,大家都想要剖析一些“0x1c语言”的相关文章。那么小编在网络上收集了一些对于“0x1c语言””的相关文章,希望我们能喜欢,同学们快快来了解一下吧!Java虚拟机非常复杂,要想真正理解它的工作原理,最好的方式就是自己动手写一个。但是这个系列的定位还是入门,所以有很多的东西暂时都不会实现,比如 malloc,GC,多线程,native interface等。主要的目的还是在于大致了解JVM是一个什么样的东西,这也是我采用java来实现JVM的一个原因,便于理解。
由于我之前也实现过一个简单的可以执行汇编的模拟器,写这个对我来说不难,但是可能出现有些地方我认为理所当然,而刚接触的读者很难理解的情况。如果出现了,欢迎大家在原博客中评论。
下面是原始博客的地址:
该项目的地址:
项目的完成度现在在90%左右了,运行一些简单的类是没有问题的,后面会慢慢扩展。
该系列的文章会对项目的代码进行详细的介绍。这个系列大致会分为下面几个部分:
实现 classpath,暂时只兼容 windows,有兴趣参与进来的可以提pr实现 class 文件的解析实现运行时数据区实现解释器,解释 method 中的指令实现类与对象的支持实现方法调用实现数组实现异常
该项目并不会去实现对象分配与内存管理,有兴趣的可以看看 csapp 的 malloc lab。
项目的参考资料:
《自己动手写Java虚拟机》,作者使用go语言实现。官方文档其他类似的开源项目等虚拟机介绍
看上图,类加载子系统将 class 文件加载到内存中,这个过程需要完成2件事:
找到 class 文件的路径将 class 文件按照格式解析
这篇文章,主要是完成第1件事,寻找 class 文件。
类路径
Java虚拟机规范并没有规定虚拟机应该从哪里寻找类,所以我们可以自由一点。但是回想一下,java 加载类的时候,它通过双亲委派机制来加载类,所以类路径至少有3个:
启动类路径(bootstrap classpath),路径默认对应jre\lib目录扩展类路径(extension classpath),路径默认对应jre\lib\ext目录用户类路径(user classpath),路径的默认值是当前目录
在 windows 上,这些路径都可以通过 JAVA_HOME 拿到。
搜索类
有了类路径,我们就可以在这些路径下面搜索指定的类了。搜索会发生以下几种情况:
class 文件是一个单独的文件,在当前目录下class 文件是一个单独的文件,在当前目录的子目录里面class 文件在压缩文件里面
根据这些情况,我们使用组合模式来写代码,看以下组合模式的类图:
我们仿照这个类图,首先定义一个接口:
public interface Entry { // className: fully/qualified/ClassName.class // for example: write/your/own/jvm/classpath/Entry.class byte[] readClass(String fullyQualifiedClassName) throws Exception;}
这里的参数格式需要注意,是全限定类名的形式。
DirEntry
我们先实现最简单的搜索情况.
public class DirEntry implements Entry { // directory absolute path which your target class file in // 在 Solaris OS 中,Path 使用 Solaris 语法(/home/joe/foo) // 在 Microsoft Windows 中,Path 使用 Windows 语法(C:\home\joe\foo)。 private final Path absDirPath; public DirEntry(String classpath) { absDirPath = Paths.get(classpath).toAbsolutePath(); } @Override public byte[] readClass(String fullyQualifiedClassName) throws Exception { return Files.readAllBytes(absDirPath.resolve(fullyQualifiedClassName)); }}
由于类文件是在当前路径下,所以我们直接读取文件流就好了。
ZipEntry
public class ZipEntry implements Entry { // zip file's absolute path // 在 Solaris OS 中,Path 使用 Solaris 语法(/home/joe/foo) // 在 Microsoft Windows 中,Path 使用 Windows 语法(C:\home\joe\foo)。 private final Path absZipFilePath; public ZipEntry(String classPath) { absZipFilePath = Paths.get(classPath).toAbsolutePath(); } @Override public byte[] readClass(String fullyQualifiedClassName) throws Exception { // unzip jar/zip file // read class file bytes from zip file // This method makes use of specialized providers that create pseudo file systems // where the contents of one or more files is treated as a file system. // pass null to loader means only attempt to locate an installed provider try (FileSystem zipFs = FileSystems.newFileSystem(absZipFilePath, null)) { return Files.readAllBytes(zipFs.getPath(fullyQualifiedClassName)); } }}
读取当前目录下的 zip 文件里面的内容,稍微麻烦点,但是我们有新的api,所以代码也不多。
CompositeEntry
在程序的运行时,我们可以设置 -cp 参数,指定用户类路径,它可以利用; 来配置多个路径。
public class CompositeEntry implements Entry { private final List<Entry> entries = new ArrayList<>(); public CompositeEntry(String compositeClasspathList) { for (String classpath : compositeClasspathList.split(File.pathSeparator)) { entries.add(EntryFactory.create(classpath)); } } @Override public byte[] readClass(String fullyQualifiedClassName) throws Exception { // search every entry to find class file for (Entry entry : entries) { try { return entry.readClass(fullyQualifiedClassName); } catch (Exception ignored) { } } // not found throw new Exception("class not found: " + fullyQualifiedClassName); }}
整个逻辑简单,就是根据每个; 分割路径然后做一个解析,然后创建对应的 Entry:
public class EntryFactory { public static Entry create(String classpath) { // /a/b/c ; /x/y/z.zip if (classpath.contains(File.pathSeparator)) { return new CompositeEntry(classpath); } // /a/b/* if (classpath.endsWith("*")) { return new WildcardEntry(classpath); } // /x/y/z.jar if (classpath.endsWith(".jar") || classpath.endsWith(".JAR") || classpath.endsWith(".zip") || classpath.endsWith(".ZIP")) { return new ZipEntry(classpath); } return new DirEntry(classpath); }}WildcardEntry
假如说我们需要加载 java/lang/String.class,该类在xxxx/Java/jdk-1.8/jre/lib的 rt.jar 里面:
但是我们最好不将类路径写死,所以可以采用通配符的方式。比如我想加载jre/lib 下的类,那么就可以指定为 jre/lib/*,这种写法会加载当前目录下所有 JAR文件。
public class WildcardEntry extends CompositeEntry { public WildcardEntry(String wildcardPath) { super(compositePath(wildcardPath)); } private static String compositePath(String wildcardPath) { // remove * String baseDir = wildcardPath.replace("*", ""); try { // The try-with-resources Statement try (Stream<Path> pathStream = Files.walk(Paths.get(baseDir))) { // filter .jar and .JAR // collect return pathStream.filter(Files::isRegularFile) .map(Path::toString) .filter(p -> p.endsWith(".jar") || p.endsWith(".JAR")) .collect(Collectors.joining(File.pathSeparator)); } } catch (IOException e) { //e.printStackTrace(System.err); return ""; } }}
实现也很简单,就是将 jre/lib 下的所有 jar 文件过滤出来,使用 ;拼接后就是一个 CompositeEntry 了。
Classpath
经过上面的步骤,我们类的搜索做完了,现在我们需要指定下面的三个路径:
启动类路径(bootstrap classpath)扩展类路径(extension classpath)用户类路径(user classpath)JRE路径
private static String getJreDir() { String jh = System.getenv("JAVA_HOME"); if (jh != null) { return Paths.get(jh, "jre").toString(); } throw new RuntimeException("Can not find JRE folder!"); }
从系统设置的环境变量里面去读取。
启动类路径
private Entry parseBootStrapClasspath() { String jreDir = getJreDir(); // jre/lib/* String jreLibPath = Paths.get(jreDir, "lib") + File.separator + "*"; return new WildcardEntry(jreLibPath); }
注意看,我们这里添加了通配符,这样就可以遍历该目录下的所有 jar 文件。
扩展类路径
private Entry parseExtensionClasspath() { String jreDir = getJreDir(); // jre/lib/ext/* String jreExtPath = Paths.get(jreDir, "lib", "ext") + File.separator + "*"; return new WildcardEntry(jreExtPath); }
同上。
用户类路径
private Entry parseAppClasspath(String cpOption) { if (cpOption == null) { cpOption = "."; } return EntryFactory.create(cpOption); }
该路径默认为当前目录,如果命令行里面有设置,那就读取命令行里面的参数。命令行的实现这里不展开,有很多库可以实现命令行参数的解析。
读取类
有了上面的框架,我们就可以开始读取一个类了:
public byte[] readClass(String className) throws Exception { className = className + ".class"; try { return bootStrapClasspath.readClass(className); } catch (Exception ignored) { } try { return extensionClasspath.readClass(className); } catch (Exception ignored) { } return appClasspath.readClass(className); }
由于我们加载类的时候,传递的类名是不带后缀的,所以需要先加上,然后再搜索。
结语
本章讨论了Java虚拟机从哪里寻找class文件,对类路径有了较为深入的了解,并且把抽象的类路径概念转变成了具体的代码。下一章将研究class文件格式,实现class文件解析。
标签: #0x1c语言