前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试八股文之【JVM类加载机制】

面试八股文之【JVM类加载机制】

作者头像
崩天的勾玉
发布2021-12-20 16:28:04
2890
发布2021-12-20 16:28:04
举报
文章被收录于专栏:崩天的勾玉崩天的勾玉

今天讲jvm的类加载器和类加载机制,包括双亲委派原则。

类加载器

类加载器自顶向下分为:

  1. Bootstrap ClassLoader启动类(引导类)加载器:默认会去加载JAVA_HOME/lib目录下的jar。使用C/C++语言实现的,嵌套在JVM内部。它用来加载Java的核心库(String类……)(JAVA_HOME/jre/lib/rt.jar、resource.jar或sun.boot.class.path路径下的内容),包括加载扩展类加载器和应用程序加载器。没有父加载器。出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。
  2. Extention ClassLoader扩展类加载器:默认去加载JAVA_HOME/lib/ext目录下的jar。Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。派生于ClassLoader类。从java.ext.dirs系统属性所指定的目录中加载类库,或从JAVA_HOME/jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
  3. Application ClassLoader系统类(应用程序类)加载器:比如我们的web应用,会加载web程序中ClassPath下的类。java语言编写,由sun.misc.Launcher$AppClassLoader实现。它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库 对于用户自定义类来说:默认使用系统类加载器进行加载 通过 ClassLoader.getSystemClassLoader() 方法可以获取到该类加载器
  4. User ClassLoader用户自定义类加载器:由用户自己定义

只有被同一个类加载器实例加载并且文件名相同的class文件才被认为是同一个class

当我们在加载类的时候,首先都会向上询问自己的父加载器是否已经加载,如果没有则依次向上询问,如果没有加载,则从上到下依次尝试是否能加载当前类,直到加载成功。

Java的加载机制是双亲委派机制来加载类,为什么要使用这种方式?这个是为了保证【如果加载的类是一个系统类,那么会优先由Bootstrap ClassLoader 、Extension ClassLoader先去加载,而不是使用我们自定义的ClassLoader去加载,保证系统的安全】

执行java程序时,会启动一个JVM进程,JVM在启动时会做一些初始化操作,比如获取系统参数等等,然后创建一个启动类加载器,用于加载JVM运行时必须的一些类到内存中,同时也会创建其他两个类加载器扩展类加载器和系统类加载器。

自定义类加载器

为什么要自定义ClassLoader?

(1)隔离加载类:在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。比如,某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。(jar包之间的冲突的消除)

(2)修改类加载方式:类的加载模型并非强制,除Bootstrap外,其它的加载并非一定要引入,或者根据实际情况在某个时间点进行按需动态加载。

(3)扩展加载源:比如从数据库、网络,甚至是电视机机顶盒进行加载。

(4)防止源码泄露。java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密的字节码。

怎么做?

步骤:继承ClassLoader,重写findClass()方法,调用defineClass()方法。

代码语言:javascript
复制
public class CustomClassLoader extends ClassLoader {

 @Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
  try {
   byte[] result = getClassFromCustomPath(name);
   if(result == null) {
    throw new FileNotFoundException();
   } else {
    return defineClass(name, result, 0, result.length );
   }
  } catch(Exception e) {
   e.printStackTrace();
  }
  throw new ClassNotFoundException(name);
 }

 private byte[] getClassFromCustomPath(String name) {
  // 从自定义路径中加载指定类
  return null;
 }

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  CustomClassLoader customClassLoader = new CustomClassLoader();
  System.out.println(customClassLoader);
  try {
   Class<?> clazz = Class.forName("One", true, customClassLoader);
   Object obj = clazz.newInstance();
   System.out.println(obj.getClass().getClassLoader());
  } catch(Exception e) {
   e.printStackTrace();
  } 
 }
}

理论执行结果:classloader.CustomClassLoader@5e481248

类加载过程

首先是加载过程(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,比如 jar 文件,class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。

第二阶段是连接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转入 JVM 运行的过程中。这里可进一步细分成三个步骤:

1,验证(Verification),这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。

2,准备(Pereparation),创建类或者接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显示初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。

3,解析(Resolution),在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。在 Java 虚拟机规范中,详细介绍了类,接口,方法和字段等各方面的解析。

最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。再来谈谈双亲委派模型,简单说就是当加载器(Class-Loader)试图加载某个类型的时候,除非父类加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型。

双亲委派

Java的加载机制是双亲委派机制来加载类,为什么要使用这种方式?这个是为了保证【如果加载的类是一个系统类,那么会优先由Bootstrap ClassLoader 、Extension ClassLoader先去加载,而不是使用我们自定义的ClassLoader去加载,保证系统的安全!】

就是当前类加载器(以系统类加载器为例)在加载一个类时,委托给其双亲(注意这里的双亲指的是类加载器中parent属性指向的类加载器)先进行加载。双亲类加载器在加载时同样委托给自己的双亲,如此反复,直到某个类加载器没有双亲为止(通常情况下指双亲为null,也即为当前的双亲为扩展类加载器,其parent为启动类加载器),然后开始在依次在各自的类路径下寻找、加载class类。

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;如果父类加载器还存在器父类加载器,则进一步向上委托,依次递归,最终到达顶层的引导类加载器;如果父类加载器可以完成类加载任务,就成功返回,如果父类无法完成加载,子类加载器才会去加载,这就是双亲委派模式。

优点

  1. 可以避免类的重复加载
  2. 防止核心的API被篡改

你点的每个好看,我都认真当成了喜欢

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 崩天的勾玉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类加载器
    • 自定义类加载器
    • 类加载过程
    • 双亲委派
    相关产品与服务
    消息队列 TDMQ
    消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档