JVM笔记 7 虚拟机类加载机制

124 阅读6分钟

类加载的整个生命周期包括:加载,验证,准备,解析,初始化,使用,卸载。 验证,准备和解析3个部分统称为连接。

image.png
解析阶段不一定按部就班执行,也可以在初始化后执行。

####1.类加载的时机 有且只有以下五种情况,必须对类做初始化,称为主动引用

  • 遇到new getstatic putstatic invokestatic 四条字节码命令的时候,如果没初始化过,需要立即进行初始化,比较的常见的字节码使用场景是:使用new关键字实例化对象,读取设置一个静态字段,或者调用一个静态方法的时候。
  • 使用Java反射机制的时候,如果没初始化,那么先要初始化
  • 当初始化一个类的时候,如果父类没初始化,那么需要先初始化父类
  • 当虚拟机启动的时候,用户需要指定一个主类(包含mian方法的),虚拟机会先初始化这个主类
  • java.lang.invoke.methodhandle 实例最后的解析结果是REF_getStatic , 或者 REF_putStatic REF_invokeStatic, (是动态的获取一个静态方法的意思吗)

接下来是被动引用

  • 子类引用父类的静态字段
  • —通过数组定义来引用类,不会触发此类的初始化
  • 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发

####2.加载 加载过程

  • 通过⼀个类的全限定名来获取定义此类的⼆进制字节流。
  • 将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。
  • 在内存中⽣存⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类 的各种数据的访问⼊⼝。

加载对象

  • 从ZIP包,也就是JAR EAR WAR格式的基础
  • 从网络读取,比如Applet
  • 运行时计算生成,也就是动态代理技术
  • 从其他文件生成,也就是JSP文件生成的Class
  • 从数据库读取,某些中间件服务器要用 比如SAP Netweaver

####3.验证

  • ⽂件格式验证(是否魔数开头,主次版本号是否可以处理.....主要是对二进制字节流做验证,通过验证的才会进入方法区中存储
  • 元数据验证 (是否有父类,父类是否不能被继承..... 主要是语义校验
  • 字节码验证
  • 符号引⽤验证

####4.准备 在java虚拟机加载class文件并且验证完毕之后,就会正式给类变量分配内存并设置类变量的初始值。这些变量所使用的内存都将在方法区分配。注意这里说的是类变量,也就是static修饰符修饰的变量,在此时已经开始做内存分配,同时也设置了初始值。比如在 public static int value = 123 这句话中,在执行准备阶段的时候,会给value分配内存并设置初始值0, 而不是我们想象中的123。对于常量即带有final修饰符的静态变量)则会直接设置其被赋予的值。

####5.解析 解析阶段是将常量池内的符号引⽤替换为直接引⽤的过程。解析动作主要针对类 或接⼝,字段,类⽅法,接⼝⽅法,⽅法类型,⽅法句柄和调⽤点限定符7类符号引⽤进⾏。

  • 符号引⽤:符号引⽤以⼀组符号来描述所引⽤的模板,符号可以是任何形 式的字⾯量,只要使⽤时能⽆歧义地定位到⽬标即可。符号引⽤与虚拟机实现的内存布局⽆关。

  • 直接引⽤:直接引用可以是直接指向⽬标的指针,相对偏移量或是⼀个能间接定位到⽬标的句柄。直接引⽤是和虚拟机实现的内存布局相关的。

####6.初始化 初始化过程其实就是执行类构造器 () 方法的过程

关于 () ⽅法

  1. ()⽅法由所有类变量的赋值动作和静态语句块合并产⽣的, 顺序由语句在源⽂件中出现的顺序所决定的,静态语句块只能访问 在静态语句块之前的变量,定义在后⾯的变量,静态语句块内可以 赋值但不能访问。
  2. 虚拟机会保证先调⽤⽗类的()⽅法,所以在虚拟机中第⼀个 被执⾏的()⽅法的类是java.lang.Object,也意味着⽗类中 定义的静态语句块要优先于⼦类。
  3. 接⼝也可能会⽣成()⽅法,因为接⼝可能存在变量初始化的 赋值操作。接⼝的()⽅法不需要先执⾏⽗接⼝的() ⽅法,只有⽗接⼝中定义的变量被使⽤时,⽗接⼝才会初始化。
  4. ()⽅法对于类或接⼝不是必需的,如果他们没有静态语句块 和静态变量的赋值操作,那么编译器可以不为他们⽣成()⽅ 法。
  5. 虚拟机会保证()⽅法是线程安全的。

当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口

  • 在初始化一个类时,并不会先初始化它所实现的接口。
  • 在初始化一个接口时,并不会先初始化它的父接口。
  • 因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化,只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

####7.类加载器 类加载器就是实现了通过一个类的权限定名获取类的二进制字节流的功能。⽐较两个类是否相等,只有在由同⼀个类加载 器加载的前提下才有意义,对于使⽤equals()⽅法,isAssignableFrom()⽅ 法,isInstance()⽅法和instanceof关键字的结果都有影响。

  • 双亲委派模型

从JDK1.2开始,java虚拟机规范推荐开发者使用双亲委派模式(ParentsDelegation Model)进行类加载,其加载过程如下: (1).如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。 (2).每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。 (3).如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。 双亲委派 模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。 通过双亲委派模型就可以解决一开始的那个自己重载类加载器导致不一致的问题。