05 类加载机制

1. 什么是类加载机制?

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。

2. 类加载过程?

类从加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载 7 个阶段。其中验证、准备和解析3个阶段又被统称为连接。

  • 首先将.java文件编译为.class文件,然后Java虚拟机就通过类加载器(类加载器本质就是一段程序)把“.class”文件加载到内存,在方法区形成该类各方法的代码段和描述该类细节信息的常量池,同时在堆区形成一个表示该类的Class对象(一个java.lang.Class类的实例)。Class对象中存储了指向了该类所有属性和方法的详细信息的指针(同时,还存储了指向该类的父类的Class对象的指针)。我们能够通过Class对象直接创建该类的实例,并调用该类的所有方法。

各个阶段的作用:

  1. 加载: 在加载阶段,虚拟机需要完成三件事:① 通过一个类的全限定名来获取定义此类的二进制字节流,这个动作通过类加载器在虚拟机外部实现;② 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;③ 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
  2. 验证: 验证阶段是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  3. 准备: 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个时候进行内存分配的仅包括被 static 修饰变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配到Java堆中。所设置的初始值默认是0或false或null,对于 public static int value = 100;变量value在准备阶段后的初始值为0,而不是100,因为这时候还没有执行任何的Java方法,把value复制为100的动作在初始化阶段才会被执行。如果类变量为常量,比如 public static final int value = 100;就会按照表达式来初始化,而不是赋值为0。
  4. 解析: 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
  5. 初始化: 初始化阶段开始真正执行定义的Java代码,它其实是执行类构造器 () 方法的过程,该方法由类变量的赋值动作和静态语句块中的语句合并产生,它与 () 方法不同的是它不需要显式的调用父类构造器。
3. 类加载机制有哪些?
  • 全盘负责:当一个类加载器加载某个Class时,该Class所依赖和引用的其它Class也将由该类加载器负责载入,除非显式的使用另外一个类加载器来载入。
  • 双亲委派:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,若父类加载器仍有父类,则继续向上追溯,直到最高级。如果最高级父类能够加载到该类,则成功返回,否则由其子类进行加载。以此类推,如果到最后一个子类还不能成功加载,则抛出一个异常。
  • 缓存机制:缓存机制会保证所有加载过的Class都会被缓存,当程序中需要使用某个类时,类加载器先从缓冲区中搜寻该类,若搜寻不到将读取该类的二进制数据,并转换成Class对象存入缓冲区中。这就是为什么修改了Class后需重启JVM才能生效的原因。
4. 双亲委派加载机制
  • 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都有自己的父加载器。JVM在加载类时默认采用的是双亲委派机制。通俗的讲,一个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,若父类加载器仍有父类,则继续向上追溯,直到最高级。如果最高级父类能够加载到该类,则成功返回,否则由其子类进行加载。以此类推,如果到最后一个子类还不能成功加载,则抛出一个异常。
  • 作用:
    ① 主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
    ② 避免了类的重复加载,可以使各种类加载环境中的类都是同一个类,避免程序混乱。

好处: Java类随着它的类加载器一起具备了带有优先级的层次关系,例如类 java.lang.Object,无论哪个类加载器加载这个类,最终都会委派给处于模型顶层的启动类加载器来加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。如果没有使用双亲委派模型,由各个类加载器自行加载的话,假如用户自己编写了一个称为 java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,java类型体系中最基础的行为也就无法保证,会导致应用程序一片混乱。 (了解:自己写的string类不能被加载,因为对于 java.* 开头的类,JVM 的实现中已经保证了必须由启动类加载器来加载)

怎么打破双亲委派模型? :① 自定义一个包名不是 java.* 的string类,放在用户目录下进行加载。 ② 重写 loadClass 方法,因为双亲委派机制是通过这个方法实现的,先找父加载器进行加载,如果父加载器无法加载再由自己来进行加载,重写了这个方法后就能自己定义加载的方式了。

5. 类加载器有哪些?负责哪些部分的类加载?
  • 启动类加载器:负责将存放在<JAVA_HOME>\lib目录中的或者被-Xbootclasspath参数所指定的路径中的,并且是被虚拟机识别的类加载到虚拟机内存中。
  • 其他类加载器: 由 Java 语言实现 ,独立于虚拟机外部 ,并且都继承抽象类 java.lang.ClassLoader。有 扩展类加载器和应用程序类加载器:① 扩展类加载器: 它负责加载 <JAVA_HOME>\lib\ext 目录中的所有类库,开发者可以直接使用扩展类加载器。 ② 应用程序类加载器: 它是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,负责加载 ClassPath 上指定的类库,开发者可以直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
6. 怎么实现自定义类加载器?

继承ClassLoader类
覆盖findClass(String className)方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值