一、概念
1. 字节码:
a. 以前的代码(例如C++)编译后是本地机器码,不同的机器编译出来的机器码是不同的
b. Java编译后是相同的字节码文件,即存放在.class文件中的二进制文件,JVM可以执行任何符合规范的字节码文件
2. 类加载机制:将描述类的二进制数据从.class文件读入内存的不同区域中,并对数据进行校验、转换、解析和初始化,最终形成可以让JVM直接使用的Java类型
3. 类加载结果:在堆区创建了一个java.lang.Class对象,用来封装类在方法区内的数据结构
4. 允许预加载
5. 可控性强,可用自定义的类加载器完成加载
二、加载.class文件的方式
1. 从本地系统中直接加载
2. 通过网络下载.class文件
3. 从zip,jar等归档文件中加载.class文件
4. 从专有数据库中提取.class文件
5. 将Java源文件动态编译为.class文件
三、类的生命周期:加载->(验证->准备->解析)链接->初始化->使用->卸载
1. 加载,如上所述
2. 链接
a. 验证:确保被加载的类的正确性
b. 准备:为类的静态变量分配内存,而不是实例变量;并初始化为数据类型的默认值,而不是代码中赋予的值;为static final赋代码中的值
c. 解析:把类中的符号引用改为直接引用
3. 初始化:为静态变量初始化实际的值,JVM对类和类变量初始化
4. 卸载
四、类加载器ClassLoader:通过一个类的全限定名来获取描述此类的二进制字节流,这个动作在JVM外部实现,以便让程序自己决定如何去获取所需的类
1. 启动类加载器,BootStrap ClassLoader
a. 由C++实现,是虚拟机的一部分,其他类加载器都由Java实现,以便让程序自己决定如何去获取所需的类
b. 加载JAVA_HOME/lib目录中的类(JAVA_HOME指明JDK安装路径),或者-Xbootclasspath参数所指定的路径中的类,并且被JVM按照文件名识别的,例如JRE/lib/rt.jar
2. 扩展类加载器,Extension ClassLoader,加载JAVA_HOME/lib/ext目录下的类,或者被java.ext.dirs系统变量所指定的路径下的类。关键词:ext
3. 应用程序类加载器,Application ClassLoader
a. 程序中默认的类加载器,除非自定义了类加载器
b. 加载用户路径(ClassPath)上的类
c. 是ClassLoader中,getSystemClassLoader方法的返回值
4. 用户自定义类加载器,User ClassLoader,加载编程人员制定的目录下的类
5. 从JVM的角度来看,类加载器分为两类:BootStrap类加载器(JVM里的C++实现)、其他类加载器(外部Java实现)。
6. 从开发人员角度,分为3类:启动类加载器、扩展类加载器、应用类加载器
五、类与类加载器的关系
1. 类加载器虽然只用于实现类的加载动作,但它在Java程序中的作用,却远远不限于类加载阶段
2. 类本身+加载这个类的类加载器=Java虚拟机中的唯一性,用于比较类是否相等
六、类的加载方式
1. 命令行启动应用时,由JVM初始化加载
2. 通过Class.forName()方法动态加载:将.class文件加载到JVM中,执行类中的static块
3. 通过ClassLoader.loadClass()方法动态加载,只加载.class文件
七、双亲委派模型
1. 如果一个类加载器收到了类加载的请求,它首先把请求委托给父加载器去完成,依次向上,只有父加载器无法完成时,才由子加载器完成。
2. 上述过程完成后,如果AppClassLoader加载失败,会抛出ClassNotFoundException
3. 作用:解决了基础类的统一问题,防止内存中出现重复的字节码,保证Java程序安全执行。例如用户编写了java.lang.Object类,并放在ClassPath中,系统中会有多个Object类,就混乱了。
4. 类加载器间的父子关系不是继承,而是组合
5. 实现过程:即java.lang.ClassLoader中的loadClass()方法,递归调用父加载器,最终是findClass()方法完成加载操作
6. 自己写一个类加载器去加载java.lang.Object或者java.lang.String类
八、破坏双亲委派模型
1. 双亲委派模型不是强制要求,而是建议性的类加载机制
2. 双亲委派模型的缺点:一般情况下,基础类是被用户代码调用的;特殊情况下,基础类会调用用户的代码,例如JNDI服务
3. 线程上线文类加载器(Thread Context ClassLoader),可以通过java.lang.Thread类中的setContextClassLoader()方法设置,父加载器请求子加载器去完成加载
4. 破坏双亲委派机制的场景:
a. 涉及SPI的加载动作,例如JNDI、JDBC、JBI
b. 热拔插、热部署、模块化,增减模块时不需要重启,只需要把此模块和类加载器一起去掉
九、自定义类加载器
1. 应用场景:用网络传输加密的字节码
2. 使用方法:继承ClassLoader类,并重写findClass()方法