JVM中的类加载

JVM中的类加载

关于JVM中类的加载这部分知识在网上有太多的文章描述这部分的知识。但是多数文章都过于冗长,难以理解。这篇文章主要是一些我对JVM中类的加载的理解。

一、一句话概括

java在类加载的时候实际上就是把xxx.class文件读入JVM方法去,并在内存中生成class的对象。

二、那么Java中是怎么加载类的

1. 首先我们要了解类加载器

java 中有四种类加载器。从底向上依次是

  1. BootStrapClassLoader: 负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  2. ExtensionClassLoader: 负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  3. ApplicationClassLoader: 负责加载用户路径(classpath)上的类库。
  4. UserClassLoader:用户自定义的ClassLoader。

2. 类加载器是怎么加载类的

类加载器是通过双亲委派机制来加载类的

2.1 什么是双亲委派机制?

类加载器在接收到类加载请求之后,低等级的ClassLoader会首先检查这个类是否已经加载过了,若是没有加载则,将加载的请求委派给双亲(比如 ApplicationClassLoader会将请求委派给ExtensionClassLoader)。这样一层一层的传送直到BootStrapClassLoader,如果BootStrapClassLoader没有找到,则逐级向下反馈,下级再寻找该类试图加载。

2.2 为什么使用双亲委派机制?双亲委派机制的好处?

  • JVM只有在两个类的类名和加载它的类加载器均相同的情况下才会判定这两个类相同。若不采用双亲委派机制,则有可能造成一个类被多个不同的类加载器加载,这样会被识别为几个相互不同的类,相互之间赋值会出现问题。
  • 双亲委派机制能保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。

3. 类加载的过程中哪些代码会被执行到?

静态代码块以及静态变量 这些内容只被执行一次,因此他们在内容中的位置是相对固定的。所以被叫做静态。用这样的概念来解释静态变量:

  • 静态变量:是代码中用static关键字修饰,告诉JVM,该变量只在内存中存在一份,该引用存在方法区且地址是相对固定不变的所以称作“静态变量”。

三、类加载的具体过程

java加载类的过程

1. 加载(loading)

加载阶段主要完成的是将虚拟机外部的二进制字节流按照JVM所需的格式存储在方法区中。

  1. 通过一个全限定名获取二进制字节流。
  2. 将二进制中的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的Class对象,作为方法区数据的访问接口。

2.验证(verification)

验证作用是确保文件的字节流包含信息符合当前虚拟机要求,保证其并不会危害虚拟机的安全。
验证的主要内容为:

  1. 文件格式验证:
    这一步主要是保证Class文件格式上符合Java信息的要求。例如文件类型,版本号,常量池,常量池数据等等。
    注:在这一步字节流就会进入内存的方法区之中了,后面的操作都是基于方法区内的存储结构进行的。
  2. 元数据验证:
    对字节码描述信息进行语义分析,例如类是否有父类,重载是否正确,final,abstract有没有用错等,其主要目的是对类的元数据进行语义分析,保证符合Java语言规范。
  3. 字节码验证:
    对数据流和控制流进行分析。例如字节码指令集的正确,程序跳转的安全。其主要目的是检查方法体内的数据安全,确保程序语义合法,符合逻辑。
  4. 符号引用验证:
    符号引用验证也是一个比较特殊的阶段,其为解析阶段服务(这也验证了前面所说的,这几个过程并不是依次执行完成的)。在解析过程中,虚拟机将符号引用转换为直接引用,其主要是对常量池中的各种符号引用做匹配性校验。检验内容包括以下几个:
  • 符号引用指向的类能否找到。
  • 指定的类有没有描述的方法和字段。
  • 符号引用指向的各种信息的访问权限是不是对的。

3. 准备(preparation)

为类变量(被static修饰的变量)分配内存并设置类变量初始值。这里需要注意的是设初始值值得是为其设置零值,例如数值量的 0,boolean 值的 false 等。但是特殊情况下,如类变量是一个常量,那么在准备阶段,虚拟机就会将其设置为常量指代的值。

4. 解析 (resolution)

在验证阶段的符号引用验证说过解析阶段就是将符号引用转换为直接引用,那么符号引用和直接引用分别指什么呢,他们之间又有何区别:

  • 符号引用。是能够无歧义定位目标的任何形式的字面量,其与虚拟机实现的内存布局无关,引用的目标不一定需要加载入内存中;
  • 直接引用。可以直接指向目标指针,偏移量的引用,其和虚拟机实现的内存布局相关,引用的目标一定需要在内存中。

在这一步虚拟机会将类/接口,字段,类方法,接口方法等进行解析,变为直接引用。

5. 初始化(initialization)

初始化阶段主要是初始化类变量和其他资源,主要是通过()方法。
()是通过编译器自动收集所有类变量的赋值动作和静态语句块(static{}块)并按照顺序合并生成的。

5.1 什么时候JVM进行初始化?

  1. 在字节码层面遇到以下指令时,new(对象都要生成了,肯定要初始化了),get/put static(使用静态变量了,肯定要赋值了),invoke static(调用静态方法了都,肯定要为静态量赋值);
  2. 反射调用。当使用java。lang。reflect中的方法对类进行反射调用;
  3. 初始化一个类的时候,发现父类还有初始化,那么需要先初始化其父类,(父接口不用立即初始化,只有使用到其常量时,才需要将其初始化);
  4. 虚拟机需要一个入口,因此主类需要初始化;
  5. 动态方法解析,解析出方法是其他类的静态方法,那么需要将其初始化。
posted @ 2019-10-25 16:40  Winstone's  阅读(186)  评论(0编辑  收藏  举报