手写JVM(十五)-类加载器

代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。

1.类加载器概述

类加载器是 JVM 执行类加载机制的前提。

ClassLoader的作用:

ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的, ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的java.lang.Class对象实例。然后交给Java虚拟机进行链接、初始化等操作。因此, ClassLoader在整个装载阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的链接和初始化行为。至于它是否可以运行,则由Execution Engine决定

类加载器最早出现在Java1.0版本中,那个时候只是单纯地为了满足Java Applet应用而被研发出来。但如今类加载器却在OSGi、字节码加解密领域大放异彩。这主要归功于Java虚拟机的设计者们当初在设计类加载器的时候,并没有考虑将它绑定在JVM内部,这样做的好处就是能够更加灵活和动态地执行类加载操作。

 

分类:

JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)

从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么”定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构主要是如下情况:

  • 除了顶层的启动类加载器外,其余的类加载器都应当有自己的“父类”加载器。
  • 不同类加载器看似是继承(Inheritance)关系,实际上是包含关系。在下层加载器中,包含着上层加载器的引用

Java虚拟机的类加载系统复杂,本节就实现一个简化版的类加载器,今后再对它进行扩展。

我们可以参考一下JVM源码对器的实现。

public Class<?> loadClass(String name) throws ClassNotFoundException——–加载名称为name的类,返回结果为java.lang.Class类的实例。如果找不到类,则返回ClassNotFoundException异常,该方法中的逻辑就是双亲委派模式的实现。

ctrl+B进入

简单看看findLoadedClass(name)源码

回到loadClass

一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。

 

2.全部代码

在/rtda/heap目录下创建class_loader.go文件,在其中定义ClassLoader结构体,代码如下:

package heap

import (
    "fmt"
    "jvmgo/ch06/classfile"
    "jvmgo/ch06/classpath"
)

/*
class names:
- primitive types: boolean, byte, int ...
- primitive arrays: [Z, [B, [I ...
- non-array classes: java/lang/Object ...
- array classes: [Ljava/lang/Object; ...
*/

type ClassLoader struct {
    cp *classpath.Classpath
    classMap map[string]*Class // loaded classes
}

func NewClassLoader(cp *classpath.Classpath) *ClassLoader {
    return &ClassLoader{
        cp:       cp,
        classMap: make(map[string]*Class),
    }
}

func (self *ClassLoader) LoadClass(name string) *Class {
    if class, ok := self.classMap[name]; ok {
        // already loaded
        return class
    }

    return self.loadNonArrayClass(name)
}

func (self *ClassLoader) loadNonArrayClass(name string) *Class {
    data, entry := self.readClass(name)
    class := self.defineClass(data)
    link(class)
    fmt.Printf("[Loaded %s from %s]\n", name, entry)
    return class
}

func (self *ClassLoader) readClass(name string) ([]byte, classpath.Entry) {
    data, entry, err := self.cp.ReadClass(name)
    if err != nil {
        panic("java.lang.ClassNotFoundException: " + name)
    }
    return data, entry
}

// jvms 5.3.5
func (self *ClassLoader) defineClass(data []byte) *Class {
    class := parseClass(data)
    class.loader = self
    resolveSuperClass(class)
    resolveInterfaces(class)
    self.classMap[class.name] = class
    return class
}

func parseClass(data []byte) *Class {
    cf, err := classfile.Parse(data)
    if err != nil {
        //panic("java.lang.ClassFormatError")
        panic(err)
    }
    return newClass(cf)
}

// jvms 5.4.3.1
func resolveSuperClass(class *Class) {
    if class.name != "java/lang/Object" {
        class.superClass = class.loader.LoadClass(class.superClassName)
    }
}
func resolveInterfaces(class *Class) {
    interfaceCount := len(class.interfaceNames)
    if interfaceCount > 0 {
        class.interfaces = make([]*Class, interfaceCount)
        for i, interfaceName := range class.interfaceNames {
            class.interfaces[i] = class.loader.LoadClass(interfaceName)
        }
    }
}

func link(class *Class) {
    verify(class)
    prepare(class)
}

func verify(class *Class) {
    // todo
}

 

3.代码解释

结构体

ClassLoader依赖Classpath来搜索和读取class文件,cp字段保存Classpath指针。classMap字段记录已经加载的类数据,key是类的完全限定名。

在前面讨论中,方法区一直只是个抽象的概念,现在可以把classMap字段当作方法区的具体实现。

 

NewClassLoader函数

NewClassLoader()函数创建ClassLoader实例,如下所示:

 

LoadClass方法

LoadClass()方法把类数据加载到方法区,先查找classMap,看类是否已经被加载。如果是,直接返回类数据,否则调用loadNonArrayClass()方法加载类。数组类和普通类有很大的不同,它的数据并不是来自class文件,而是由Java虚拟机在运行期间生成。本章暂不考虑数组类的加载,留到第8章详细讨论。

 

loadNonArrayClass方法

loadNonArrayClass()方法的代码如下:

 

类的加载大致可以分为三个步骤:首先找到class文件并把数据读取到内存;然后解析class文件,生成虚拟机可以使用的类数据,并放入方法区;最后进行链接。下面分别讨论这三个步骤。

 

实现可以参考java类加载器的源码:

下面尝试实现

 

readClass

readClass()方法只是调用了Classpath的ReadClass()方法,并进行了错误处理。需要解释一下它的返回值。为了打印类加载信息,把最终加载class文件的类路径项也返回给了调用者。

 

defineClass

defineClass()方法,defineClass()方法首先调用parseClass()函数把class文件数据转换成Class结构体。Class结构体的superClass和interfaces字段存放超类名和直接接口表,这些类名其实都是符号引用。调用resolveSuperClass()和resolveInterfaces()函数解析这些类符号引用。

 

parseClass函数

下面是parseClass()函数的代码。

 

resolveSuperClass函数

resolveSuperClass()函数:superClass存放超类名表,解析这些类符号引用,除java.lang.Object以外,所有的类都有且仅有一个超类。因此,除非是Object类,否则需要递归调用LoadClass()方法加载它的超类。

 

resolveInterfaces函数

与此类似,resolveInterfaces()函数递归调用LoadClass()方法加载类的每一个直接接口,代码如下:

 

link

类的链接分为验证和准备两个必要阶段,link()方法的代码如下:

 

Java虚拟机规范要求在执行类的任何代码之前,对类进行严格的验证,Java虚拟机规范4.10节详细介绍了类的验证算法,这里不对其验证,如果我有空再回来补充:

 

类的加载过程:

 

准备阶段主要是给类变量分配空间并给予初始值,prepare函数下节说。

 

4.参考

尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ

周志明:深入理解java虚拟机

张秀宏:自己动手写Java虚拟机 (Java核心技术系列)

GO语言官网:Standard library – Go Packages

Java虚拟机规范:Chapter 4. The class File Format (oracle.com)

 

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇