Java中的类的加载与双亲委派机制

一、类加载机制:
jvm把class文件加载到内存,并对数据进行转换、校验、解析和初始化,最终形成jvm可以直接使用的java类型的过程就是类加载机制。

二、类加载的过程:

(一)、加载:
1、通过类的全限定名来获取类的二进制字节流。
2、将字节码文件静态的存储结构转换为JVM动态的存储结构。
3、在内存中生成Java类的class对象。

(二)、链接:
1、验证:确保class类里边的字节流不会对JVM造成损害。
2、准备:在内存空间中为静态的变量开辟空间并将其赋值为变量类型对应的零值。
3、解析:将虚拟机中常量池中的符号引用替换为直接引用的过程,让引用指向对象(举例:String s =“hello”,转化为 s的地址指向“hello”的地址)。

(三)、初始化:
初始化阶段是执行类构造器方法的过程。类构造器方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。

三、类的主动引用:

类的引用分为两种:主动引用和被动引用。
类的主动引用只有五种情况,只有这五种情况才会引发类的加载!

1、遇到new getstatic putstatic或者invokestatic这四条字节码指令时,如果类没有初始化过,那么就要进行对该类的类加载。

这四条指令常见于:
使用new关键字实例化对象时。
读取或者设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态变量除外)。
调用类的静态方法。

2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,那么就要对类加载并初始化。

3、当需要对一个类进行初始化的时候,如果此时它的父类还没有进行初始化,则需要先对父类进行初始化。

4、当虚拟机启动的时候,虚拟机会优先初始化由用户指定的一个要执行的主类(就是包含main方法的那个类)。

5、当使用JDK1.7的动态语言支持的时候,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类并未初始化的时候,则先要进行初始化。

这五种情况之外都是对类的被动引用,不会触发类的初始化!

四、类加载的顺序:

普通情况下的加载顺序:
静态变量/静态代码块 -》 (main方法) -》 非静态变量/代码块 -》 构造方法。

有继承情况出现时的加载顺序:
父类的静态变量/静态代码块-》子类静态变量/静态代码块-》(子类main方法)-》父类变量/初始化块-》父类构造器-》子类变量/初始化块-》子类构造器。

结论:
1、先静态代码,再非静态代码,最后是构造方法,父类在子类之前加载。main方法在静态之后非静态之前加载。
2、静态变量/初始化块和非静态变量/初始化块的加载顺序取决于它们在类中出现的先后顺序。

类加载例题:求输出结果。

class ATest{
    public int i = method1();
    public static int j = method2();
    public int k = 0;

    public ATest(){
        System.out.println(1);
    }

    public int method1(){
        System.out.println(2);
        return 2;
    }

    public static int method2(){
        System.out.println(3);
        return 3;
    }
}
public class BTest extends ATest{

    public int m = method3();
    public static int n = method4();
    public int t = 0;

    public BTest(){
        System.out.println(4);
    }

    public int method3(){
        System.out.println(5);
        return 5;
    }

    public static int method4(){
        System.out.println(6);
	    return 6;
    }	
   public static void main(String[] args){
       ATest a = new BTest();
       System.out.println(7);
   }
}	

输出结果是3 6 7 2 1 5 4

解析:
首先Java虚拟机会优先对主类进行初始化:所以先要去对含有main函数的类BTest类进行初始化,但是BTest类是ATest类的子类,所以这时要去对ATest类进行一个初始化。

ATest类中先初始化的是静态代码,所以先初始化的是静态变量j和静态方法method2,method2()方法被初始化,所以第一个输出3;父类静态代码初始完成之后,要开始初始化子类的静态代码,子类的静态变量n和静态方法method4,method4被初始化所以输出6。

父类和子类中的静态代码初始化完毕后,开始初始化main方法,所以main方法里边的打印7会被初始化。

静态代码和main方法初始化完毕后,该去初始化非静态代码,先父类后子类。父类中的非静态变量i和非静态方法method1被初始化输出2;同理子类的非静态变量和方法也被初始化输出1。

最后初始化父类和子类的构造器,父类在先子类在后,所以先输出5,后输出4。

双亲委派机制:
Java的类加载器:
启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

应用程序类加载器(Application ClassLoader):负责加载用户类的路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

在这四种类加载器之外还可以有使用者自定义的类加载器

这四种类加载器之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)。

双亲委派模型:
当某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

具体过程如图所示:
在这里插入图片描述
双亲委派模型的优势:

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

双亲委派的具体实现:
在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

面试考点:重写Java类加载器。
JDK1.2后因为双亲委派机制的优势,我们不需要去破坏双亲委派机制,不用重写loadClass()方法,只需要重写findclass方法,来改变双亲委派的顺序就可以。

面试例题:如何自己定义父类加载器已有的类(例如java.lang.System)
答案:由于双亲委派机制,通常是不行的。以System类为例:因为System是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。
但是Java的加载器都是加载指定目录的,如果我们将自己的类加载器放在一个特殊的目录,那么虚拟机的父类加载器就无法加载,由于父类虚拟机无法加载,从而虚拟机就会加载我们自己的类。

参考文章:
1、《深入理解Java虚拟机》周志明著。
2、Alexia(minmin)博客,http://www.cnblogs.com/lanxuezaipiao/p/4138511.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值