`

java类的 [加载 -> 链接 ->初始化 ]

    博客分类:
  • java
 
阅读更多
1 加载

Java类的加载是由类加载器来完成的。一般来说,类加载器分成两类:启动类加载器(bootstrap)和用户自定义的类加载器(user-defined)。两者的区别在于启动类加载器是由JVM的原生代码实现的,而用户自定义的类加载器都继承自Java中的java.lang.ClassLoader 类。在用户自定义类加载器的部分,一般JVM都会提供一些基本实现。应用程序的开发人员也可以根据需要编写自己的类加载器。JVM中最常使用的是系统类加载器(system),它用来启动Java应用程序的加载。通过java.lang.ClassLoader的getSystemClassLoader() 方法可以获取到该类加载器对象。

类加载器需要完成的最终功能是定义一个Java类,即把Java字节代码转换成JVM中的java.lang.Class类的对象。但是类加载的过 程并不是这么简单。Java类加载器有两个比较重要的特征:层次组织结构和代理模式。层次组织结构指的是每个类加载器都有一个父类加载器,通过getParent() 方 法可以获取到。类加载器通过这种父亲-后代的方式组织在一起,形成树状层次结构。代理模式则指的是一个类加载器既可以自己完成Java类的定义工作,也可 以代理给其它的类加载器来完成。由于代理模式的存在,启动一个类的加载过程的类加载器和最终定义这个类的类加载器可能并不是一个。前者称为初始类加载器, 而后者称为定义类加载器。两者的关联在于:一个Java类的定义类加载器是该类所导入的其它Java类的初始类加载器。比如类A通过import导入了类 B,那么由类A的定义类加载器负责启动类B的加载过程。

一般的类加载器在尝试自己去加载某个Java类之前,会首先代理给其父类加载器。当父类加载器找不到的时候,才会尝试自己加载。这个逻辑是封装在java.lang.ClassLoader类的loadClass() 方法中的。一般来说,父类优先的策略就足够好了。在某些情况下,可能需要采取相反的策略,即先尝试自己加载,找不到的时候再代理给父类加载器。这种做法在Java的Web容器中比较常见,也是Servlet规范 推荐的做法。比如,Apache Tomcat 为每个Web应用都提供一个独立的类加载器,使用的就是自己优先加载的策略。IBM WebSphere Application Server 则允许Web应用选择类加载器使用的策略。

类加载器的一个重要用途是在JVM中为相同名称的Java类创建隔离空间。在JVM中,判断两个类是否相同,不仅是根据该类的二进制名称 ,还需要根据两个类的定义类加载器。只有两者完全一样,才认为两个类的是相同的。因此,即便是同样的Java字节代码,被两个不同的类加载器定义之后,所得到的Java类也是不同的。如果试图在两个类的对象之间进行赋值操作,会抛出java.lang.ClassCastException 。这个特性为同样名称的Java类在JVM中共存创造了条件。在实际的应用中,可能会要求同一名称的Java类的不同版本在JVM中可以同时存在。通过类加载器就可以满足这种需求。

2 链接

java类的链接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程。在链接之前,这个类必须被成功加载。类的链接包括验证、准备和解析等几个步骤。验证是用来确保Java类的二进制表示在结构上是完全正确的。如果验证过程出现错误的话,会抛出java.lang.VerifyError 错 误。准备过程则是创建Java类中的静态域,并将这些域的值设为默认值。准备过程并不会执行代码。在一个Java类中会包含对其它类或接口的形式引用,包 括它的父类、所实现的接口、方法的形式参数和返回值的Java类等。解析的过程就是确保这些被引用的类能被正确的找到。解析的过程可能会导致其它的 Java类被加载。

不同的JVM实现可能选择不同的解析策略。一种做法是在链接的时候,就递归的把所有依赖的形式引用都进行解析。而另外的做法则可能是只在一个形式引 用真正需要的时候才进行解析。也就是说如果一个Java类只是被引用了,但是并没有被真正用到,那么这个类有可能就不会被解析
。考虑下面的代码:

public class LinkTest {  

   public static void main(String[] args) {      

      ToBeLinked toBeLinked = null;      

      System.out.println("Test link.");  

   }

}

类 LinkTest引用了类ToBeLinked,但是并没有真正使用它,只是声明了一个变量,并没有创建该类的实例或是访问其中的静态域。在

Oracle的JDK 6中,如果把编译好的ToBeLinked的Java字节代码删除之后,再运行LinkTest,程序不会抛出错误。这是因为ToBeLinked类没有

被真正用到,而Oracle的JDK 6所采用的链接策略使得ToBeLinked类不会被加载,因此也不会发现ToBeLinked的Java字节代码实际上是不存在的。如果把代码改成

ToBeLinked toBeLinked = new ToBeLinked();之后,再按照相同的方法运行,就会抛出异常了。因为这个时候ToBeLinked这个类被真正使用到了,会需要加载这个类。

3 初始化

当一个Java类第一次被真正使用到的时候,JVM会进行该类的初始化操作。初始化过程的主要操作是执行静态代码块和初始化静态域。在一个类被初始 化之前,它的直接父类也需要被初始化。但是,一个接口的初始化,不会引起其父接口的初始化。在初始化的时候,会按照源代码中从上到下的顺序依次执行静态代 码块和初始化静态域。考虑下面的代码:
public class StaticTest {  
   public static int X = 10;  
   public static void main(String[] args) {      
      System.out.println(Y); //输出60  
   }  
   static {      
      X = 30;  
   } 
   public static int Y = X * 2;
}

在上面的代码中,在初始化的时候,静态域的初始化和静态代码块的执行会从上到下依次执行。因此变量X的值首先初始化成10,后来又被赋值成30;而变量Y的值则被初始化成60。

Java类和接口的初始化只有在特定的时机才会发生,这些时机包括:

    创建一个Java类的实例。如

    MyClass obj = new MyClass()

    调用一个Java类中的静态方法。如

    MyClass.sayHello()

    给Java类或接口中声明的静态域赋值。如

    MyClass.value = 10

    访问Java类或接口中声明的静态域,并且该域不是常值变量。如

    int value = MyClass.value

    在顶层Java类中执行assert语句。
通过Java反射API也可能造成类和接口的初始化。需要注意的是,当访问一个Java类或接口中的静态域的时候,只有真正声明这个域的类或接口才

会被初始化。考虑下面的代码:

class B {  
   static int value = 100;  
   static {      
      System.out.println("Class B is initialized."); //输出  
   }
}

class A extends B {  
   static {      
      System.out.println("Class A is initialized."); //不会输出  
   }
}
public class InitTest {  
   public static void main(String[] args) {      
      System.out.println(A.value); //输出100  
   }
}
在上述代码中,类InitTest通过A.value引用了类B中声明的静态域value。由于value是在类B中声明的,只有类B会被初始化,而类A则不会

被初始化。但如果将类B修改如下:

class B {  

   final static int value = 100;  

   static {      

      System.out.println("Class B is initialized."); //输出  

   }

}

则最后的输出结果又当如何呢?

创建自己的类加载器

在 Java应用开发过程中,可能会需要创建应用自己的类加载器。典型的场景包括实现特定的Java字节代码查找方式、对字节代码进行加密/解密以及实现同名 Java类的隔离等。创建自己的类加载器并不是一件复杂的事情,只需要继承自java.lang.ClassLoader类并覆写对应的方法即可。 java.lang.ClassLoader中提供的方法有不少,下面介绍几个创建类加载器时需要考虑的:

    defineClass() :这个方法用来完成从Java字节代码的字节数组到java.lang.Class的转换。这个方法是不能被覆写的,一般是用原生代码来实现的。
    findLoadedClass() :这个方法用来根据名称查找已经加载过的Java类。一个类加载器不会重复加载同一名称的类。
    findClass() :这个方法用来根据名称查找并加载Java类。
    loadClass() :这个方法用来根据名称加载Java类。
    resolveClass() :这个方法用来链接一个Java类。

这里比较 容易混淆的是findClass()方法和loadClass()方法的作用。前面提到过,在Java类的链接过程中,会需要对Java类进行解析,而解 析可能会导致当前Java类所引用的其它Java类被加载。在这个时候,JVM就是通过调用当前类的定义类加载器的loadClass()方法来加载其它 类的。findClass()方法则是应用创建的类加载器的扩展点。应用自己的类加载器应该覆写findClass()方法来添加自定义的类加载逻辑。 loadClass()方法的默认实现会负责调用findClass()方法。

前面提到,类加载器的代理模式默认使用的是父类优先的策略。这个策略的实现是封装在loadClass()方法中的。如果希望修改此策略,就需要覆写loadClass()方法。

下面的代码给出了自定义的类加载的常见实现模式:

public class MyClassLoader extends ClassLoader {  

   protected Class<?> findClass(String name) throws ClassNotFoundException {     

      byte[] b = null; //查找或生成Java类的字节代码      

  return defineClass(name, b, 0, b.length);  
   }
}
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    [jacob]---------------->java 实现 word 转换为html jacob

    - **初始化Jacob**:首先,我们需要在Java程序中加载Jacob库,通过`com.jacob.com.ComThread.initAutomation()`方法初始化Jacob。 - **创建Word实例**:使用`ActiveXComponent`类创建一个Word应用实例。 - **打开...

    Java类加载器:静态变量初始化.docx

    Java 类加载器静态变量初始化机制详解 Java 类加载器是 Java 语言的核心组件之一,负责将 Java 字节码文件加载到内存中,以便 JVM 可以执行它们。在 Java 中,类加载器是通过委派机制来实现的,即一个类加载器可以...

    分析Java类加载全过程

     加载-&gt;链接(验证+准备+解析)-&gt;初始化(使用前的准备)-&gt;使用-&gt;卸载  其中加载(除了自定义加载)+链接的过程是完全由jvm负责的,什么时候要对类进行初始化工作(加载+链接在此之前已经完成了),jvm有严格的...

    java的ClassLoader类加载器机制

    在 Java 中,类加载器的层次结构是固定的,引导类加载器 -&gt; 扩展类加载器 -&gt; 系统类加载器。每个类加载器都有其特定的加载范围和优先级,确保了 Java 类的正确加载和使用。 类加载器的工作原理可以分为三个阶段:...

    深入研究Java类加载机制 深入研究Java类加载机制

    类加载过程涉及到类的加载、链接(验证、准备、解析)、初始化等阶段,并且这一过程是由类加载器系统完成的。 #### 二、类加载器系统 Java中的类加载器系统主要包括以下几种类型的类加载器: 1. **Bootstrap ...

    ckeditor-java-core-3.5.3

    在"ckeditor-java-core-3.5.3"压缩包中,通常会包含CKEditor的Java API接口文档,这些接口允许开发者控制编辑器的行为,如初始化编辑器、设置和获取编辑器内容、处理用户事件等。此外,还可能有CKEditor的核心...

    JAVA-JVM-01类加载机制

    5. 初始化:执行类的静态初始化代码块,为静态变量赋予指定的初始值。 6. 使用:类被成功加载后,可以被程序正常调用。 7. 卸载:在某些情况下,JVM会卸载不再使用的类,释放内存资源。 类加载器在类加载过程中起着...

    解析Java虚拟机中类的初始化及加载器的父委托机制共14页

    Java虚拟机(JVM)是Java程序运行的核心,它的内部机制包括了类的加载、链接、初始化等关键过程。在Java编程中,了解这些过程对于优化程序性能、理解和解决类加载问题至关重要。本文将深入探讨Java虚拟机中的类初始...

    Java虚拟机(加载,链接,初始化)1

    在JVM中,类的加载、链接和初始化是至关重要的三个步骤,它们共同确保了程序的正常运行。 1. **加载(Loading)** 加载阶段是JVM寻找和导入类或接口的二进制数据的开始。这个过程通常是从类路径下的`.class`文件中...

    Java虚拟机类加载顺序

    类加载过程主要包括三个阶段:加载、链接和初始化。 - **加载**:类加载器读取类文件数据并转换为二进制流加载到内存中。 - **链接**:包括验证、准备和解析三个子阶段。验证确保加载的类符合Java语言规范;准备...

    Java加壳源码-自定义类加载器

    在Java中,类加载过程包括加载、验证、准备、解析和初始化五个阶段。类加载器主要负责加载阶段的工作,它查找并加载指定类的字节码文件,然后由JVM完成后续的验证和执行。默认的类加载器包括Bootstrap ClassLoader...

    快捷方式工具类

    2. 在Java中使用`System.loadLibrary`加载动态链接库。 3. 定义JNI方法签名,并在Java中调用对应的C++函数,传入必要的参数,如快捷方式的目标路径和工作目录。 4. C++函数调用Windows API,创建快捷方式并保存到...

    Java类加载机制

    它负责将 Java 类的字节码(.class 文件)加载到 Java 虚拟机(JVM)中,并确保类的正确加载、链接和初始化。本文将深入探讨 Java 类加载机制的核心概念和技术细节,帮助开发者更好地理解 Java 类加载的过程,以及...

    Java面试题-内存+GC+类加载器+JVM调优.pdf

    类加载机制可以分为三个阶段:加载、链接和初始化。 在加载阶段,JVM 会将类文件加载到内存中。在链接阶段,JVM 会将类文件与其他类文件链接起来。在初始化阶段,JVM 会将类文件初始化,以便应用程序可以使用这些类...

    java类加载器1

    加载阶段是查找或生成字节码,链接阶段则包括验证、准备和解析,初始化则是执行类构造器`&lt;clinit&gt;`方法。 2. `java.lang.ClassLoader`: 这是所有Java类加载器的基类,提供了如`loadClass()`, `findClass()`, `...

    java 类从哪个jar包加载的

    5. **初始化**:执行类的初始化方法 `&lt;clinit&gt;` ,这包括执行静态初始化块和对静态变量的赋值。 在Java中,类加载器有层次结构,主要由Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader(也称为...

    深入研究Java的类加载机制.pdf

    类加载机制的过程可以分为三个阶段:加载、链接和初始化。 加载阶段:在加载阶段,Java虚拟机将.class文件加载到内存中,并将其转换为机器可执行的代码。加载阶段是类加载机制的第一阶段。 链接阶段:在链接阶段,...

    使用Eclipse做JAVA SE开发从零到一的过程

    - **加载外部 JAR**:通过 “Project” -&gt; “Properties” -&gt; “Java Build Path” -&gt; “Libraries” 添加外部JAR文件。 以上步骤涵盖了使用Eclipse进行JAVA SE开发的基本流程,从安装配置到项目的创建与管理,希望...

Global site tag (gtag.js) - Google Analytics