JVM类加载机制

JVM类加载机制,第1张

JVM类加载机制 JVM类加载机制

JVM类加载机制分为五部分:加载、验证、准备、解析、初始化

  • 加载:加载阶段会在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的入口。
    • 注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)
  • 验证:验证阶段主要目的是确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求
  • 准备:为类中定义的静态变量分配内存空间并设置初始值。这里的初始值不是代码里等号右边的那个值,而是指默认值,比如int类型的默认值为0,若为基本静态常量(这里的基本是指,这个常量的类型为基本数据类型),则会在准备阶段会根据ConstantValue 属性 被赋值为相应的值,ConstantValue 属性是编译阶段生成的。
  • 解析:解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程 。
    • 符号引用:符号引用与虚拟机实现的布局无关, 引用的目标并不一定要已经加载到内存中。 各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的
    • 直接引用:直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在
  • 初始化:开始真正执行类中定义的 Java 程序代码 或者说 初始化阶段是执行类构造器()方法的过程。、
    • 方法:由编译器自动收集类中的类变量的赋值 *** 作和静态语句块中的语句合并而成的 。子类方法的执行在父类之前。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法 。
    • 不会执行类初始化的情况:
      • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
      • 定义对象数组,不会触发该类的初始化。
      • 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
      • 通过类名获取 Class 对象,不会触发类的初始化
      • 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化
      • 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。
类加载器

JVM支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。

从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

JVM 通过双亲委派模型进行类的加载,可以通过继承 java.lang.ClassLoader实现自定义的类加载器 。由上往下分为以下四个层次:

  • 启动类加载器(Bootstrap ClassLoader) :主要负责加载核心的类库(java.lang.*等) 无负责加载器
  • 扩展类加载器(Extension ClassLoader) :主要负责加载jre/lib/ext目录下的一些扩展的jar
  • 应用程序类加载器(Application ClassLoader) :负责加载用户路径(classpath)上的类库 该类加载器为程序中默认的类加载器
  • 用户自定义类加载器 实现自定义累加器的两种方式:继承ClassLoader
    • 重写loadClass方法 该方法包含了双亲委派模型的逻辑,
    • 重写findClass方法 官方推荐,不会破坏双亲委派模型

自定义类加载器的优点:

  • 隔离加载类

  • 修改类加载的方式

  • 扩展加载源

  • 防止源码泄漏

双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父加载器去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class), 子类加载器才会尝试自己去加载。 这些加载器并不是继承的父子关系,是组合的“父子关系”。

优点

防止内存中出现多份同样的字节码(避免类的重复加载),多个类都需要加载某个类,且这些类有继承关系,保证被加载的类只加载一次。

防止Java核心库被篡改。如果用户自己编写了一个称为java.lang.Object的类,因为有了双亲委派机制,自定义的Object类不会被加载。

如何避免双亲委派机制:由于系统自带的三个类加载器都加载特定目录下的类,我们自定义一个类加载器,且放置在相同自带的类加载器加载不到的位置,这样我们就可以使用这个类来加载我们自定义的类了。

弊端

顶层的ClassLoader无法访问底层的ClassLoader所加载的类,

解决方法

线程上下文类加载器,默认为系统类加载器。这个设计破坏了双亲委派模型。

破坏双亲委派模型
  • 覆盖loadClass()方法
  • 线程上下文类加载器的出现
  • **代码热替换(Hot Swap)、模块热部署(Hot Deployment)**等
    • 热替换实现:热替换的关键在于服务不能中断,修改必须立即表现在正在运行的系统中。对Java来说,热替换并非天生就支持,如果一个类已经加载到系统中,通过修改类文件,并无法让系统再来加载并重定义这个类。因此,在Java中实现这一功能的一个可行的方法就是灵活运用ClassLoader。基本思路就是创建自定义类加载器,然后加载需要热替换的类并通过ClassLoader实例创建类的对象,并运行新对象的方法即可。
类加载机制的基本特征

双亲委派模型。但不是所有类加载都遵守这个模型,有的时候,启动类加载器所加载的类型,是可能要加载用户代码的,比如JDK内部的ServiceProvider/ServiceLoader机制,用户可以在标准API框架上,提供自己的实现,JDK也需要提供些默认的参考实现。例如,Java中JNDI、JDBC、文件系统、Cipher等很多方面,都是利用的这种机制,这种情况就不会用双亲委派模型去加载,而是利用所谓的上下文加载器。

可见性,子类加载器可以访问父加载器加载的类型,但是反过来是不允许的。不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑。

单一性,由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就不会在子加载器中重复加载。但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。

Class.forName()与ClassLoader.loadClass()
  • Class.forName()是一个静态方法,而该方法会执行类的初始化
  • ClassLoader.loadClass()是一个实例方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。
沙箱安全机制
  • 保证程序安全

  • 保护Java原生的JDK代码

Java安全模型的核心就是Java沙箱(sandbox)。什么是沙箱?沙箱是一个限制程序运行的环境。

沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地资源的访问。通过这样的措施来保证对代码的有限隔离,防止对本地系统造成破坏。

动态模型系统(OSGI)

是Java中目前唯一的一个模块化、动态化的规范。用处在于模块化和热插拔

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5694852.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存