JVM 类加载系统

  • Class Loader Subsystem

类加载器子系统的作用

  • 从文件系统或者网络中加载class文件;
  • class文件在开头有特定的标识;
  • ClassLoader只负责class文件加载,是否可以运行由ExecutionEngine决定;
  • 加载的类信息被存放在方法区的内存空间,方法区还存在运行时常量池信息;

类的加载过程

  • 加载-链接(验证、准备、解析)-初始化

加载

  • 通过一个类的全限定名获取此类的二进制字节流;
    • 从本地系统加载;
    • 通过网络获取;
    • 从压缩包中读取;
    • 动态代理;
    • 从加密文件中获取;
  • 将这个字节流所代表的静态存储结构,转化为方法区的运行时数据结构;
  • 内存中生成一个代表此类的Class对象,作为方法区这个类的各种数据访问入口;

链接

  • 验证-准备-解析

验证

  • 验证class文件字节流中包含的信息符合虚拟机要求;
  • 四种验证:
    • 文件格式
    • 元数据
    • 字节码
    • 符号引用

准备

  • 为类变量分配内存并且设置类变量的初始值
  • 不包含final修饰的static变量值,在编译时候会分配值;
  • 不会为实例变量分配初始化;

解析

  • 将常量池中的符号引用转为直接引用;

初始化

  • 执行类构造器方法<clinit>
  • <clinit>javac编译器自动收集类中的所有类变量赋值动作、静态代码块中的语句合并而来;
  • 构造器方法中的指令按照语句在源文件中出现的顺序执行;
  • <clinit>不同于类的构造器;
  • 若该类有父类,会保证父类的<clinit>先执行;
  • 虚拟机保证同一个类的<clinit>方法在多线程下被同步加锁;

类加载器分类

  • JVM支持两类:引导类加载器、自定义加载器;
  • Java将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器;

虚拟机自带的加载器

  • BootstrapClassLoader:启动类加载器,java核心类库使用引导类加载器;jre/lib/rt.jar、resource.jar;
    • 不继承自ClassLoader
    • 负责加载扩展类加载器和应用程序类加载器;
    • 只加载包名为java、javax、sun等头的类;
  • ExtensionClassLoader:扩展类加载器
    • Java编写,继承与ClassLoader
    • jre/lib/ext/*
    • 如果用户创建的jar放在上面的目录,也会自动由扩展类加载器加载;
  • AppClassLoader:应用程序类加载器
    • 父类加载器为扩展类加载器;
    • 负责加载环境变量classpath下的类库;
    • 程序中默认类加载器;
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取上层:扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@61bbe9ba
//获取上层:无法获取引导类加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//null
  • 获取bootstrap能够加载的类的路径
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL url :urLs){
    System.out.println(url);
}
/*
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/classes
 */
  • 扩展类加载器加载的路径
String property = System.getProperty("java.ext.dirs");
for (String path:property.split(":")){
    System.out.println(path);
}
/*
/Users/***/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
 */
  • 获取类加载器的方式
//获取classloader的方式
//sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader classLoader = StackStruTest2.class.getClassLoader();

ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();

ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

双亲委派机制

  • JVM对于class文件采用按需加载机制;
  • 加载时采用双亲委派模式;

工作原理

  • 如果一个类加载器收到加载类的请求,不会自己加载,而是把请求委托给父类加载;
  • 如果父类加载器还存在父类加载器,则进一步向上委托,直到到达最顶层启动类加载器;
  • 如果父类可以完成加载,成功返回,如果父类无法完成任务,则子类去加载;
15865028680504.jpg

优势

  • 可以避免类重复加载;
  • 可以避免核心API被篡改;(沙箱安全机制)

其他

  • 在JVM中保证两个class对象是否为同一个类的两个必要条件:
    • 类的完整类名需要一致;
    • 加载这个类的ClassLoader的实例对象必须相同;
    • 在jvm中,就算来源于同一个class文件,但是被不同的ClassLoader实例加载,那么这两个类对象也不是相等的;
  • 如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类的一部分保存在方法区中;
  • Java中主动使用类:
    • 创建类的实例;
    • 访问某个类或者借口的静态变量;
    • 调用类的静态方法;
    • 反射;
    • 初始化一个类的子类;
    • Java虚拟机启动时被表明为启动类的类;
    • java.lang.invoke.MethodHandler实例的解析结果
  • 除以上7种外都是被动使用,主动与被动的区别在于类会不会被初始化
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容