栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JVM内存与垃圾回收-2-类加载器子系统

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM内存与垃圾回收-2-类加载器子系统

目录
  • 类加载器子系统整体结构
  • 类加载过程
    • 加载
      • 加载过程
      • 类加载器
        • 引导类加载器
        • 扩展类加载器
        • 应用类加载器
        • 自定义类加载器
        • 线程上下文类加载器
        • JVM类加载双亲委托机制
        • JVM类加载双亲委托机制+线程上下文类加载器
        • Tomcat类加载机制
    • 链接
      • 验证
      • 准备
      • 解析
    • 初始化
      • 初始化过程
      • 类的初始化时机
  • class对象同类判断
  • 同类同类加载器
  • 作者声明

类加载器子系统整体结构

  • 输入为.class文件
  • 输出到Runtime-运行时数据区中
  • 对.class文件进行加载》链接(验证-准备-解析)》初始化
  • 类加载子系统只负责加载.class文件到运行时数据区中,不负责编译检查和运行检查
  • 编译时检查有javac(前端编译器)决定;运行时检查执行引擎中解释器和JIT(即时编译器)决定
类加载过程

  • 双亲委托机制
  • 其它机制
加载 加载过程

过程:

  1. 判断:加载前先判断是否已经加载过这个类了
  2. 获取.class文件:通过类的全限定名获取此类的二进制字节流
  3. 生成Class对象:在运行时数据区下的方法区中生成java.lang.Class对象,作为类信息的访问入口
    具体方法:输入为类的全限定名,输出为Class对象
//Class loadClass(String name)
public Class loadClass(String name) throws ClassNotFoundException {
    
}
//Class findClass(String name)
protected Class findClass(String name) throws ClassNotFoundException {

}

//Class defineClass(String name)
protected final Class defineClass(String name, java.nio.ByteBuffer b,ProtectionDomain protectionDomain) throws ClassFormatError{

}

类加载方式-.class文件加载方式

  • 本机获取加载
  • 网络获取加载
  • zip包中获取加载(jar和war格式是zip格式)
  • 加密文件中获取加载(防止.class文件被反编译)
  • 动态生成加载(动态代理)
  • 其它文件或数据库中获取加载(JSP应用)
类加载器

JVM类加载器

  • BootStrap ClassLoader(引导类加载器)
  • Extension ClassLoader(扩展类加载器)
  • Application ClassLoader(应用类加载器)
    线程上下文类加载器
  • 一般是Application ClassLoader(应用类加载器)
引导类加载器

BootStrap ClassLoader(引导类加载器)

  • 用来加载JVM自身所需要的类
  • 加载扩展类加载器和应用类加载器,并指定为它们的父类加载器
  • C/C++语言编写实现
  • 不继承java.lang.ClassLoader
  • 用来加载java核心库,提供了JVM自身所需要的类
    • JAVA_HOME/jre/lib/rt.jar
    • resource.jar
    • sun.boot.class.path路径下的内容
  • 为了安全,它只加载包名为java, javax, sun等开头的类
扩展类加载器

Extension ClassLoader(扩展类加载器)

  • 用来加载扩展库
  • 向上委托BootStrap ClassLoader(引导类加载器)
  • java语言编写实现
  • 继承java.lang.ClassLoader
  • 加载位置
    • jre/lib/ext下加载类库
    • java.ext.dirs系统属性指定路径下的类
应用类加载器

Application ClassLoader(应用类加载器)

  • 用来加载应用程序
  • 程序中默认的类加载器
  • 向上委托Extension ClassLoader(扩展类加载器)
  • java语言编写实现
  • 继承java.lang.ClassLoader
  • 加载位置
    • 加载环境变量classpath路径下的类
    • java.class.path系统属性指定路径下的类
自定义类加载器

原因

  • 隔离加载类:引入多个框架依赖的类,路径相同类名也相同,类的内容不同
  • 修改类加载方式:启动类加载器是一定会调用的,JVM用户自定义类加载器可以调整
  • 扩展加载源:网络中*.class文件、DB中*.class文件
  • 防止源码泄露:加密后的字节码文件,自定义累加器解密后运行

步骤

  • extends Classloader》重写findClass()方法 (findClass()方法中需要有获取字节码流的过程)
  • 或extends URLClassloader类,不需要自己编写findClass()方法

方法

  • 输入为类的全限定名,输出为Class对象
//Class loadClass(String name)
public Class loadClass(String name) throws ClassNotFoundException {
    
}
//Class findClass(String name)
protected Class findClass(String name) throws ClassNotFoundException {

}

//Class defineClass(String name)
protected final Class defineClass(String name, java.nio.ByteBuffer b,ProtectionDomain protectionDomain) throws ClassFormatError{

}
线程上下文类加载器
  • 出现原因
    • 如果基础类要调用用户代码
    • 需要父类加载器请求子类加载器去完成类加载的动作
    • JDBC标准:JDBC接口由java团队实现;JDBC实现由不同厂商负责
  • 设置
    • 这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,
    • 如果创建线程时还未设置,它将会从父线程中继承一个,
    • 如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
  • 应用场景
    • Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等
  • 结果
    • 在一定程度上打破了双亲委托机制
JVM类加载双亲委托机制


过程

  1. 类加载器收到类加载请求,先交由直接双亲加载器加载
  2. 双亲加载器收到委托类加载请求,先交由直接双亲加载器加载,最终到达顶层的BootStrap ClassLoader(引导类加载器)
  3. 如果双亲类加载器能够完成类加载任务则成功返回,不能完成类加载任务则返还子加载器尝试加载,子加载器也不能加载则抛出异常
    注意
  • 双亲委托不是继承
  • 扩展类加载器没有继承引导类加载器而是继承ClassLoader
  • 应用类加载器没有继承扩展类加载器而是继承ClassLoader
  • 引导类加载器使用C语言/C++语言编写没有继承ClassLoader
JVM类加载双亲委托机制+线程上下文类加载器

  • SPI接口由引导类加载器加载
  • SPI接口实现由线程上下文类加载器(默认是App Class Loader)加载
  • SPI接口调用SPI接口实现
  • 举例:
    • jdbc接口是SPI接口,jdbc.jar是SPI接口实现
    • Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等
Tomcat类加载机制

知识基础

  • jsp文件本质是servlet,servlet就是一个java类
  • jvm类加载器通过类的全类名判断是否已经加载过该类
  • 导致问题1:相同类库不同版本无法区分
  • 导致问题2:jsp文件修改后由于全类名未变不会重新加载jsp文件

Tomcat(web容器)需要解决的问题

  • 自身依赖问题:
    • web容器本身有依赖的类库,不能与web应用程序依赖的类库混淆。
  • 版本问题:
    • 相同版本依赖相互共享:不同web应用程序依赖同一个第三方类库的相同同版本,保证相同同版本依赖相互共享。
    • 不同版本依赖相互隔离:不同web应用程序依赖同一个第三方类库的不同版本,保证不同版本依赖相互隔离。
  • jsp热修改问题:
    • web容器(tomcat)jsp修改后不用重启web容器(tomcat)问题

Tomcat(web容器)解决问题的类加载器

自身依赖问题

  • 通过Catalina类加载器(Catalina ClassLoader)加载Tomcat需要的依赖类库
    相同版本依赖相互共享:
  • 通过Shared类加载器(Shared ClassLoader)加载web应用共享的依赖类库
    不同版本依赖相互隔离:
  • 每个web应用都有自己的WebApp类加载器(WebApp ClassLoader)
    jsp热修改问题
  • jsp文件本质是servlet,servlet就是一个java类
  • 一个jsp文件对应一个唯一的jsp类加载器
  • 问题:修改jsp文件内容,但jsp生成的*.class文件全类名不变
  • 解决:直接卸载掉这jsp文件的类加载器
  • 过程:Tomcat(web容器)检测到jsp文件被修改》卸载对应的jsp类加载器》重新创建jsp类加载器》重新加载这个jsp文件

其它详细内容请查看Tomcat类加载机制

链接 验证

介绍

  • 确保.class文件数据符合JVM规范
  • 验证:文件格式,元数据,字节码,符号引用
    例子
  • 符合JVM规范的字节码文件开头必须是CA FE BA BE
准备
  • static变量(static无final修饰):在方法区中分配内存与默认初始化(JVM给定值)
  • static final变量:进行显示初始化(用户值),final变量编译时在常量池中分配内存
解析

介绍

  • 解析就是将常量池中符号引用转化为直接引用的过程
  • 事实上,解析操作伴随着初始化过程
  • 符号引用:JVM规范中定义的符号,这些符号用来描述所引用的目标
  • 直接引用:是直接指向目标的指针、相对偏移量或句柄
  • 解析类,接口、类方法、接口方法、方法类型、字段等,对应常量池中的CONSTANT_Class_info、CONSTANT_Methodref_info、CONSTANT_Fieldref_info
    例子
  • 上图是,经过jclasslib反编译后的常量池(静态常量池)
  • 静态常量池:位于.class文件中的常量池,静态文件中的常量池
  • 运行时常量池:.class文件中的常量池被装载到运行时数据区就变成了运行时常量池
初始化 初始化过程

介绍

  • 初始化阶段就是执行类构造方法()的阶段
    ()
  • 来历
    • 此方法不是用户自定义的
    • 此方法由javac前端编译器收集类中所有“类变量赋值操作”和“静态代码块中的语句”合并而来
    • “类变量赋值语句”和“静态代码块中语句” 在()方法中的顺序与在源文件中出现的顺序一致
  • 父子类()
    • 若该类有父类,JVM会保证先执行父类的()方法,再执行子类的()方法
  • 接口():
    • 接口不能使用静态代码块,但可以有类变量的初始化赋值操作,因此可以生成()方法。
    • 与类不同的是,执行接口的()方法不需要先执行父接口的()方法,只有父类接口定义的变量被使用时父类接口才会初始化。
    • 实现接口的类在初始化时不会执行接口的()方法。
  • 多线程()
    • JVM保证()只会有1个线程执行1次
    • ()是多线程安全的方法,JVM会保证类构造方法()在多线程下被同步加锁
    • 如果多线程同时初始化一个类,那么只会有一个线程去执行这个类的()方法,其它线程都需要阻塞等待,直到活动线程的()方法执行完毕,其它线程不会再次初始化。
  • ()执行时机,见后面 类的初始化时机
    • 类的首次主动使用时才会进行静态变量的初始化,才会执行()方法
    • 只有在类的第1次主动使用时,才会执行()方法1次。
      例子

      注意
  • ()方法不一定会出现,如果一个类中没有“类变量赋值语句”或“静态代码块”,那么编译器就不会为这个类生成()方法。
  • ()方法如果出现,必然有对“类变量赋值语句”或“静态代码块”
  • ()是对象构造方法
  • .class文件经过jclasslib反编译之后才会出现()方法
类的初始化时机

类的初始化时机-()方法执行时机

  • 类的首次主动使用时才会进行静态变量的初始化,才会执行()方法
  • 只有在类的第1次主动使用时,才会执行()方法1次。

问题:什么叫做类的首次主动使用呢
解答:创建本对象;创建子类对象;使用类的静态方法或属性

  • 创建对象
    • 创建本类对象(Apple apple=new Apple())
    • 创建子类对象(FuShiApple fsApple=new FuShiApple ())
    • 反射创建对象;通过Class文件反射创建对象(Class.forname(“cn.xxx.Test”)).newInstance();
  • Java虚拟机启动时被标记为启动类的类(含有main方法)
  • 使用静态属性/方法
    • 使用类的静态属性:调用类的静态属性或者给静态属性赋值
    • 调动类的静态方法
  • 动态语言支持(java.lang.invoke.MethodHandle实例解析的结果等)
  • 创建类引用数组不会进行类的初始化,A[] as=A{10}
class对象同类判断

两个class对象所属类是否为同一个类-两个必要条件

  • 全类名一致
  • 类加载器实例对象一致
同类同类加载器

类加载器实例的引用存储

  • 同样一个类应该被同一个类加载器加载
  • 类信息(Class实例)中保存类加载器引用信息
  • 如果一个类是由用户自定义类加载器加载的,JVM会将这个类加载器实例的引用作为类型信息的一部分放到方法区
作者声明
  • 文章如有问题,欢迎指正!!!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/434269.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号