学习自周志明老师的《深入理解Java虚拟机》第二版

类的加载时机

如上图所示:

  类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了: 加载、验证、准备、解析、初始化、使用和卸载 这7个阶段。其中, 验证、准备和解析这三个部分统称为连接(linking)

  其中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的“开始”(仅仅指的是开始,而非执行或者结束, 因为这些阶段通常都是互相交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段),而解析阶段则不一定(它在某些情况下可以在初始化阶段 之后再开始,这是为了支持Java语言的运行时绑定。

何时开始类的初始化

什么情况下需要开始类加载过程的第一个阶段:"加载"。虚拟机规范中并没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化。

  1. 创建类的实例
  2. 访问类的静态变量 (除常量【 被final修辞的静态变量】 原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变 量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变 final域的值那么每一块用到那个域的代码都需要重新编译。
  3. 访问类的静态方法
  4. 反射 如( Class.forName("my.xyz.Test") )
  5. 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
  6. 虚拟机启动时,定义了main()方法的那个类先初始化.

其中这个过程也可以参见鄙人的另外一篇博客。

类的加载过程

  

“加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段,在此阶段,虚拟机需要完成以下三件事情:

1、 通过一个类的全限定名来获取定义此类的二进制字节流。

2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

根据第三条我们可以看出并不是所有的对象都放在堆里面,现在看来至少Class对象是在方法区(HotSpot),提供了程序访问方法区中这些类型数据的外部接口。

  在加载的过程中,数组类的加载过程和普通非数组类的加载是不相同的,这一点在周老师的书上有详细的说明。

  加载阶段即可以使用系统提供的类加载器在完成,也可以由用户自定义的类加载器来完成。加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

Java语言本身是相对安全的语言,使用Java编码是无法做到如访问数组边界以外的数据、将一个对象转型为它并未实现的类型等,如果这样做了,编译 器将拒绝编译。但是,Class文件并不一定是由Java源码编译而来,可以使用任何途径,包括用十六进制编辑器(如UltraEdit)直接编写。如果 直接编写了有害的“代码”(字节流),而虚拟机在加载该Class时不进行检查的话,就有可能危害到虚拟机或程序的安全。

不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证 :文件格式验证、元数据验证、字节码验证和符号引用验证。

1、文件格式验证,是要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。

    验证魔数是否0xCAFEBABE;

    主、次版本 号是否正在当前虚拟机处理范围之内;

    常量池的常量中是否有不被支持的常量类型

    常量池中的各种索引是否有指向不存在的常量或者不符合类型的常量。

……该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区中, 经过这个阶段的验证后,字节流才会进入内存的方法区中存储,所以后面的三个验证阶段都是基于方法区的存储结构进行的。

2、元数据验证,是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。

  可能包括的验证如:

    这个类是否有父类;

    这个类的父类是否继承了不允许被继承的类;

    如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法

    类中的方法和字段是不是和父类的相关方法字段发生矛盾。

    ……

  本阶段主要对类的元数据进行语义校验,保证其合法性。

3、字节码验证,主要工作是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。

4、符号引用验证,发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在“解析阶段”中发生。

    验证符号引用中通过字符串描述的权限定名是否能 找到对应的类;

    在指定类中是否存在符合方法字段的描述符及简单名称所描述的方法和字段;

    符号引用中的类、字段和方法的访问性(private、 protected、public、default)是否可被当前类访问

    …………

  本阶段只要是保证解析动作的正常执行,否在会抛出java.lang.incompatibleClassChangeError

  验证阶段对于虚拟机的类加载机制来说,不一定是必要的阶段。

  如果所运行的全部代码确认是安全的, 可以使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载时间。

准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

  其中这种默认值一般是零值(引用 null),

解析

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

   符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

直接引用(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。

  对于同一个符号引用进行多次解析请求是很常见的事情,除invokedynamic指令外,虚拟机实现可以对第一次解析的结果进行缓存(在运行时常量池中记录直接引用,并把常量标识为已经解析状态)从而避免重复解析。

  解析动作主要针对类或着接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符。

周老师的书上详细的介绍了这七种字符解析过程,不再赘述。

  其中特别重要的一点是类方法的解析过程,在一定程度上给我们透露了多态的一些优先级顺序问题。可以参见鄙人博客

初始化

类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

  在准备阶段变量已经赋了一次系统要求的初始值,而在初始化阶段,根据程序员指定的主观计划区初始化类变量和其他资源。

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

  --->  <clinit>()方法是由编译器自动 收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的 ,编译器的收集顺序是由语句在源文件中出现的顺序来决定的,静态语句块只能访问到定义在他之前的变量。

  --->   <clinit>()方法与类的构造函数(init())不同,他不需要显示的调用父类构造器,虚拟机会保证子类的<clinit>()方法之前调用父类的<clinit>()方法,所以第一个被执行的<clinit>()方法的类肯定是Object.

  --->  由于父类<clinit>()方法先执行,所以父类中定义的静态语句块要优先于子类静态语句块。

  ---> <clinit>()方法对于类或者接口不是必须的,因为一个类中可以没有静态语句块

  ---> 一个接口中不允许使用静态语句块,但是仍然可以对变量进行赋值操作,因此接口和类一样,会生成<clinit>()方法,但是不同的是执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有在父接口中的变量被使用的时候,才会对父接口进行初始化。

接下来就详细介绍一下一道非常经典的面试题目

 class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = ; private SingleTon() {
count1++;
count2++;
} public static SingleTon getInstance() {
return singleTon;
}
} public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}

现在大家都知道了结果该是 1  0

为什么?

其他人是这么分析的:

1:SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
2:类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3:类初始化化,为类的静态变量赋值和执行静态代码快。singleton赋值为new SingleTon()调用类的构造方法
4:调用类的构造方法后count=1;count2=1
5:继续为count1与count2赋值,此时count1没有赋值操作,所有count1为1,但是count2执行赋值操作就变为0

我觉得上面顺序还是有点乱的

 1、略过其他阶段,直接到准备阶段,开始给类变量赋系统零值,singleton=null count1=0,count2=0

 2、到了初始化阶段,开始执行<clinit>方法,private static SingleTon singleTon = new SingleTon(); 触发类的初始化,调用实例构造方法,进而实现了两个变量的初始化,此时count1 = 1,count2=1

 3、 public static int count1;
     public static int count2 = 0;

  开始对这两个变量进行初始化,count1不变,count2 = 0;

这样就完成了整个过程。

为什么这么说呢,

 package com.volshell.loadingclass;

 public class SingleTon {

     public static int count1;
public static int count2 = ;
private static SingleTon singleTon = new SingleTon(); private SingleTon() {
count1++;
count2++;
} public static SingleTon getInstance() {
return singleTon;
}
}

和上面的代码不同,交换了一下他们的顺序,使用相同的测试代码

这次的输出确是:1 1

大开眼界了~~~~

JVM -- 类加载的更多相关文章

  1. JVM类加载过程学习总结

    JVM类加载过程学习总结 先不说JVM类加载的原理,先看实例: NormalTest类,包含了一个静态代码块,执行的任务就是打印一句话. /** * 在正常类加载条件下,看静态代码块是否会执行 * @ ...

  2. JVM类加载续

    上一篇理解了JVM类加载过程的第一个阶段,这篇来说说剩下的阶段:验证.准备.解析.初始化.需要注意的是,这些阶段(解析除外)只是按照这个顺序开始,但是执行的过程中可能存在交叉. 验证:就是要对加载的二 ...

  3. JVM类加载以及执行的实战

    前几篇文章主要是去理解JVM类加载的原理和应用,这一回讲一个可以自己动手的例子,希望能从头到尾的理解类加载以及执行的整个过程. 这个例子是从周志明的著作<深入理解Java虚拟机>第9章里抄 ...

  4. JVM类加载机制以及类缓存问题的处理

    一:JVM类加载机制 和 类缓存问题的处理 当一个java项目启动的时候,JVM会找到main方法,根据对象之间的调用来对class文件和所引用的jar包中的class文件进行加载(其步骤分为加载.验 ...

  5. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  6. 【深入Java虚拟机】一 JVM类加载过程

    首先Throws(抛出)几个自己学习过程中一直疑惑的问题: 1.什么是类加载?什么时候进行类加载? 2.什么是类初始化?什么时候进行类初始化? 3.什么时候会为变量分配内存? 4.什么时候会为变量赋默 ...

  7. JVM总结(四):JVM类加载机制

    这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...

  8. JVM 类加载机制详解

    如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lan ...

  9. Java虚拟机(四):JVM类加载机制

    1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  10. JVM类加载机制详解(二)类加载器与双亲委派模型

    在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有: 1.通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件).而获取的方式,可 ...

随机推荐

  1. CentOS修改IP

    编辑 /etc/sysconfig/network-scripts/ifcfg-eth0 然后 service network restart DEVICE=eth0BOOTPROTO=noneNM_ ...

  2. IOS 表视图(UITableVIew)的使用方法(8)表视图的编辑功能(多选)

    在表视图的删除操作中,每次只能够对其中一个单元进行删除,如果想要同时删除多条记录,不得不挨个地进行标准的删除操作 所以如果能够实现多选的机制,无论是删除还是其他功能的嫁接,都会变得更加方便 当UITa ...

  3. QT全局热键(用nativeKeycode封装API,不跨平台)

    在网上找了很长时间,大家都提到了一个QT全局热键库(qxtglobalshortcut),支持跨平台.在这篇文章中,我将只展示出windows平台下全局热键的设置. 这里提供的方法是在MyGlobal ...

  4. golang ODBC 访问access数据库(问题解决之心理路程)

    最近项目需要,需要操作access,以前是用VC++ OLE访问,网络用ACE库,感觉很庞大...决定用go试试 网上用的最多的就是这个https://github.com/weigj/go-odbc ...

  5. HDU 1131 Count the Trees

    卡特兰数再乘上n的阶乘 #include<iostream> #include<cstdio> using namespace std; #define base 10000 ...

  6. nginx服务器屏蔽上游错误码

    平时的开发工作中,有时会遇到脚本权限不对导致403,文件被删除导致404,甚至后端业务异常导致5xx等情况,其实我们可以在服务器加上判断,检测当后端服务出现异常的时候前端返回一个指定的静态文件(也可以 ...

  7. 如何设计一个 iOS 控件?(iOS 控件完全解析)

    前言 一个控件从外在特征来说,主要是封装这几点: 交互方式 显示样式 数据使用 对外在特征的封装,能让我们在多种环境下达到 PM 对产品的要求,并且提到代码复用率,使维护工作保持在一个相对较小的范围内 ...

  8. SQL 语言划分

    从功能上划分,SQL 语言能够分为DDL,DML和DCL三大类. 1. DDL(Data Definition Language)     数据定义语言,用于定义和管理 SQL 数据库中的全部对象的语 ...

  9. HttpModule、HttpHandler和Page的生命周期

    1.引言 2.两个处理步骤 2.1.创建ASP.NET环境 2.2.用触发的MHPM事件处理请求 3.什么事件中应该做什么 4.示例代码 5.深入ASP.NET页面事件 1.引言 这篇文章我们将试图理 ...

  10. python开发环境安装

    1.首先安装python-3.4.2.msi,此为python主程序,双击安装,根据自身的情况做选择,也可以使用默认设置,一路next也没什么问题. 2.设置环境变量=>编辑Path,在最后加上 ...