虚拟机的类加载机制

0.前言

最近又开始看《深入理解Java虚拟机》这本书了,发现这东西很久不看忘得很快,还是写下来加深点影响吧,在这一篇文章里,我们要讨论的问题主要有以下几个方面:
(1)什么是虚拟机的类加载机制。
(2)类加载的过程
(3)类加载器

1. 什么是虚拟机的类加载机制

虚拟机把描述类的文件从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程,我们称为虚拟机的类加载机制。

2. 类加载的过程是怎么样的

整个过程如下图所示:


image.png

下面我们来分步解释下整个过程:

2.1 加载

在这个阶段,虚拟机主要完成一下三件事:
(1)通过一个全额限定名来获取定义此类的二进制字节流。
(2)将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构。
(3)在Java堆中声称一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
Java虚拟机内存模型有不懂的可以看这篇文章:
https://www.jianshu.com/p/43d403b3eff7

2.2 连接

类的连接主要分为三个步骤:

(1)验证:验证被加载后的类是否有正确的结构,类数据是否符合虚拟机的要求,确保不会危害虚拟机安全。
包含四个阶段的校验动作:

  • a.文件格式验证;
  • b.原数据信息进行语义校验;
  • c.字节码验证;
  • d.符号引用验证。

(2)准备:为类的静态变量(static filed)在方法区分配内存,并设置默认初始值(0值或null值),这些内存都将在方法区分配。对于一般的成员变量是在类实例化时候,随对象一起分配在堆内存中。

这里有2点要特别注意:

  • 这里虚拟机只初始化类的静态变量,不初始化实例变量。
  • 这里说的初始值“通常情况下”,指的是数据类型的零值。
    举个例子:public static it value = 123 准备阶段后初始值是0,不是123。把value赋值为123的操作,在初始化阶段才做。

(3)解析:将类的二进制数据内的符号引用替换为直接引用。
什么意思呢?要解答这个问题,首先我们需要知道什么是符号引用和直接引用:

  • 符号引用:用自足福海来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
  • 直接引用:可以是指向目标的指针,相对偏移量或者能间接找到目标的句柄。如果有了直接引用,那目标必定已经在内存中了。

2.3 初始化

初始化是类加载过程的最后一步,之前的4步都是完全有虚拟机主导的,直到这一步才由Java程序代码来控制。在准备阶段,变量已经赋过一次系统要求的初始值了,在初始化阶段,则是根据程序员的要求来初始化变量和其他资源。或者说是执行类的构造器<clinit>()的过程。
在Java类中,对类变量指定初始值有两种方式:
(1)在声明类变量时指定初始值。

public static int A = 1

(2)在使用静态初始化块时,为类变量指定初始值。

public int A;
static {
  A = 2;
}

JVM会按照这些语句在程序中的排列顺序依次执行他们。

  • JVM初始化一个类的步骤:
    (1)假如这个类还没有被加载和连接,则程序先加载并连接该类。
    (2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。若该直接父类又有直接父类,依次类推。所以JVM最先初始化的总是 java.lang.Object 类。
    (3)当程序主动使用任何一个类时,系统会保证该类以及所有父类(包括直接父类和间接父类)都会被初始化。
    (4)假如类中有初始化语句,则系统依次执行这些初始化语句

3. 类加载器

3.1 什么是类加载器

虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此文件的二进制字节流"这个动作放到Java虚拟机外部趋势线,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

3.2类与类加载器

对于任何一个类,都需要由加载它的类加载器和这个类本身一同确定其唯一性,怎么理解这句话呢?简单来说就是如果两个类即使来自于同一个文件,如果加载他们的类加载器不一样,那这两个类必定不相同。

可以看个书上的例子:

package demo.jvm;

import java.io.IOException;
import java.io.InputStream;

public class ClassLoadTest {

    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };

        Object obj = myLoader.loadClass("demo.jvm.ClassLoadTest").newInstance();

        System.out.println(obj.getClass());
        System.out.println(obj instanceof demo.jvm.ClassLoadTest);

    }

}

输出:

class demo.jvm.ClassLoadTest
false

代码做了两件事,第一:自己构造了一个类加载器myLoader, 第二:使用这个类加载器去加载了一个名为demo.jvm.ClassLoadTest的类,并实例化了这个类对象。

从输出来看,这个对象却是是类demo.jvm.ClassLoadTest实例化出来的,但是从第二句来看,这个对象与系统实例化出来的对象类型并不相同,这也验证了之前的说法:如果两个类即使来自于同一个文件,如果加载他们的类加载器不一样,那这两个类必定不相同。

3.3 双亲委派模型

Java虚拟机只有两种不同的类加载器:

  • 启动类加载器(Bootstrap ClassLoader):使用C++语言(HotSpot)实现,是虚拟机的一部分,该类加载器实例无法被用户获取;
  • 所有其它的类加载器:均由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader;

从Java程序员的角度,类加载器还可以继续细化,绝大部分Java程序都会使用到以下3种类加载器。

  • 启动类加载器 (Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的目录中的,并且是虚拟机识别的(仅按照文件名识别,例如rt.jar)类库加载到虚拟机内存中。 启动类加载器无法被Java程序直接引用,用户在编写自定义加载器时,如果需要把加载请求委托给引导类加载器,直接使用null代替即可。
  • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,他负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。该类是ClassLoader中的getSystemClassLoader()方法的返回值,因此也称作“系统类加载器”。它负责用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有定义过自己的类加载器,一般情况下这个就是程序的默认类加载器。

如下图所示的这种类加载器之间的关系,我们称为双亲委派模型。

image.png

它的工作过程是这样的
(1)如果一个类加载器收到了类加载的请求,他首先不会自己尝试去加载这个类,而是把这个请求发给父类加载器去完成。
(2)所有的加载请求都是会传送到顶层的启动类加载器。
(3)当父类加载器反馈自己无法完成这个类的加载,子加载器会尝试自己加载。

那么双亲委派的好处是什么呢?

双亲委派模型能保证基础类仅加载一次,不会让jvm中存在重名的类。比如String.class,每次加载都委托给父加载器,最终都是BootstrapClassLoader,都保证java核心类都是BootstrapClassLoader加载的,保证了java的安全与稳定性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容