类的加载过程

简介: 本博客主要讨论类加载过程中主要的内容,如果想了解更加详细的内容可以参考JVM规范或者其他数据。本文的主要参考资料为《深入理解Java虚拟机》。   一  类的生命周期 1       类的生命周期 说明:在类加载过程中,加载、验证、准备、初始化、卸载这几个阶段的顺序是固定的,而解析的阶段不固定。

本博客主要讨论类加载过程中主要的内容,如果想了解更加详细的内容可以参考JVM规范或者其他数据。本文的主要参考资料为《深入理解Java虚拟机》。

 

一  类的生命周期

1       类的生命周期

881b7eaa169eb8dbb3aa93a8b022bc0389fe3af1


说明:在类加载过程中,加载、验证、准备、初始化、卸载这几个阶段的顺序是固定的,而解析的阶段不固定。

 

2       加载

1)       主要职责

通过类的权限定名来获取此类的二进制字节流

将字节流代表的静态存储结构转换成方法去的运行时数据结构

在内存中生成一个代表这个类的Class对象,作为方法区这个类的访问入口

2)       说明

加载阶段与连接阶段的部分内容是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。但是两者的开始时间仍然保有固定的先后顺序

 

3       验证

1)       主要职责

确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟节自身安全。

包括:文件格式验证、元数据验证,字节码验证、符号引用验证。

2)       文件格式验证

检查class文件格式是否符合规范,并且能够被当前虚拟节处理。例如:魔数是否是0Xcafebabe,主次版本号是否被jvm接受,常量池中是否有不被支持的常量类型等等。

 

3)       元数据验证

对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求。

例如:是否有父类(出object类以外都应该有父类);是否继承了不允许继承的类(final修饰的类);如果不是抽象类,是否实现了父类、父接口中的所有抽象方法等等。

 

4)       字节码验证

通过数据流和控制流分析,确定程序语义是合法性、符合逻辑的。主要是对类的方法进行校验分析,确保运行时不会做出危害虚拟机安全的事情。

例如:确保操作数栈的数据类型与指令集能匹配工作;保证跳转指令不会包含跳转到方法外的字节码指令上;确保类型转换是有效的等等。

 

5)       符号引用验证

此校验发生在解析阶段中进行,可以认为是对类自身以外的信息进行匹配性校验。此阶段的目的是确保解析动作能正常执行。

例如:符号引用的类是否能够被找到;符号引用指定的类中是否存在执行的尚需经、方法;符号引用的类、字段、方法是否可以被当前类访问。

 

 

4       准备

1)       主要职责

为类变量(static修饰的)变量分配内存并设置初值(也称之为零值)。

这些变量所需的内存在方法区中分配

2)       数据类型的零值

数据类型

零值

int

0

long

0L

short

(short)0

char

'\u0000'

byte

(byte)0

boolean

false

float

0.0f

double

0.0d

reference

null

 

3)       例外

对于使用final修饰的类变量,在准备阶段将会被设置为指定的值。例如:public static final int value = 1 在准备阶段完成以后,value的值是1。

 

4)       字节码比较

以下示例的差别是一个使用final修饰,一个不使用final修饰,从编译出来的字节码我们看到使用final修饰的类变量的初始值就被设置成了1

a)      类变量示例

Ø  示例代码

    public class StaticTester {
       public static int value = 1;
    }
 

Ø  编译后的字节码

Constant pool:
   #1 = Class             #2             //com/wzf/greattruth/jvm/classinit/StaticTester
   #2 = Utf8              com/wzf/greattruth/jvm/classinit/StaticTester
   #3 = Class             #4             //java/lang/Object
   #4 = Utf8              java/lang/Object
   #5 = Utf8              value
   #6 = Utf8              I
   #7 = Utf8              <clinit>
   #8 = Utf8              ()V
   #9 = Utf8              Code
  #10 = Fieldref          #1.#11         //com/wzf/greattruth/jvm/classinit/StaticTester.value:I
  #11 = NameAndType       #5:#6          // value:I
  #12 = Utf8              LineNumberTable
  #13 = Utf8              LocalVariableTable
  #14 = Utf8              <init>
  #15 = Methodref         #3.#16         //java/lang/Object."<init>":()V
  #16 = NameAndType       #14:#8         // "<init>":()V
  #17 = Utf8              this
  #18 = Utf8              Lcom/wzf/greattruth/jvm/classinit/StaticTester;
  #19 = Utf8              SourceFile
  #20 = Utf8              StaticTester.java
{
  public static int value;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC
 
  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0,args_size=0
         0:iconst_1
         1:putstatic     #10                // Field value:I
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start Length  Slot  Name   Signature


b)      final修饰类变量示例

Ø  示例代码

    public class StaticTester {
        public static final int value = 1;
    }

 

Ø  编译后的字节码

Constant pool:
   #1 = Class             #2             //com/wzf/greattruth/jvm/classinit/StaticTester
   #2 = Utf8              com/wzf/greattruth/jvm/classinit/StaticTester
   #3 = Class             #4             //java/lang/Object
   #4 = Utf8              java/lang/Object
   #5 = Utf8              value
   #6 = Utf8              I
   #7 = Utf8              ConstantValue
   #8 = Integer           1
   #9 = Utf8              <init>
  #10 = Utf8              ()V
  #11 = Utf8              Code
  #12 = Methodref         #3.#13         //java/lang/Object."<init>":()V
  #13 = NameAndType       #9:#10         // "<init>":()V
  #14 = Utf8              LineNumberTable
  #15 = Utf8              LocalVariableTable
  #16 = Utf8              this
  #17 = Utf8              Lcom/wzf/greattruth/jvm/classinit/StaticTester;
  #18 = Utf8              SourceFile
  #19 = Utf8              StaticTester.java
{
  public static final int value;
    descriptor: I
    flags: ACC_PUBLIC,ACC_STATIC, ACC_FINAL
    ConstantValue: int 1
 
  publiccom.wzf.greattruth.jvm.classinit.StaticTester();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1,args_size=1
         0: aload_0
         1:invokespecial #12                 //Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start Length  Slot  Name   Signature
            0      5     0  this  Lcom/wzf/greattruth/jvm/classinit/StaticTester; 

5       解析

1)       主要职责

将常量池的符号引用替换成直接引用的过程。虚拟机规范中没有规定解析阶段的具体发生时间,只要求在执行anewarray, checkcase, getfield, getstatic, instanceof,invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual,ldc, ldc_w, multianewarray, new, putfield, putstatic这16个指令前,先对他们使用的符号引用解析。

解析主要包括:类或接口的解析,字段解析,类方法解析,接口方法解析

 

6       初始化

1)       主要职责

是执行类构造器<clinit>()方法的过程,即按照程序去初始化类变量和其他资源。

 

2)       clinit

<clinit>()方法是由编译器自动收集类中的所有变量赋值动作、静态语句块合并产生的。编译器收集的顺序是由语句在代码中出现的顺序决定。

<clinit>()方法不需要显示调用父类的构造器,虚拟节会保证子类<clinit>()方法执行以前,父类的<clinit>()方法已经执行完毕。所以虚拟机中第一个被执行的<clinit>()方法一定是Object的<clinit>()方法。

父类中<clinit>()方法先执行,意味着父类中的静态语句块要优先于子类的静态语句块。

<clinit>()方法是非必须的,如果类中没有静态语句块,也没有对变量赋值操作,那么编译器可以为这个类生成<clinit>()方法。

接口中可以存在赋值语句,所以编译器也可能为接口生成<clinit>()方法。注意接口中执行<clinit>()方法前不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。接口的实现类在初始化时,也不需要执行接口的<clinit>()方法。

虚拟机保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步。多线程同时初始化一个类时,只有一个线程执行<clinit>()方法,其他的线程都需要阻塞等待。

 

3)       初始化

虚拟机规定了有且只有5中情况需要对类进行初始化:

Ø  遇到new(实例化对象), getstatic(读取一个静态字段), putstatic(设置一个静态字段), invokestatic(调用静态方法)这4个指令时,如果类还没有初始化,那么要先触发器初始化。

Ø  使用java.lang.reflect报的方法对类进行反射调用时,如果类还没有初始化,那么先触发器初始化。

Ø  初始化一个类时,如果父类未初始化,则先触发其父类的初始化。

Ø  虚拟机启动时,用户需要制定一个要执行的主类,虚拟机先初始化主类。

Ø  使用1.7动态语言支持是,如果一个MethodHandler实例最后解析结果是REF_getStatic,REF_putStatic, REF_invokeStatic的方法句柄,并且这个方法句柄对应的类未初始化时,那么先触发其初始化。

 

 

二  类加载器

1       双亲委派工作过程

如果一个类加载器收到了类加载请求,他首先将这个类加载请求委派给父类加载器去完成(每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该先传送到顶层的启动类加载器中),只有父类加载器反馈无法完成加载请求时,子加载器才会尝试自己去加载。

 

2       双亲委派模型

示例为tomcat的双亲委派模型

 3ddebde722b7aeb9e0705547378505a1c11b5aba

 

1)       jdk自定义的类加载器

BootstrapClassLoader由C++语言实现,是虚拟机自身的一部分;负责加载<JAVA_HOME>/lib目录下或者被-Xbootclasspath参数指定的类库。无法被java程序直接引用。

ExtensionClassLoader由sun.misc.Launcer$ExtClassLoader实现;负责加载<JAVA_HOME>/lib/ext目录中的或者被java.ext.dirs指定路径的jar,此类加载器可以被开发者直接使用。

ApplicationClassLoader由sun.misc.Launcher$AppClassLoader实现。负责架子用户类路径上的jar。此类可以被开发者直接使用。

2)       Tomcat定义的类加载器

CommonClassLoader加载/common目录中的类库。这些类库可被Tomcat和所有的web应用程序共同使用

CatalinaClassLoader加载/server目录中的类库。这些类库可被Tomcat使用,对所有的Web应用程序都不可见

SharedClassLoader加载/shared目录中的类库。这些类库可被所有web应用程序共同使用,但对Tomcat自己不可见

WebAppClassLoader加载/WebApp/WEB-INF目录中的类库。类库仅仅可以被此web应用程序使用,对Tomcat和其他web应用程序都不可见

 

3       热部署

以后提供一个示例,详细讲述热部署,等待以后更新

 

 

 

相关文章
|
7天前
|
Java 编译器 容器
浅谈类的加载过程
浅谈类的加载过程
10 3
|
1月前
|
Java 编译器
|
2月前
|
安全 Java 程序员
深入理解jvm - 类加载过程
深入理解jvm - 类加载过程
50 0
|
4月前
|
存储 前端开发 安全
浅谈 JVM 类加载过程
浅谈 JVM 类加载过程
42 0
|
9月前
|
存储 安全 Java
类加载器与类的加载过程
类加载器与类的加载过程
|
9月前
|
存储 安全 Java
一文解读类的加载过程(类的生命周期)(上)
一文解读类的加载过程(类的生命周期)
|
9月前
|
缓存 Java 开发者
一文解读类的加载过程(类的生命周期)(下)
一文解读类的加载过程(类的生命周期)(下)
|
10月前
|
Java Spring
jvm类的加载过程
jvm类的加载过程
58 0
|
10月前
|
存储 安全 Java
说说类加载的过程
说说类加载的过程
53 0
|
存储 安全 Java
JVM09-类加载过程
这一篇我们来学习一下JVM中的类加载过程。说到类的加载过程,我们需要先了解一下JVM中类的生命周期。在JVM中类的生命周期有七个阶段。
78 0

热门文章

最新文章