java中的类加载器ClassLoader和类初始化

        每一个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,通常的,咱们只会用到一个原生的类加载器AppClassLoader,它只加载Java API等可信类,一般只是在本地磁盘中加载,这些类通常就够咱们使用了。若是咱们须要从远程网络或数据库中下载.class字节码文件,那就须要咱们来挂载额外的类加载器。

        通常来讲,类加载器是按照树形的层次结构组织的,每一个加载器都有一个父类加载器。另外,每一个类加载器都支持代理模式,便可以本身完成Java类的加载工做,也能够代理给其它类加载器。html

ClassLoader中的几个实现类
java

    一、Bootstrap ClassLoader 这个是JVM加载自身工做须要的类,彻底由JVM本身来控制,外部没法访问到这个;web

    二、ExtClassLoader比较特殊的,服务的特定目标在System.getProperty("java.ext.dirs");redis

    三、AppClassLoader,父类是ExtClassLoader,java中参数-classpath中的类均可以被这个类加载器加载;数据库

    四、URLClassLoader,通常这个类帮咱们实现了大部分的工做,自定义能够继承这个类,这样仅仅在须要的地方作修改就好了;tomcat

       类加载器的加载顺序有两种,一种是父类优先策略,一种是是本身优先策略,父类优先策略是比较通常的状况(如JDK采用的就是这种方式),在这种策略下,类在加载某个Java类以前,会尝试代理给其父类加载器,只有当父类加载器找不到时,才尝试子类加载器去加载,若是找到了,本身就不用加载。本身优先的策略与父类优先相反,它会首先尝试本身加载,若是找到了就不用父类加载器去加载,只有找不到的时候才要父类加载器去加载,这种在web容器(如tomcat)中比较常见。网络

动态加载数据结构

       无论使用什么样的类加载器,类都是在第一次被用到时,动态加载到JVM的。这句话有两层含义:架构

  1. Java程序在运行时并不必定被完整加载,只有当发现该类尚未加载时,才去本地或远程查找类的.class文件并验证和加载(赖加载);
  2. 当程序建立了第一个对类的静态成员的引用(如类的静态变量、静态方法、构造方法——构造方法也是静态的)时,才会加载该类。Java的这个特性叫作:动态加载

JVM加载clas文件到内存的方式
函数

    一、显示加载:不经过代码里的ClassLoader调用,而是JVM来自动加载类到内存中的方式;

            1.一、经过Class中的forName;

            1.二、经过ClassLoader中的loadClass

            1.三、经过ClasLoader中的findSystemClass

    二、隐身加载:经过代码中ClassLoader来加载的方式;

如何加载class文件

    1)加载(Loading),由类加载器执行,查找字节码,并建立一个Class对象(只是建立);

             a)经过类的全名产生对应类的二进制数据流。(注意,若是没找到对应类文件,只有在类实际使用时才抛出错误。)

              b)分析并将这些二进制数据流转换为方法区(JVM 的架构:方法区、堆,栈,本地方法栈,pc 寄存器)特定的数据结构(这些数据结构是实现有关的,不一样 JVM 有不一样实现)。这里处理了部分检验,好比类文件的魔数的验证,检查文件是否过长或者太短,肯定是否有父类(除了 Obecjt 类)。

              c)建立对应类的 java.lang.Class 实例(注意,有了对应的 Class 实例,并不意味着这个类已经完成了加载链连接!)。

     2)连接(Linking),验证字节码,为静态域分配存储空间(只是分配,并不初始化该存储空间),解析该类建立所须要的对其它类的应用;

              a)验证(verification)

连接的第三部解析会把类中成员方法、成员变量、类和接口的符号引用替换为直接引用,而在这以前,须要检测被引用的类型正确性和接入属性是否正确(就是 public ,private 的的问题),诸如检查 final class 又没有被继承,检查静态变量的正确性等等。(注意到实际上有一部分验证过程已经在加载的过程当中执行了。)

              b)准备(preparation)

对类的成员变量分配空间。虽然有初始值,但这个时候不会对他们进行初始化(由于这里不会执行任何 Java 代码)。具体以下:全部原始类型的值都为 0。如 float: 0f, int: 0, boolean: 0(注意 boolean 底层实现大多使用 int),引用类型则为 null。值得注意的是,JVM 可能会在这个时期给一些有助于程序运行效率提升的数据结构分配空间。好比方发表(相似与 C++中的虚函数表,参见另外一篇博文《Java:方法的虚分派和方法表》)。

              c)解析(Resolution)

首先,为类、接口、方法、成员变量的符号引用定位直接引用(若是符号引用先到常量池中寻找符号,再找先应的类型,无疑会耗费更多时间),完成内存结构的布局。

而后,这一步是可选的。能够在符号引用第一次被使用时完成,即所谓的延迟解析(late resolution)。但对用户而言,这一步永远是延迟解析的,即便运行时会执行 early resolution,但程序不会显示的在第一次判断出错误时抛出错误,而会在对应的类第一次主动使用的时候抛出错误!

最后,这一步与以后的类初始化是不冲突的,并不是必定要全部的解析结束之后才执行类的初始化。不一样的 JVM 实现不一样。详情见另外一篇博文《Java 类加载的延迟初始化》

     3)初始化(Initialization)。

注 意:static{}子句是在类第一次被java程序主动使用时才执行且执行一次,也就是执行Class.forName()方法时。若是用类.class并不会执行static{}子句,由于它是在编译时就已经存在类的.class文件,否则会编译失败。代码能够说明问题,注意类A的输出结果:

动态加载类:


public class BeanUtilsTest
{
    public static void main(String[] args)
        throws Exception
    {
        Class clz = Class.forName("com.ai.redis.A");
    }
}

class A
{
    public static int VALUE;
    static
    {
        System.out.println("run parent static code.");
    }
}

输出结果:打印run parent static code.

类.class:

public class BeanUtilsTest
{
    public static void main(String[] args)
        throws Exception
    {
        Class clz1 = A.class;
    }
}

class A
{
    public static int VALUE;
    static
    {
        System.out.println("run parent static code.");
    }
}

输出结果:啥也没有。

经过以上比较,下面这段代码应该知道打印什么了吧。

public class BeanUtilsTest
{
    public static void main(String[] args)
        throws Exception
    {
        System.out.println(A.VALUE);
    }
}

class A
{
    public static final int VALUE = 10;
    static
    {
        System.out.println("run parent static code.");
    }
}


输出结果:10

有人要问了,为何不打印run parent static code.由于VALUE变量是在编译时就已经肯定的一个常量值跟类.class文件是一个道理,因此不打印。

注:编译时常量必须知足3个条件:static的,final的,常量。

而下面的代码都会致使打印run parent static code.

<pre class="html" name="code">    static int a;
    final int b;
    static final int c = Math.abs(10);
    static final int d;
    static
    {
        d = 5;
    }

 

PS:

根据java虚拟机规范,全部java虚拟机实现必须在每一个类或接口被java程序首次主动使用时才初始化。

主动使用有如下6种:
1) 建立类的实例
2) 访问某个类或者接口的静态变量,或者对该静态变量赋值(若是访问静态编译时常量(即编译时能够肯定值的常量)不会致使类的初始化)
3) 调用类的静态方法
4) 反射(Class.forName(xxx.xxx.xxx))
5) 初始化一个类的子类(至关于对父类的主动使用),不过直接经过子类引用父类元素,不会引发子类的初始化(参见示例6)
6) Java虚拟机被标明为启动类的类(包含main方法的)

类与接口的初始化不一样,若是一个类被初始化,则其父类或父接口也会被初始化,但若是一个接口初始化,则不会引发其父接口的初始化。

为何接口不能定义成员变量,而只能定义 final static 变量。

  • 1.接口是不可实例化,它的全部元素都没必要是实例(对象)层面的。static 知足了这一点。
  • 2.若是接口的变量能被修改,那么一旦一个子类实现了这个接口,并修改了接口中的非 final 变量,而该子类的子类再次修改这个非 final 的变量后,形成的结果就是虽然实现了相同的接口,但接口中的变量值是不同的。

综上述,static final 更适合于接口。

参考:

一、《经过类字面常量解释接口常量为何只能定义为 static final,类加载过程—Thinking in java》

二、http://blog.csdn.net/biaobiaoqi/article/details/6909141

三、http://www.cnblogs.com/zhguang/p/3154584.html

四、http://iamzhongyong.iteye.com/blog/2091549

相关文章
相关标签/搜索