概述

类加载机制是指虚拟机将描述类的数据从Class文件中加载到内存,并进行数据验证、解析、初始化等过程,最后形成可以直接被虚拟机使用的java类型。在java语言中类的加载、链接、初始化等过程并不是在编译时期完成,而是在运行时期才进行的,这样做的好处在于可以为语言提供了动态扩展的特性(可以在运行期间动态接收二进制的字节码文件解析运行),坏处在于增加了性能的开销。

类加载过程

类在jvm中的整个生命周期包括:

加载(loading)-->验证(verification)-->准备(preparation)-->解析(resolution)-->初始化(initialization)-->使用(using)-->卸载(unloading)

其中验证、准备、解析并称为链接阶段。并且加载、验证、准备、解析、初始化阶段的开始时机是确定的,当然这里不需要等待前一个完成才执行后一个。而解析过程则不一定了,因为Java语言可能会存在运行时动态解析。

加载

加载过程实际上就是通过二进制字节流生成Class对象的过程,整个过程可以分为以下阶段:

  1. 通过类的全限定名来获取定义该类二进制字节流,这里的字节流并不要求一定是从Class文件获取,可以从压缩包(jar,war)、其他文件生成(jsp)、网络、计算生成(proxy)等等途径获得。
  2. 将这个字节流所表示的静态存储结构转换为方法去的动态运行时数据结构。数据存储格式由虚拟机自行实现。
  3. 在内存中实例化一个java.lang.Class对象,作为方法区中该类的数据访问入口。在HotSpot虚拟机中,Class对象在JDK1.7前是存储在方法区中,在JDK1.7之后Class实例存放在堆中。

在加载过程中的获取二进制字节流阶段既可以使用JDK提供了类加载器进行加载,也可以使用我们自定义的类加载器加载。但是如果是加载一个数组类就有些不一样了。

数组类本身并不通过类加载器加载,其由虚拟机直接创建,但是数组中承载的对象还是由类加载器加载:

如果数组类承载的对象时引用类型那么就递归(数组可以嵌套)的调用加载过程对类进行加载,同时该数组将在加载被承载对象的类加载器的类名称空间中被标识。如果数组中的内容并不是引用类型(基本数据类型),那么Java虚拟机将会将该数组标记为与引导类加载器关联。

验证

验证是链接过程的开始,这一阶段的目的是为了保证Class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机的安全。

在加载一节我们说过二进制的字节流并不一定必须是通过java代码编译而来,统一通过多种形式,甚至可以直接使用十六进制数据构造。所以我们必须要对数据进行验证,否则随意的数据可能导致虚拟机崩溃。在java虚拟机规范中对数据的约束和规范规则较多,大致可以分为4种:

  1. 文件格式验证:主要验证字节流是否符合Class文件规范以及能否被当前版本的虚拟机处理,包括魔数验证,版本号等等。目的是为了保证输入的字节流能够正确的解析和存储于方法区内,在格式上符合描述一个java类型信息的要求。通过验证后,会将字节流信息存储于内存中的方法区中。
  2. 元数据验证:对字节码描述的信息进行语义分析,保证不存在不符合java语言规范的元数据。如该类是否有父类,父类是否继承了不允许继承的类等等。
  3. 字节码验证:通过数据流和控制流来确定程序语义是否是合法的、符合逻辑的。该阶段是对类的方法体进行校验。例如保证指令的跳转不会跳到方法体外的字节码指令上,保证类型转换时有效地等等。但是即使通过了字节码验证也并不能表示程序就是绝对无措的。因为我们的检验程序的程序本身是可能有错或不足的。
  4. 符号引用验证:该验证是在解析阶段进行的(类加载的过程可能是相互交叉的)。符号引用验证的目的是为了确保解析动作能正常执行。如果无法通过符号引用验证,像java.lang.NoSuchMethodError等都是在该阶段抛出的。

需要注意的是该校验并不是每一次都需要的,假设我们的程序在测试环境下多次测试都是正常的,那么我们在生产环境下可以通过-Xverify:none参数来关闭大部分的验证措施,用以提高虚拟机的类加载时间。

准备

准备阶段是正式为类变量分配内存和设置类变量初始值的阶段,这些变量所需的内存都是你在方法区进行分配。

需要注意的是这里分配内存的仅仅是类变量(static)而不包括实例变量。实例变量内存分配和赋初值是在对对象实例化阶段进行的。同时这里的赋初值也并不是设置我们在程序中定义好的值,而是零值,比如int类型的零值为0,应用类型零值为null。

但是如果是constantValue(同时被final和static进行修饰)的值则是在该阶段进行赋值的,其他类型正式赋值是在初始化阶段进行的。

解析

解析是虚拟机将常量池中的符号引用替换为直接引用的过程。解析主要的对类,接口,字段,类方法,接口方法,方法类型等符引用进行。

  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任意形式的字面量,只要使用时能够无歧义的定位到目标即可。
  • 直接引用:直接引用可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。

初始化

初始化过程实际上就是对变量赋值(不是赋初值)的过程。包含所有类变量的赋值以及静态代码语句块的执行代码,包括对父类的初始化。

类加载器

在前面的加载过程中有说到“通过类的全限定名来获取该类的二进制字节码流”,这一过程的实现就是通过类加载器完成的。

类与类加载器

对于任意的一个类,都需要由该类本身和加载该类的类加载器来确定其在Java虚拟机中的唯一性,每一个类加载器都有一个独立的类名称空间。这句话的意思就是要判断两个类是否一样,不能仅仅比较两个类是否通过同一个Class文件生成的,假如两个类通过同一个Class文件生成但是各自加载他们的类加载器不一样,那么这两个类也是不相等的,在使用equals方法和instanceof关键字等时都遵循这个原则。

几种类加载器

这里介绍几种已经内置的类加载器(都是针对于HotSpot vm):

  • BootStrap ClassLoader(启动类加载器):该类加载器负责加载<JAVA_HOME>/lib目录和由-Xbootclasspath参数指定的路径下的类库。但是并不是说把任意类库放在lib目录下都会被加载,必须是虚拟机内定义好的名字的类库。同时在HotSpot VM中BootStrap ClassLoader是由C++实现,所以在java程序中我们无法获取到该类加载器的实例,如果我们自定义的类加载器中需要用到BootStrap ClassLoader的话可以直接使用null代替。
  • Extension ClassLoader(扩展类加载器):该类加载器负责加载<JAVA_HOME>/lib/ext目录下或者由java.ext.dirs变量指定的路径下的类库,该类加载器我们可以直接使用。
  • Application ClassLoader(应用类加载器):该类加载器负责加载用户路径(ClassPath)下的类库。该类加载器也是默认的类加载器。

我们也可以继承ClassLoader类并重写findClass方法进行自定义类加载器。

双亲委派模型(Parents Delegation Model)

上面介绍的不同类加载器之间也并不是各自独立运作的,其相互之间存在着一些关系。

像上图这种层级关系被称为双亲委派模型,双亲委派模型是如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载。实际上双亲委派模型的并不是指每一个类加载器都有两个父加载器,parents是指可以有多个父加载器,当然也可以只有一个。这里的翻译有点怪怪的。

双亲委派模型的好处在于通过不同层次的类加载器加载的类先天的带有一个层次关系,保证同一个类不会被多次加载,例如类java.lang.Object,它由启动类加载器加载。双亲委派模型保证任何类加载器收到的对java.lang.Object的加载请求,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。

双亲委派模型的实现:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {Class<?> c = findLoadedClass(name);//如果该类没有被加载则通过类加载器加载//已经被加载就返回if (c == null) {long t0 = System.nanoTime();try {//判断父加载器是否为空 为空表示使用BootStrap ClassLoader加载if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//父加载器无法加载该类 下面有当前类加载器自己加载}if (c == null) {long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
复制代码

双亲委派模型并不是一个强制性的约束,我们的实现理论上可以不遵循该模型理论。如果有必要的话可能会对双亲委派模型进行破坏(让父类加载器主动将类加载行为交给子类进行或者直接由当前类加载器加载不交给父类),比如JNDI,OSGI等。

《深入java虚拟机》读书笔记类加载相关推荐

  1. java虚拟机读书笔记 第三章 垃圾收集器和内存分配策略

    java虚拟机读书笔记 第三章 垃圾收集器和内存分配策略 GC需要完成的三件事情:哪些内存需要回收.什么时候回收.如何回收 垃圾回收器在对堆进行回收前,首先要确定那些对象存活,哪些对象已经死去,判断的 ...

  2. Java 虚拟机学习笔记 | 类加载过程和对象的创建流程

    前言 创建对象是 Java 语言绕不开的话题,那么对象是如何创建出来的呢?我们今天就来聊一聊.对象创建第一步就是检查类是否加载,而类的加载又牵扯到类的加载过程.如果单说对象的创建而绕开类的加载过程,感 ...

  3. Java虚拟机不能满足_深入理解Java虚拟机--读书笔记1/3

    <深入理解Java虚拟机-JVM高级特性与最佳实践> Chap 2 Java内存区域与内存溢出异常 1.Java运行时数据区域 A.程序计数器:当前线程所执行字节码的行号指示器,线程私有( ...

  4. 深入理解java虚拟机-读书笔记

    内存动态分配与内存回收技术已经相当成熟,看起来进入了自动化的时代,为什么还要去了解垃圾收集和内存分配? 当需要排查各种内存溢出.内存泄漏问题时 当垃圾收集成为系统达到更高并发量的瓶颈时 我们就必须对内 ...

  5. 深入理解Java虚拟机读书笔记十二

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

  6. 深入Java虚拟机读书笔记[10:20]

    第十章 栈和局部变量操作 第十一章 类型转换 第十二章 整数运算 第十三章 逻辑运算 第十四章 浮点运算 第十五章 对象和数组 第十六章 控制流 第十七章 异常 以上一些是操作码相关的内容, 第十八章 ...

  7. 深入理解java虚拟机---读书笔记

    第一章 走近java 1. java 技术体系: java 程序设计语言 各种硬件平台上的java虚拟机 class 文件格式 java API 类库 来自商业机构或者开源机构的第三方java类库 j ...

  8. 深入Java虚拟机读书笔记第五章Java虚拟机

    Java虚拟机 Java虚拟机之所以被称之为是虚拟的,就是因为它仅仅是由一个规范来定义的抽象计算机.因此,要运行某个Java程序,首先需要一个符合该规范的具体实现. Java虚拟机的生命周期 一个运行 ...

  9. 深入理解Java虚拟机——读书笔记01

    基础知识 JDK:Java程序设计语言.Java虚拟机.Java API类库 JRE:Java API类库中的Java SE API子集和Java虚拟机 Java技术基础支撑点:JAR文件格式.JDB ...

  10. 深入理解Java虚拟机读书笔记七

    3.5.内存分配与回收策略 3.5.1.对象优先在Eden分配 大多数情况下,对象在新生代Eden区中分配.当 Eden区没有高足够的空间进行分配 时,虚拟机将发起一次Minor GC. 虚拟机提供- ...

最新文章

  1. 改进AI/ML部署的5种方法
  2. tensorboard ValueError: Duplicate plugins for name projector
  3. 别再搜集面经啦!小夕教你斩下NLP算法岗offer!
  4. html调整文字位在基线显示,html – 将标题对齐到相同的基线,无论后续文字是什么?...
  5. 三个线程打印ABC10次,ABCABCABC....
  6. 创建多模块springcloud应用eureka server和client和消费端demo
  7. HDU2032 杨辉三角【入门+趣味程序】
  8. RobotStudio机器人运行路径的创建与仿真
  9. 弹窗修改数据 临时解决方法 + ajax
  10. Septentrio RAIM+接收机自主完好性监测实验
  11. linux中如何编译成bin文件,gcc 如何编译成bin文件
  12. CCNA 测试题及答案 第一章
  13. android outlook日历同步,Android上使用Exchange ActiveSync不能同步Outlook.com账号中的多个日历...
  14. python代码错误有哪些_Python常见十六个错误集合,你知道那些?
  15. 人像姿势,从细节做起!
  16. input输入框 去掉外边框 解决方案
  17. LLVM IR转CFG
  18. photoshp案例技巧-彭亮-专题视频课程
  19. 2020 icpc济南 A - Matrix Equation (高斯消元求自由元个数)
  20. Python优势和用途--perfect!

热门文章

  1. Ubuntu安装Anaconda
  2. 眠眠interview Question
  3. [elixir! #0007] [译] 理解Elixir中的宏——part.5 重塑AST by Saša Jurić
  4. PHP-Fpm应用池配置
  5. ubuntu修改IP
  6. liunx 下dhcp中继及服务器配置
  7. 中兴V880使用手记之五——刷入recovery
  8. Confluence 6 创建小组的公众空间
  9. Cookie实现记住密码、自动登录
  10. Clean-Code: 注释