虚拟机类加载机制
共 5483字,需浏览 11分钟
·
2020-11-24 13:22
PS:虽然最近更新频率低了,但是思危一直没有停止,共勉!
概述
类加载的时机
类加载的过程
类加载器
类加载器分类
双亲委托模型
概述
类加载的时机
遇到 new、getstatic/putstatic、invokestatic 指令时,如果该类没有被初始化,则需对类进行初始化,上面指令分别对应使用 new 关键字进行对象实例化、读取或设置一个静态属性、调用静态方法,具体可以使用
javap
命令查看字节码文件的实现来验证;使用 java,lang.reflect 对类进行反射调用的时候,如果该类没有被初始化,则需对类进行初始化;
当初始化一个类的时候,如果其父类还没有进行初始化,则先进行该类父类的初始化;
当 JVM 启动时,用户指定要启动的主类,比如还有 main 方法的类,JVM 会先初始化这个类;
当使用 JDK 1.7 的动态语言支持时,如果 java.lang.invoke.MethodHandler 实例最后解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic,如果这些句柄对应的类没有进行初始化,则需先对其进行初始化,至于 MethodHandler 可以理解为反射的另一种形式。
类加载的过程
加载
从 zip 包中获取,最终成为 jar、war 格式的基础。
从网络中获取,典型应用就是 Applet。
运行时生成,典型应用就是动态代理技术,在 java.lang.reflect.Proxy 中就是使用 ProxyGenerator.generatrProxyClass 来为特定接口生成形如 Proxy 的代理类的二进制字节流。
其他文件生成、数据库中获取等。
链接
验证:确保 class 文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身的安全,从整体来看,验证阶段主要包括文件格式验证、元数据验证、字节码验证和符号引用验证,具体验证内容可以自行查看 Java 虚拟机规范。
准备:正式为类变量分配内存并设置类变量的初始值,这个初始值一般是数据类型的初始值,而不是真正代码中初始化的值,如 int 初始值就是 0,这些类变量使用的内存都将在方法区进行分配,类变量指的就是被 static 关键字修饰过的变量。
解析:JVM 将常量池中的符号引用替换为直接引用,这里的符号引用就是在前面验证阶段提到的符号引用验证中的符号引用
初始化
初始化阶段就是执行类构造器
() 方法的过程。 <clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块 statuc{} 中的语句合并产生的,编译器收集顺序和源码中语句顺序一致,如静态语句块中只能访问定义在它之前的变量,定义在它后面的变量只能复制不能访问。 初始化一个类时,如果父类还没初始化,则先进行父类的初始化。
JVM 会保证一个类的
() 方法在多线程环境中被正确的加锁、同步。 当访问一个 Java 类的静态域时,只有真正声明这个类才会被初始化。
类加载器
java.lang.ClassLoader
类的一个实例,前面知道在类的加载阶段会通过类加载器来加载 class 文件,也就是可以通过一个类的全限定名来获取定义此类的二进制字节流 ,这个动作的代码实现就是类加载器的实现。类加载器分类
启动类加载器(Bootstrap ClassLoader):一般使用 C++ 语言实现,具体由 JVM 实现。
其他类加载器:使用 Java 语言实现,独立于 JVM 之外,且都是 java.lang.ClassLoader 的一个实例,如 Android 中的 DexClassLoader。
启动类加载器(Bootstrap ClassLoader):负责加载的是 JAVA_HOME\lib 下的类库,或者被-Xbootclasspath 参数所指定的路径中的并且是 JVM 识别的(仅按照文件名识别,如 rt.jar 等,名字不符合的类库即使放在 lib 目录中也不会被加载),启动类加载器无法被JAVA程序直接应用。
扩展类加载器(Extension ClassLoader):这个类加载器由
sum.misc.Launcher$ExtClassLoader
实现,负责加载 JAVA_HOME\lib\ext 下的类,或者是被 java.ext.dirs 系统变量所指定的路径下的类库,可以直接使用扩展类加载器。应用程序类加载器(Application ClassLoader):这个类加载器由
sun.misc.Launcher$AppClassLoader
实现,这个类加载器是 ClassLoader 中的getSystemClassLoader() 方法的返回值, 一般也称它为系统类加载器,负责加载用户类路径(ClassPath) 下所指定的类库, 开发者可以直接使用这个类加载器, 如果应用程序中没有自定义过自己的类加载器, 这个就是程序中默认的类加载器。
双亲委托模型
来实现, 而是都使用组合(Composition) 关系来复用父加载器的代码,这种方式并不是一个强制性的约束模型, 而是 Java 设计者推荐给开发者的一种类加载器实现方式。
1protected Class> loadClass(String name, boolean resolve)throws ClassNotFoundException{
2 synchronized (getClassLoadingLock(name)) {
3 // 1. 检查是否已经加载过类
4 Class> c = findLoadedClass(name);
5 if (c == null) {
6 long t0 = System.nanoTime();
7 try {
8 if (parent != null) {
9 // 2. 如果没有被加载,则调用父类的类加载器加载
10 c = parent.loadClass(name, false);
11 } else {
12 // 3. 如果父类的类加载器不存在,则直接使用启动类加载器进行加载
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
23 long t1 = System.nanoTime();
24 // 4. 父类或启动类加载器都没有加载该类,则调用自己的也就是子类类加载器的findClass犯法进行类加载
25 c = findClass(name);
26 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
27 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
28 sun.misc.PerfCounter.getFindClasses().increment();
29 }
30 }
31 if (resolve) {
32 resolveClass(c);
33 }
34 return c;
35 }
36}