类加载机制

1- 类加载的时机

与其他语言不一样,java的类的加载、连接和初始化都是运行期间完成的,在初始化之前,必须要完成加载、验证、准备。所以说要对类进行初始化就是要对类进行加载的时机。

1.1- 以下为5种会触发类初始化的情况

有且只有这5种情况会触发类的初始化,也成为对一个类的主动引用,其他的引用类的方式都不会触发初始化,称为被动引用。

  • 创建类的实例、也就是new一个对象的时候
  • 访问某个类或接口的静态变量、或者对该静态常量赋值,调用静态方法(除了被final修饰,以及已在编译期间把结果放入常量池的静态字段
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有加载,就会首先触发其初始化。
  • 当初始化一个类的子类,会首先初始化子类的父类
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会首先初始化这个类。
1.2- 几个典型的被动引用类的例子
  • 通过子类引用父类的静态字段,不会导致子类的初始化,而是对父类进行初始化
  • 通过数组定义来引用类,不会触发此类的初始化 MyClass [] a = new MyClass[10];这将会加载数组类的初始化,并不会触发MyClass类的加载
  • 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。通过常量传播优化,这两个类实际上已经没有任何关联了。

这里区分一下动态加载和静态加载,new创建对象时静态加载类,因为类必须在编译阶段提供,forName动态加载类,编译阶段不必提供。比如new一个对象,则编译的时候必须提供这个类的定义及文件在一起编译,否则会抛出ClassNotFound的异常,而用forName在进行编译是不用提供这个类的定义以及文件,而是在运行时虚拟机去指定路径上,比如classpath上主动搜寻这个类的定义文件(也可以从网络上获取class文件的二进制流)。

2- 类加载的过程

将二进制字节码文件转换成JVM中的Class对象,初始化不是类加载时必须触发的。类加载的各个阶段都是按照顺序开始的,但是在同一时间可能会出现多个阶段混合执行的情况。

2.1- 加载
  1. 获取类的class文件中的二进制数据,读到内存中。

  2. 将其代表的静态存储结构转化成为方法区的运行时数据结构。

  3. 创建一个这个类的Class对象,作为方法去此类数据的访问入口。

    注意:Class对象封装了类在方法区内的数据结构,并向java程序员提供了访问方法区内的数据结构的接口,hotSpot虚拟机的Class对象在方法区。

数组类本身不通过类加载器创建,它是java虚拟机直接创建的,递归采用数组元素的类加载过程去加载这个数组元素

2.2- 验证

确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全(文件格式,元数据、字节码、符号引用验证)

2.3- 准备

为类变量分配内存并设置类变量初始值(各种数据类型的零值)的阶段,这些内存将在方法区中进行分配。但是如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量值就会初始化为ConstantValue属性指定的值。

2.4- 解析
  • 将常量池内的符号引用替换成直接引用(类或接口,字段,方法等)
  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面常量,只要使用时能无歧义地定位到目标即可。符号引用于虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。如果有了直接引用,那引用目标必定存在与内存中。
2.5- 类的初始化

执行类构造器<clinit>()方法的过程:

<clinit>()是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,不需要先初始化父类构造器,也非必须。

2.6- 类加载器(双亲委派模型)
  • Bootstrap ClassLoader

    负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类,是虚拟机的一部分,HotSpot启动时会初始化此ClassLoader,开发者无法直接获取启动类加载器的引用,所以不允许直接通过引用进行操作。其他的ClassLoader都是由java实现的,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。当运行一个程序时,JVM启动,运行Bootstrap ClassLoader,该ClassLoader加载Java核心API(Extension ClassLoader、App ClassLoader包含在内,也被加载),然后调用Extension ClassLoader加载拓展API,最后调用App ClassLoader加载CLASSPATH目录下定义的Class。

  • Extension ClassLoader

    负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或者被java.ext.dir系统变量所指定的路径中的所有类库。

  • App ClassLoader

    负责加载classpath中指定的jar包目录中的class,被称为系统加载器,是ClassLoader.getSystemClassLoader()方法的返回值,如果用户没有自定义过自己的类加载器,一般情况下这就是程序中默认的类加载器。AppClassloader加载器的父加载器是ExtClassloader

  • Custom ClassLoader

    属于应用程序根据自身需要自定义的ClassLoader,如tomcat,jboss都会根据j2ee规范自行实现ClassLoader;用户自定义的无参加载器的父类加载器默认是AppClassloader加载器

双亲委派模型
类加载器双亲委派模型

委派过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求,搜索范围内没有找到所需的类时,子加载器才会尝试自己加载。

双亲委派模型使得类具有带有优先级的层次关系,避免同一个类在不同类加载器环境中发生混乱,导致出现不同类加载器都加载一个类的情况。比如java.lang.Object存在于启动类的搜索路径上,所以无论哪个类加载器要加载这个类最终都会委派给启动类加载器,因此Object类在程序的各种类加载器环境下都是同一个类。

比较两个类是否相等:

由同一个类加载器加载,类的全限定名相等
其中相等的含义是指:通过** equals()、isAssignableFrom()、isInstance()、instanceof关键字 **做对象所属关系判定情况。

package jvm;

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

/**
 * Created by zhanglbjames@163.com on 2017/4/11.
 */
public class ClassLoaderTest {
    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 in = getClass().getResourceAsStream(fileName);
                    if (in == null){
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[in.available()];
                    in.read(b);
                    return defineClass(name,b,0,b.length);
                }catch (IOException e){
                    throw new ClassNotFoundException();
                }
            }
        };
        // 由自定义加载器加载的
        Object obj = myLoader.loadClass("jvm.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        // jvm.ClassLoaderTest位于classpath上,所以App ClassLoader会加载这个类
        // 所以会存在两个由不同加载器加载进来的jvm.ClassLoaderTest,进行判别返回false
        System.out.println(obj instanceof jvm.ClassLoaderTest);
        
    }
}
/*
* output
* 
* class jvm.ClassLoaderTest
* false
*
* */

这个自定义的类加载器覆写了loadClass方法,方法内没有遵循类加载委派的模型规范,而是首先自己加载,找不到则由父类加载器进行加载。

一种特殊的类加载器Thread Context ClassLoader(线程上下文加载器)

使用Thread Context ClassLoader能破坏双亲委派的类加载机制,被广泛用于JNDI,用来对资源进行集中管理和查找,解决双亲委派自身模型的缺陷(用户代码可以调用基础API,但是基础API无法调用用户代码)


先获取线程上下文加载器,然后指定自定义的类加载器,然后就会使用用户自定义类加载器加载指定类,此时不会委派给父类加载器。

参考链接
http://www.jianshu.com/p/a4dc755652ff

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

推荐阅读更多精彩内容