虚拟机类加载机制

2018-03-30  本文已影响0人  jqdywolf

虚拟机把Class文件加载到内存,并对数据进行校验、转换、初始化,最终形成可以被虚拟机直接使用的Java类型。这就是虚拟机的类加载机制。
Java语言的加载、连接、初始化都是在运行期完成的,这也让Java天生有了动态灵活性

类加载的时机

简述类加载过程

类从被加载到用完之后被卸载,生命周期存在7个阶段:

类加载过程
其中加载--验证--准备--初始化--卸载,这5个过程的开始顺序是固定的。
这里说的开始顺序是指:5个过程开始的次序固定,可能前一个没结束,后一个开始,即通常情况下,这几个都是交叉进行的。
类加载的时机

加载的时机Java虚拟机规范并没有明确规定(各个虚拟机实现版本可以自行决定)。但初始化的时机确有很严格的规定。__有且只有__5种情况必须立刻对类进行初始化:

  1. 遇到new/putstatic/getstatic/invokestatic四种指令时,如果类还没初始化,则先触发其初始化。四种指令对应的Java代码是:使用new关键字实例化对象、修改/读取类的静态变量、调用类的静态方法。
  2. 使用java.lang.reflect对类进行反射调用的时候。
  3. 当初始化一个类,发现其父类还没初始化,则先触发父类初始化。
  4. 当虚拟机启动时,指定运行的主类(包含main方法,程序的人口),要先初始化。


    image.png

第5点先放着,回头再细看一下。

类加载的过程

分别对加载、验证、准备、解析、初始化五个过程做详细介绍。

加载

简单地说,就是将class文件读到内存中方法区的过程。
主要完成了3件事:

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

然后分别对这三点做出说明:

注意两点:

  1. 对于加载阶段,当是非数组类时,我们可以使用系统提供的导入类加载器,也可以自己指定自己的类加载器。
    当是数组类时,数组类本身不通过类加载器完成,而由虚拟机自己创建。
    关于类加载器的东西,我们后面细说。
  2. 在加载阶段还没结束时,验证已经开始了。
验证

验证阶段的目的:保证class文件流是符合当前虚拟机要求的,并且不会危害虚拟机本身。
简单地说就是保证输入class文件流的合法性。
主要包括4个阶段的验证工作:

  1. 文件格式的验证
    验证字节流是否符合Class文件格式规范。
    比如:magic、minor/major version是否符合当前虚拟机版本
  2. 元数据的验证
    主要是对字节流的元数据进行语义校验。
    比如:这个类是否有父类,父类是否是可被继承的,是否是抽象类,是否实现了抽象类中的所有抽象方法等。
  3. 字节码的验证
    对类的方法体做校验,根据数据流和控制流分析,确保方法体不会做出危害虚拟机的行为。
    比如:保证跳转指令不会跳到方法体外面的字节码上。
  4. 符号引用的验证
    发生在虚拟机将符号引用转化为直接引用的时候。--这个转化动作是在解析阶段进行。
    主要是对类外信息匹配性校验。
    比如:符号引用中的全限定名是否可以找到,指定类中是否存在指定的方法等。
准备

准备阶段是为类变量分配内存(方法区中)并赋初始值。
注意三点:

  1. 是类变量,即static修饰的变量,并不是实例变量。
  2. 这里的赋初始值为0值,并不是程序里定义的值。
private static int value  = 3;

准备阶段value的值为0,在初始化阶段才被赋值为3。

  1. 这里的类变量不包括常值类变量。
private final static int value  = 3;

此时的value在编译阶段为value生成ConstValue属性,在准备阶段会被赋值为3。

插入一点知识总结:

private final static int value1 = Test.VALUE;
private final static int value2 = 12;
private final static String value3 = new String("123");

编译阶段和准备阶段都干了什么?
编译阶段:
1和2都为value生成ConstValue属性。
1是把在本类中的常量池中将Test.VALUE的具体值给替换掉。
准备阶段:
1和2都被赋值为12
3被赋值为0 3在初始化阶段才被赋值为123.

解析

解析阶段就是虚拟机将常量池中符号引用转换为直接引用的过程。

初始化

初始化阶段是执行类构造器clinit方法的过程。

非法向前引用问题:static语句块只能访问此块之前的变量。此块之后定义的变量可以赋值,不可使用。

static {
   j = 10;
//  int j; 如果把下面定义j的地方移到这里也直接报错。
//  System.out.println(j); 加上这行报错非法前置引用。注释这行可以正常运行。
}
static int j;
public static void main(String[] args){
 System.out.println(j);
}

类加载器

我们上面说过在类加载中的加载阶段,JVM允许我们自己指定类加载器。这里我们就说说类加载器。
类加载器的功能:通过一个全限定名来获取描述此类的二进制字节流。

类与加载器

对于任意一个类,都需要加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。因为每一个类加载器都拥有一个独有的类名称空间。
换句话说,同一个类被不同的类加载器加载,这两个类一定不相等。事实上,这两个类完全不相干。相等包括equals方法、instanceof方法等。

  public static void main(String[] args) throws Exception{
      ClassLoader classLoader = new ClassLoader() {
          @Override
          public Class<?> loadClass(String name) throws ClassNotFoundException {
              try {
                  String fileName = name.substring(name.lastIndexOf(".")+1) + ".class";
                  InputStream is = getClass().getResourceAsStream(fileName);
                  if (null == is) {
                      return super.loadClass(name);
                  }
                  byte[] b = new byte[is.available()];
                  is.read(b);
                  return defineClass(name, b, 0, b.length);
              }catch (IOException e) {
                throw new ClassNotFoundException(name);
              }
          }
      };
      Object obj = classLoader.loadClass("ruleEngine.Test");
      System.out.println(obj.getClass());//ruleEngine.Test
      System.out.println(obj instanceof ruleEngine.Test);//false
  }

上面例子中,obj是通过我们自定义的类加载器加载出来的类定义的实例。而ruleEngine.Test是系统应用程序类加载器加载的,所有返回的是false。

双亲委派模型

系统提供的类加载器分为三种

我们的应用程序都是由这3种类加载器互相配合进行加载,如果有必要,还可以加入自定义类加载器。关系如图:


类加载器双亲委派模型
破坏双亲委派模型
Class.forName("com.mysql.jdbc.Driver");  
Connection conn=DriverManager.getConnection("jdbc:mysql://DBName?user=xxx&password=xxx");

这么做的缺点是将具体配置和代码写在一起,如果替换还要改代码。而有了JNDI之后,我们就可以将代码和配置解耦。
如下:

import javax.naming.Context;

Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS");
DataSource ds=(Datasource)datasourceRef;
Connection c=ds.getConnection();  

然后我们在配置文件中定义java:MySqlDS这个对象的构造参数就可以了。

上一篇 下一篇

猜你喜欢

热点阅读