类加载
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
其中类加载过程包括加载、验证、准备、解析和初始化五个阶段。
加载:导入class文件,通过一个类的全限定名来获取描述此类的二进制字节流
验证:保证输入的字节流能够正确解析并存储在方法区之内(文件格式,元数据,字节码,符号引用)
准备:为一些静态变量去分配内存,初始化变量(初始值)
解析:把类当中的符号引用转换成直接引用
初始化:为静态变量赋值,执行类的初始化
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照顺序按部就班进行,而解析阶段则不一定。它在某种情况下可以在初始化阶段是之后再开始,这也是为了支持Java的动态绑定。
类加载的时机
当我们对类主动引用的时候,都会对类初始化,而类初始化必定带有类加载。
- 遇到new、get static 和 put static 或 invoke static 这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。
- 对类进行反射调用(class.forName)的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化类的父类还没有进行过初始化,则需要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全部都完成了初始化)
- 虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。
- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
类加载器
类加载器是类加载流程的实现者,它将 class 文件二进制数据放入方法区内,然后在堆内(heap)创建一个 java.lang.Class 对象,Class 对象封装了类在方法区内的数据结构,并且向开发者提供了访问方法区内的数据结构的接口。
类加载器分类
加载器 | 实现 | 加载路径 | 备注 |
---|---|---|---|
启动类加载器 | C++ | 加载jre/lib下的rt.jar等核心包 | |
扩展类加载器 | Java实现 | 加载jre/lib/ext扩展目录的包 | Lancher的内部类 |
应用加载器 | Java实现 | 加载classpath的包 | Lancher的内部类 |
自定义类加载器 | Java实现 | 自定义路径 |
类继承结构
类 | 职责 | 关键方法 |
---|---|---|
ClassLoader | 1)实现双亲委派;2)加载类字节码到内存; | loadClass(), defineClass() |
URLClassLoader | 根据URL定位资源,加载指定jar中的class到内存中 | findClass(), getResource(), getResources() |
AppClassLoader | 定义应用类加载器的资源路径(System.getProperty(“java.class.path”)) | |
ExtClassLoader | 定义扩展类加载器的资源路径(System.getProperty(“java.ext.dirs”)) |
类加载机制
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 若所有都找不到,抛出ClassNotFoundException
双亲委派模型作用
1.避免类的重复加载
2.保护程序安全,防止核心java语言环境被破坏
双亲委派模型破坏史
1.第一次破坏
由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。为了向前兼容,JDK 1.2之后的java.lang.ClassLoader添加了新的protected方法findClass(),在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法唯一逻辑就是去调用自己的loadClass()。
2.第二次破坏
双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的同一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。
如果基础类又要调用回用户的代码,那该么办?
为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
3.第三次破坏
双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简答的说就是机器不用重启,只要部署上就能用。
OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构。
1)将java.*开头的类委派给父类加载器加载。
2)否则,将委派列表名单内的类委派给父类加载器加载。
3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7)否则,类加载器失败。