jvm之类加载(3)详解

类的加载

  • 类的加载的最终产品是位于内存中的Class对象
  • Class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口

类加载器

  • 有两种类型的类加载器
    • java虚拟机自带的加载器
      • 根类加载器(Bootstrap)
      • 扩展类加载器(Extension)
      • 系统(应用)类加载器(System)
    • 用户自定义的类加载器
      • java.lang.ClassLoader的子类
      • 用户可以定制类的加载方式
  • 类的加载并不需要等到某个类被“首次主动使用”时再加载它
  • JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)
  • 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

如下代码

package com.finedo.jvm.classloader;
public class Mytest1 {
    public static void main(String[] args) {
            System.out.println(MyChild1.str); 
    }
}

class MyParent1 {
    public static  String str = "hello world";
    static {
        System.out.println("MyParent1 static block");
    }
}
class MyChild1 extends MyParent1 {
    public static String str2 = "welcome";
    static {
        System.out.println("MyChild1 static block");
    }
}
运行结果:
MyParent1 static block
hello world
  • 上面代码并没有对MyChild1 主动使用,我们通过追踪类加载信息如下
    在这里插入图片描述
    我们发现MyChild1 已被JVM加载。

类的验证

  • 类被加载后,就进入连接阶段。连接就是将已经读入到内存的二进制数据合并到虚拟机的运行时环境中去
  • 类的验证内容
    • -类文件的结构检查
    • -语义检查
    • 字节码验证
    • 二进制兼容性的验证

类的初始化

  • 类的初始化步骤
    • 假如这个类还没有被加载和连接,那就先进行加载和连接
    • 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类
    • 假如类中存在初始化语句,那就依次执行这些初始化语句

类的初始化时机

  • 只有当程序访问的静态变量或静态方法确实在当前类或当前接口定义时,才可以认为是对类或接口的主动使用
  • 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化

如下代码:

package com.finedo.jvm.classloader;

class CL {
    static {
        System.out.println("Class CL");
    }
}
//调用ClassLoader类的loadclass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
public class MyTest12 {
    public static void main(String[] args) throws Exception{
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class clazz = loader.loadClass("com.finedo.jvm.classloader.CL");
        System.out.println(clazz);
        System.out.println("---------------");
        clazz = Class.forName("com.finedo.jvm.classloader.CL");
        System.out.println(clazz);
    }
}

运行结果:
class com.finedo.jvm.classloader.CL
---------------
Class CL
class com.finedo.jvm.classloader.CL
  • 由上面代码输出结果我们发现调用ClassLoader类的loadclass方法加载一个类,并不是对类的主动使用,不会导致类的初始化,而通过反射(对类的主动使用)Class.forName(“com.finedo.jvm.classloader.CL”)会导致CL类被初始化

类加载器父亲委托机制

  • 在父亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器
  • 加载原理图:
    jvm之类加载(3)详解_第1张图片
  • Bootstrap ClassLoader /启动类加载器
    - $JAVA_HOME中jre/lib/rt.jar里面所有的class,由c++实现,不是ClassLoader子类
  • Extension ClassLoader /扩展类加载器
    • 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
  • App ClassLoader/ 系统类加载器
    • 负责加载classpath中指定jar包及目录中的class
  • 若有一个类加载器能够成功加载Test类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器
  • 我们可以通过以下代码获得相应的类加载器
    • 获得当前类的ClassLoader(clazz.getClassLoader())
    • 获得当前线程上下文的ClassLoader(Thread.currentThread().getContextClassLoader())
    • 获得系统的ClassLoader(ClassLoader.getSystemClassLoader())
    • 获得调用者的ClassLoader(DriverManager.getCallerClassLoader())

如下代码:

package com.finedo.jvm.classloader;

public class MyTest13 {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);

        while (null != classLoader) {
            classLoader = classLoader.getParent();
            System.out.println(classLoader);
        }
    }
}

运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@567d299b
null

  • 通过上面代码我们可以看到系统加载器为(AppClassLoader),其父级加载器为(ExtClassLoader),ExtClassLoader的父级加载器为根类加载器,之所以打印为null,当我们跟进getParent()方法后,我们看到javadoc中这样一句话(Some implementations may use null to represent the bootstrap class loader.(有些实现用null来代表根类加载))

你可能感兴趣的:(java,jvm)