类加载机制概述
- 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是类加载机制
Hotspot类加载器策略是采用懒加载策略
类加载的时机
类的声明周期
类的初始化例子:
-
遇到new,getstatic,putstatic或者invokestatic这4个字节码指令时,如果类么有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或者设置一个类的静态字段(被final修饰,已经在编译器把结果放入常量池中的静态字段除外)的时候,以及调用一个类的静态方法的时候。
-
使用Java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化
-
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
-
当虚拟机启动的时候,用户需要制定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类
类不被初始化的例子:
- 通过子类引用父类的静态字段
- 通过数组定义来引用类
- 调用类的常量
类加载过程:
类加载过程-加载:
- 通过一个类的全限定类名获取定义此类的二进制流(加载源:文件、网络、二进制流)
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的class对象,作为这个类的各种数据的访问入口
类加载过程-验证:
验证是连接的第一步,这一阶段的目的是为了确保Class文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 文件格式验证(魔数、版本号、常量池…)
- 元数据验证(父类)
- 字节码验证
- 符号引用验证
类加载过程-准备:
- 准备阶段正式为类变量分配内存并且设置变量的初始值(并非是我们指定的值,是默认值)。这些变量使用的内存都将在方法区中进行分配
类加载过程-解析:
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程
- 类或者接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
类加载过程-初始化:
- 初始化是类加载的最后一步
- 初始化是执行方法的过程
类加载器
- 虚拟机的设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字符流”,这个动作放到Java虚拟机外部去实现,以便让程序自己决定如何去获取所需要的类。实现这个动作的代码模型称之为类加载器
- 只有被同一个类加载器加载的类才可能会相等。相同的字节码被不同的类加载器加载的类是不相等的。
类加载器分类
-
启动类加载器:这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库。用户无法直接使用。
-
扩展类加载器:这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。用户可以直接使用。
-
应用程序类加载器:这个类由sun.misc.Launcher$AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。
-
自定义加载器:用户自己定义的类加载器。
自定义类加载器的优势
- 类加载器是Java语言的一项创新,也是java语言流行的重要原因之一,它最初的设计是为了满足java Applet的需求而开发出来的
- 高度的灵活性
- 通过自定义的类加载器可以实现热部署
- 代码加密
双亲委派模型
双亲委派模型实现 java.lang.ClassLoader