Java类加载器

来源:互联网 发布:centos php版本切换 编辑:程序博客网 时间:2024/05/17 22:12

Java虚拟机载入Java类的步骤

Java文件经过编译器编译后变成字节码文件(.class文件),类加载器(ClassLoader)读取.class文件,并且转换成java.lang.Class 的一个实例,最后通过newInstance方法创建该类的一个对象。ClassLoader的作用就是根据一个类名,找到对应的字节码,根据这些字节码定义出对应的类,该类就是java.lang.Class的一个实例。


类的加载过程
JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化三个步骤:
这里写图片描述

加载:查找并加载类的二进制数据,此阶段完成的功能

1)通过类的全限定名来定义此类的二进制字节流2)将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构3)在内存中生成代表此类的java.lang.Class对象,作为该类访问入口

验证:链接阶段第一步,目的是确保Class文件中的字节流符合jvm要求,不会危害虚拟机安全。大致完成以下四个校验动作:文件格式验证,元数据验证,字节码验证,符号引用验证
准备:链接阶段第二步,正式为类变量分配内存并设置变量的初始值(仅包含类变量,不包含实例变量)
解析:链接阶段的第三部,虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等

(符号引用是一个字符串,给出了被引用的内容的 名字并且可能会包含一些其他关于这个被引用项的信息,直接引用,即指向实际的内存地址)

初始化:类的初始化是类加载过程的最后一步,在该阶段,才真正意义上的开始执行类中定义的java程序代码,该阶段会执行类构造器

准备阶段与初始化:private static int a =0 ,准备阶段给a分配内存,此时a等于int默认值0 。初始化阶段才把真正的值10赋给a,a=10)

使用:使用该类所提供的功能
卸载:从内存中释放


类的初始化:

类什么时候被初始化:
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“xxx”))
5)初始化一个类的子类(首先会初始化子类的父类)
6)JVM启动时标明的子类,即文件名和类名相同的那个类

类的初始化步骤:
1)如果这个类还没有被直接加载和链接,先进行加载和链接
2)如果这个类存在直接父类,并且这个类还没有被初始化(在一个类加载器中类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(static变量和static块),那就依次执行这些初始化语句

获取class文件途径
Java类可以动态的被加载到内存,这是Java的一大特点,也称运行时绑定,或动态绑定
1)从ZIP包中读取(jar,war,ear)
2)从网络中获取(applet)
3)运行时计算生成,典型场景Java动态代理技术
4)从其他文件中生成,(jsp生成对应class类)


加载器:
JVM的类加载是通过ClassLoader及其子类完成,类的层次关系和加载顺序如下图:
这里写图片描述

Bootstrap classLoader: (引导类加载器,初始类加载器),用来加载Java核心库(rt.jar中的jdk类文件),原生代码实现。Bootstrap加载器没有任何父类加载器,调用getClassLoader()会返回空

extensions class loader (扩展器加载器):加载Java扩展库(jre.lib.ext目录下或者java.ext.dirs系统属性定义的目录下加载类)

app class loader( system class loader 系统类加载器):根据Java应用的类路径(classpath)来加载Java类,可以通过ClassLoader.getSystemClassLoader()获取它.

custom class loader:通过继承java.lang.ClassLoader 实现自定义的类加载器

类的加载过程:

加载过程中会先检查类是否已被加载,检查自底而上,只要某个加载器已加载就视为已加载此类,保证此类只被所有classloader加载一次。而加载的顺序是自顶向下,也就是由上层逐层尝试加载此类。

真正完成类的加载工作通过调用defineClass 实现,而启动类的加载过程通过调用LoadClass实现,前者称为一个类的定义加载器(defining loader),后者称为初始化加载器(initiating loader).

方法loaderClass 抛出java.lang.ClassNotFindException 异常

方法defineClass ()抛出java.lang.NoClassDefFoundError。

类加载器在成功加载某个类后,会将得到的java.lang.Class实例缓存起来,下次再请求该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。即对于一个类加载器实例来说相同全名的类只加载一次,即loadClass方法不会被重复调用.

类的加载通过调用java.lang.classLoader的loaderClass 方法,loadClass()调用findClass 来定位相应类的字节码。(findLoadClass()检查该类是否已被加载)

双亲委派

双亲委派模型是一种组织类加载器之间的规范:

如果一个类加载器收到了类加载的请求,首先将请求委派给父类加载器去完成,(app classloader->extensions class loader->bootstrap class loader),只有父类加载器无法完成这个加载请求,才会交给子类加载器去尝试加载

Java类随着它的类加载器一起具备了带有优先级的层次关系,这是十分必要的.如java.lang.Object,它存放在/jre/lib/rt.jar中.他是所有Java 类的父类,无论哪个类加载都要加载这个类,最终所有加载请求都汇总到bootstrap class loader 。因此object 类会由bootstrap classloader 加载,所以加载的都是同一个类.如果不使用双亲委派模型,由各个加载器自行加载,系统中就会出现不止一个Object类

可见性机制
子类加载器可以看到父类加载器加载的类,反之不行。

单一性机制
父加载器加载过的类不能被子加载器加载二次。


Java程序动态扩展

Java的连接模型允许用户运行时扩展应用程序,既可以通过当前虚拟机中预定义的加载器加载编译时已知的类或者接口,又允许用户自行定义类装载器,在运行时动态扩展用户的程序。通过用户自定义的类装载器,程序可以装载在编译时并不知道或者尚未存在的类或者接口,并动态连接他们并进行有选择的解析。

实现途径

1)java.lang.Class.forName

Class.forName()是一个静态方法,最常见的是Class.forName(String name);根据传入两日的全限定名,返回一个class 对象,该方法在将class 文件加载到内存的同时,会执行类的初始化。

 与loadClass()区别 : laodClass(),是CLassLoader对象的一个实例方法。 该方法将class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用才进行初始化。 该方法因为需要得到一个classLoader对象,所以可以根据需要制定使用哪个类加载器。

Class.forName(String name,boolean initialize,ClassLoader loader) throws ClassNotFoundException

initialize :表示在加载时是否完成初始化工作
loader:指定加载器

有些场景下需要将initialize设置为true来强制加载同时完成初始化。例如利用DriverManager进行jdbc驱动程序类注册问题

2)通过用户自定义类加载器


context ClassLoader

线程上下文类加载器。类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader dl)
用来获取和设置线程的上下文加载器,如果没有设置,线程将继承父线程的山西该文加载器。Java应用运行的初试线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

使用线程上下文加载器,可以在执行线程中抛弃双亲委派加载链模式,使用线程上下文里的类加载器。

一些问题:
考考:http://blog.csdn.net/zhoudaxia/article/details/35824249
1.由不同类加载器加载的指定类还是相同的类型吗

在Java中,一个类用其完全匹配类名作为标识,这里的完全匹配类名包括包名和类名。在jvm中一个类用其全名和一个加载器的实例作为唯一标示,不同类加载器加载的类被放置在不同的命名空间

2.Class.forName(String name) 触发哪个类加载器进行类加载行为

默认会使用调用类的类加载器进行类加载

3.编写自定义加载器时,如果没有设定父加载器,那么父加载器是?

系统类加载器

4.自定义加载器,父加载器强制设为null

会自动将bootstrap classLoader 设为自定义加载器的父类加载器

5,自定义加载器需要注意

1)尽量不要覆写loadClass(..)中委派逻辑2)正确设置父加载器3)保证findClass(String name)方法的逻辑正确性

6获取系统类加载器加载那些路径上类

1)UrlCLassLoader(ClassLoader.getSystemClassLoader()).getUrls()2)System.getProperty("java.class.path")

7.获取扩展类加载器加载那些路径上类

UrlCLassLoader(ClassLoader.getSystemClassLoader().getParent()).getUrls()