`

深入剖析ClassLoader(三)—编译常量、类加载器深入剖析

    博客分类:
  • java
 
阅读更多

 

类的初始化

静态变量的生民语句以及静态代码块都被看作类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次夹在他们。

 



  


 

 

上图中a的初始化其实经过了四步

1、  a被初始化为默认值0

2、  a被赋予正确的初始值1

3、  执行静态代码块,将a的值赋为2

4、  执行静态代码块,将a的值赋为4

因此最终的执行结果是4

一个类只能被一个ClassLoader加载一次,只有没有被加载过的类或者已经被卸载的类才可能 被再次加载。类的初始化步骤如下:

1、    假如这个类还没有被加载和连接,那就先进行加载和连接

2、    加入存在直接的父类,并且这个父类还没有被初始化则先初始化直接的父类

3、    假如类中存在初始化语句,那就依次执行初始化语句。

注意:Java虚拟机在初始化一个类的时候要求它的父类已经被初始化,但是这条规则并不适应于接口!在初始化一个类的时候并不会初始化他所实现的接口!在初始化一个接口的时候也不会去初始化他的父接口!因此一个父接口并不会因为他的实现类或者子接口的初始化而初始化,只有当程序使用该接口特定的静态变量的时候才会去初始化这个接口!

我们上面的例子印证了第三点,对于前两点我们知道我们构造一个类的时候假设它有父类,就会默认调用父类的无参构造方法,然后就初始化了父类,而初始化之前必须进行加载和连接!

我们来看一个具体的程序来验证上面的结论!

package com.yhj.jvm.classloader.finalTest;

/**

 * @Description:父类

 * @Author YHJ  create at 2011-6-26 下午03:36:50

 * @FileName com.yhj.jvm.classloader.finalTest.$Parent.java

 */

class Parent{

    static int a=1;

    static{

       System.out.println("Parent's static block");

    }

}

/**

 * @Description:子类

 * @Author YHJ  create at 2011-6-26 下午03:37:04

 * @FileName com.yhj.jvm.classloader.finalTest.$Child.java

 */

class Child extends Parent{

    static int b=2;

    static{

       System.out.println("Parent's static block");

    }

}

/**

 * @Description:初始化测试用例

 * @Author YHJ  create at 2011-6-26 下午03:28:35

 * @FileName com.yhj.jvm.classloader.finalTest.InitTestCase.java

 */

public class InitTestCase {

    static{

       System.out.println("InitTestCase's static block");

    }

    public static void main(String[] args) {

       System.out.println(Child.b);

    }

}

这个程序很简单,我们一猜就能知道结果,因为是InitTestCase启动类,因此优先初始化!然后嗲用子类Childb静态变量,Child继承自Parent,因此先初始化父类Parent,所以运行结果是:

 



 
我们来修改一下调用方的程序,如下

package com.yhj.jvm.classloader.finalTest;

/**

 * @Description:父类

 * @Author YHJ  create at 2011-6-26 下午03:36:50

 * @FileName com.yhj.jvm.classloader.finalTest.$Parent.java

 */

class Parent{

    static int a=1;

    static{

       System.out.println("Parent's static block");

    }

}

/**

 * @Description:子类

 * @Author YHJ  create at 2011-6-26 下午03:37:04

 * @FileName com.yhj.jvm.classloader.finalTest.$Child.java

 */

class Child extends Parent{

    static int b=2;

    static{

       System.out.println("Parent's static block");

    }

}

/**

 * @Description:初始化测试用例

 * @Author YHJ  create at 2011-6-26 下午03:28:35

 * @FileName com.yhj.jvm.classloader.finalTest.InitTestCase.java

 */

public class InitTestCase {

    static{

       System.out.println("InitTestCase's static block");

    }

    public static void main(String[] args) {

       Parent parent;

       System.out.println("===== split line =====");

       parent=new Child();

       System.out.println(Parent.a);

       System.out.println(Child.b);

    }

}

为了能够看清楚parent具体的初始化状态,我们加入split line来隔开程序段,这样又会返回怎样的结果呢?

刚开始的Parent是否会初始化?

我们之前已经说的很清楚了,只有主动使用的6种情况会导致类的初始化,因此刚开始parent不会初始化,因为InitTestCase是启动类,所以最先初始化,然后是分隔符,然后初始化子类Child,初始化Child的时候发现继承了Parent,所以先初始化Parent,然后初始化Child,然后再次调用parent的静态变量,因为Parent已经初始化了,一个类只能被一个ClassLoader初始化一次,所以不会初始化Parent,直接输出Parent.a的数据,Child同理,因此最终执行结果是:

 



 
说到这里我们再次强调一下之前我们所说的类的初始化时机?只有一下6种主动使用的情况会导致类的初始化,其他任何情况都被视为是被动使用,不会导致类的初始化!

1、    创建类的实例

2、    访问某个类或接口的静态变量,或者对该静态变量赋值

3、    调用类的静态方法

4、    反射(如Class.forName(com.yhj.jvm.classloader.finalTest.TestCase”)

5、    初始化一个类的子类

6、    Java虚拟机启动时被标明为启动类的类(Java Test

除了以上6种情况都属于被动使用,不会导致类的初始化。

我们来看这样一个程序

/**

 * @Description:静态成员测试

 * @Author YHJ  create at 2011-6-26 下午02:54:23

 * @FileName com.yhj.jvm.classloader.finalTest.$StaticClassTest.java

 */

class StaticClassTest{

    public static int staticValue = 2 / 1;

    static{

       System.out.println("StaticClassTest's static block!");

    }

}

/**

 * @Description测试用例:用于测试

 * @Author YHJ  create at 2011-6-26 下午02:52:58

 * @FileName com.yhj.jvm.classloader.finalTest.TestCase.java

 */

public class TestCase {

    public static void main(String[] args) {

       System.out.println(StaticClassTest.staticValue);

    }

}

很显然属于调用类的静态成员变量,类会被初始化,初始化就会执行执行静态初始化语句块,就会执行System.out.println("StaticClassTest's static block!");语句,因此运行结果如下

 



 
但是如果这个类的静态成员是常量呢?

如下代码

package com.yhj.jvm.classloader.finalTest;

import java.util.Random;

/**

 * @Description:静态成员测试

 * @Author YHJ  create at 2011-6-26 下午02:54:23

 * @FileName com.yhj.jvm.classloader.finalTest.$StaticClassTest.java

 */

class StaticClassTest{

    public static int staticValue = 2 / 1;

    static{

       System.out.println("StaticClassTest's static block!");

    }

}

//===================================================================

/**

 * @Description:Final成员测试1

 * @Author YHJ  create at 2011-6-26 下午02:56:36

 * @FileName com.yhj.jvm.classloader.finalTest.$StaticFinalTest1.java

 */

class StaticFinalTest1{

    public final static int staticValue = 2 / 1;

    static{

       System.out.println("StaticFinalTest1's static block!");

    }

}

//===================================================================

/**

 * @Description:Final成员测试2

 * @Author YHJ  create at 2011-6-26 下午02:56:46

 * @FileName com.yhj.jvm.classloader.finalTest.TestCase.$StaticFinalTest2.java

 */

class StaticFinalTest2{

    public final static int staticValue = new Random().nextInt(10);

    static{

       System.out.println("StaticFinalTest2's static block!");

    }

}

//===================================================================

/**

 * @Description测试用例:用于测试

 * @Author YHJ  create at 2011-6-26 下午02:52:58

 * @FileName com.yhj.jvm.classloader.finalTest.TestCase.java

 */

public class TestCase {

    public static void main(String[] args) {

       System.out.println(StaticClassTest.staticValue);

       System.out.println(StaticFinalTest1.staticValue);

       System.out.println(StaticFinalTest2.staticValue);

    }

}

第一个我们已经知道了,会执行静态代码块,那第二个和第三个呢?他们的区别是第二个是在第一个的基础之上将staticValue变为了final,第三个对于第二个的区别是第三个static不是一个计算的具体值,而是0-10之间的一个随机数!又有什么样的区别呢?我们先来看一下运行结果!

 



 
从运行结果我们看出,StaticFinalTest1的静态代码块没有执行,即StaticFinalTest1类并没有被初始化,而StaticFinalTest2被初始化了!

这是为什么呢?我们再来看一下他们的区别:StaticFinalTest1的静态常量值是2/1=2,对于这种值在编译的时候JVM就会把结果计算出来放进class文件中,相当于StaticFinalTest1=2,而StaticFinalTest2在编译的时候并不知道具体的数值是多少,需要运行的时候才会被赋值,所以我们可以看出,调用编译时的静态常量并不会初始化这个类(即不属于主动使用),而调用运行时的静态常量是会初始化该类的!

我们再来改动一下上面父子类的程序

package com.yhj.jvm.classloader.finalTest;

/**

 * @Description:父类

 */

class Parent{

    static int a=1;

    static{

       System.out.println("Parent's static block");

    }

}

/**

 * @Description:子类

 */

class Child extends Parent{

    static int b=2;

    static{

       System.out.println("Parent's static block");

    }

}

/**

 * @Description:初始化测试用例

 */

public class InitTestCase {

 

    public static void main(String[] args) {

       System.out.println(Child.a);

    }

}

这样执行结果有什么呢?我们直接调用子类中父类定义的a会怎样呢?

按照我们之前的理论,执行结果应该是调用子类,先初始化父类,我们看下执行结果

 



 
 我们看到子类好像没有被初始化,不对,是确实没有被初始化!所以我们一定要

注意:JVM规范规定只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用,所以我们直接调用Child中没有定义的a不属于对Child的主动使用,因此没有初始化Child

注意:调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

我们来看一下loadClassAPI文档

 



 
  根据二进制名称来加在类,返回该类的Class对象的实例!那什么是二进制名称?

 



 

 二进制名称我们可以理解为类的全称,如上图中的类、内部类、匿名内部类等!

以前我们写的类都是通过启动类或者Web容器帮我们加载的,这里我们可以看到ClassLoader可以直接加载载这个类,但是我们看到这个类ClassLoader是抽象类

 



 

 也就是说我们无法通过

new的方法创建实例,那我们该怎么做呢?

我们知道如果这个类不能实例化,我们可以通过他的静态方法访问这个类,我们来看一下ClassLoader的一个静态方法getSysytemClassLoader()这个方法

 



 

 我们看这段程序

package com.yhj.jvm.classloader.finalTest;

/**

 * @Description;

 */

class Test{

    static{

       System.out.println("Test's static block");

    }

}

/**

 * @Description:ClassLoader测试用例

 */

public class ClassLoaderTestCase {

 

    public static void main(String[] args) throws ClassNotFoundException {

       ClassLoader classLoader=ClassLoader.getSystemClassLoader();

       System.out.println("ClassLoader:"+classLoader);

       Class<?>testClass=classLoader.loadClass("com.yhj.jvm.classloader.finalTest.Test");

       System.out.println("using class.forName('com.yhj.jvm.classloader.finalTest.Test')");

       testClass=Class.forName("com.yhj.jvm.classloader.finalTest.Test");

    }

}

这段程序执行下来,我们来看下运行结果

 



 

 很显然,这段程序当调用

loadClass的时候没有对类Test进行初始化,而当调用Class.forName的时候才被初始化,因此调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化!只有主动使用的6种情况才会初始化该类!

 



 

 从加载到连接到初始化,我们看到

loadClass只做加载和连接的操作,只有主动使用的6种才会对类进行初始化!

  • 大小: 21.7 KB
  • 大小: 28.6 KB
  • 大小: 10.2 KB
  • 大小: 16.5 KB
  • 大小: 10.2 KB
  • 大小: 16.2 KB
  • 大小: 8.7 KB
  • 大小: 56.7 KB
  • 大小: 48.6 KB
  • 大小: 25.9 KB
  • 大小: 202 KB
  • 大小: 23.7 KB
  • 大小: 21.1 KB
分享到:
评论

相关推荐

    JAVA ClassLoader 讲解 (类加载器)

    ClassLoader类加载器讲解,理解JAVA类加载机制

    【图解版】深入分析ClassLoader类加载工作机制

    【图解版】深入分析ClassLoader类加载工作机制,从原理到JVM的装载过程,详情分析了ClassLoader加载类以及自定义类加载器的过程,不可用于商业用途,如有版权问题,请联系删除!

    ClassLoader类加载器

    ClassLoader的API使用和自定义

    ClassLoader类加载机制

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java ...

    java应用程序类加载器,ClassLoader for java Application

    java应用程序类加载器(ClassLoader for java Application),类似exe4j, 方便启动java程序, 配置灵活,支持多平台选择性配置

    ClassLoader类加载机制和原理详解

    ClassLoader类加载机制和原理详解

    java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器

    类加载器分为根加载器(bootstrap classloader)、扩展类加载器(ext classloader)、系统类加载器(system classloader)、自定义类加载器(通常继承java.net.URLClassLoader,重写findClass()),它们的关系通常...

    classloader类加载器_基于java类的加载方式详解

    下面小编就为大家带来一篇classloader类加载器_基于java类的加载方式详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    java类加载器实例

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java ...

    Java类加载器(ClassLoader)1

    如果户创建的JAR放在此录下,也会动由扩展类加载器加载.应程序类加载器(系统类加载器,Application ClassLoader)java语编写,由sun.

    java类加载器

    ClassLoader 三种类加载方式 Boostrap Extenxsion 以及Application ClassLoad分别适用的场景

    java的ClassLoader类加载器机制

    jvm运行的过程中,需要载入类,而类的加载需要类加载器,本文章提供了java的类加载器的工作原理。可以使读者更加理解jvm的运行机制。

    Java类加载机制与反射-PPT

    Java的类加载机制:加载,连接,初始化。JAVA类加载器: Bootstrap ClassLoader : 根类加载器, Extension ClassLoader: 扩展类加载器, System ClassLoader : 系统类加载器, Java反射

    深入java加载器源代码

    ClassLoader,,深入java加载器,,深入java加载器源代码

    【JVM】类加载器与双亲委派模型

    内嵌在JVM内核中的加载器,由C++语言编写(因此也不会继承ClassLoader),是类加载器层次中最顶层的加载器。用于加载java的核心类库,即加载jre/lib/rt.jar里所有的class。由于启动类加载器涉及到虚拟机本

    掌握Java类加载器

    类加载器是Java最强大的特征之一。但是开发者常常忘记类加载组件。类加载器是在运行时负责寻找和加载类文件的类。Java允许使用不同的类加载器,甚至自定义的类加载器。类加载器从源文件(通常是.class 或 .jar文件)...

    类加载器,classload

    关于类加载器的 上课ppt -java虚拟机自带的加载器 根类加载器(Bootstrap) c++写的看不到扩展类加载器(extension) 系统类加载器(System) AppClassLoad 用户自定义的类加载器 Java.lang.ClassLoader的子类

    自定义类加载器

    简单的自定义类加载器问候世界hello word,基于磁盘的ClassLoader

    类加载器文件

    JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。 SE(J2SE),standard edition,标准版,是我们通常用的一个版本,从JDK 5.0开始,改名为Java SE。

    Java类加载器加载类顺序

    java ClassLoader的学习  java是一门解释执行的语言,由开发人员编写好的java源文件先编译成字节码文件.class...  一个类如果要被JVM所调度执行,必须先把这个类加载到JVM内存里,java.lang下有个很重要的类ClassL

Global site tag (gtag.js) - Google Analytics