`
geeksun
  • 浏览: 953021 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java类加载机制

阅读更多

类的加载属于Java中的JVM的底层工作。

 

类加载包括类的生命周期中加载、连接、初始化三个阶段。

在加载阶段,JVM在做什么工作?

简单地说,就是找到需要加载的类并把类的信息加载到jvm的方法区,然后在堆区实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。

 

当执行Class中的下列代码的时候,会做下面的事情:

User user = new User();

 

1. 在方法区(Method Area)中找User的class信息,如果方法区存在User的class信息,则根据User的class信息构造出User对象,存入heap中,并把指向这个User对象的引用句柄user放在stack中。

2. 如果在方法区不存在User的class信息,则在classpath路径查找User.class文件,查找的时候全匹配全名查询,比如User.java的package是com.wiki.domain,就会在classpath寻找/com/wiki/domain/User.class,找到目标class文件后,加载User的class信息(获取类的二进制字节流),把class信息放在方法区(Method Area)中,并生成User的Class对象,把User的Class对象放入堆(Heap)中,User对象的引用user放在栈(stack)中,这样Class对象就可以访问方法区中的信息。

生成的Class对象,是所有User对象的模板,当User对象想获取类信息的时候,就通过这个代表该类的唯一的Class对象访问方法区的信息。

3. 类的加载器可以使用系统提供的,也可以使用自定的类加载器。

4. 类对象的唯一性由类加载器和这个类本身来确定其在Java虚拟机中的唯一性。也就是说,即使两个类对象来源于同一个类Class文件,但是类加载器不同,这两个类对象就不是同一类的对象。

 

 

类加载器的分类

1.启动类加载器:Bootstrap ClassLoader, 一般使用Hotspot实现的jvm,负责加载 ${jdkpath}/jre/lib下的class, 所有java.*开头的类都由Bootstrmp ClassLoader加载,java程序不能直接调用Bootstrap Classloader。

2.扩展类加载器:Extension ClassLoader, 此加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载 ${jdbpath}/jre/lib/ext下的class, 开发都可以直接使用扩展类加载器。javax.*开头的类都由该加载器加载。

3.应用程序类加载器:Application ClassLoader,该加载器由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径(ClassPath)指定的类,开发者可以直接使用该类加载器。如果没有自定义类加载器,程序中默认的类加载器就是应用程序类加载器。

4.当需要时,也可以使用自定义类加载器。

 

 这几种类加载器的层次关系如下图所示:



 

这种层次关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前层类加载器的父加载器,当然,它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。该模型在 JDK1.2 期间被引入并广泛应用于之后几乎所有的 Java 程序中,但它并不是一个强制性的约束模型,而是 Java 设计者们推荐给开发者的一种类的加载器实现方式。

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是 Java 类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。例如,类java.lang.Object 类存放在 ${jdkpath}/jre/lib下的 rt.jar 之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了 Object 类在程序中的各种类加载器中都是同一个类。

 

类加载的几种方式

1. Class.forName(className)

2. ClassLoad.loadClass(className)

3. JVM加载Main方法的主类 

 

Java中的绑定

绑定指方法的调用与方法所在的类(方法主体)的绑定,分为静态绑定和动态绑定(运行时绑定)。

静态绑定:

在程序执行前方法已经被绑定,是编译期的绑定,像User user = new User(); 就是静态绑定。

动态绑定:

在程序执行时绑定,是运行时的绑定,比如像 Person p = Factory.createPerson(type); 是运行时的绑定。

 

 连接

连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。

  1. 验证:当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。
  2. 准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的:
    • 基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
    • 引用类型的默认值为null。
    • 常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。
  3.  解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。那么什么是符号引用,什么又是直接引用呢?我们来举个例子:我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:比如安徽省黄山市余暇村18号张三,通过这个信息我们就能找到这个人了。这里,123456790就好比是一个符号引用,而安徽省黄山市余暇村18号张三就是直接引用。在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。这里show就是符号引用,而c17164就是直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。

连接阶段完成之后会根据使用的情况(直接引用还是被动引用)来选择是否对类进行初始化。

 

 

 

 

  • 大小: 14.5 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics