JVM类加载机制

在Java语言里,类的加载、连接和初始化都是在程序运行期间完成的。这种方式虽然在性能上会一定的开销,但是它会是Java的应用程序具有更高的灵活性。

比如:

  • 我们在写一个类中用了一个接口的方法,现在这个类编译后的class中是不知道我们具体是哪个类实现的,只有等到了运行程序的时候才指定了具体的实现类。
  • 另外我们可以通过先定义类加载器,然后我们可以随时从任何地方加载一个二进制流来动态的加载一个类。
  • 可以实现动态的替换jsp,还有OSGI的热插拔技术

这些都是java提供给我们的可以用类加载机制来实现的功能

Java中类的生命周期

加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)

一个类的存在的顺序大致会按照这个顺序进行,但是也会存在特殊的情况,在初始化的时候去解析

Java中什么情况下需要对类进行初始化(阶段),再此之前其他的操作:加载、验证等已经完成

  1. 在使用new创建个对象时,或者使用这个类的静态方法或者静态变量(有一种情况除外,在静态变量被final修饰的时候不会初始化对象,因为这种对象在编译期间已经把变量放在了常量池中了。)
  2. 在使用java.lang.reflect包中的方法,也就是在使用反射的时候会触发初始化操作
  3. 初始化一个类的时候如果还没有初始化父类的时候会初始化父类
  4. 启动的时候会初始化执行类的主类(Main方法)
  5. JDK1.7加上了动态语言支持,就是在遇到REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄时这个类如果没有初始化才会触发其初始化。(就是类似于Js、Python动态语言,不需要事先确定这个参数的类型,当运行到的时候在去,如果发现没有初始化的话再去进行初始化这个类)

加载阶段

加载阶段虚拟机主要做了这三件事情:

  1. 通过一个类的全限定名(包名+类名)来获取定义此类的二进制流(得到Class二进制流)
  2. 把这个字节流锁代表的经他爱存储结构转化为方法区的运行时数据结构(把流转化成方法区里能够使用的数据结构)
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口(生成Class对象,注:Class对象比较特殊,在HotSpot虚拟机中它是在方法区内不是在堆中)

这三个阶段中可控性最强的就是第一个得到二进制流的阶段,我们可以通过各种方式来得到,比如从本地磁盘,从数据库,从网络上...等到流之后我们可以重写一个类的加载器的loadClass()方法,或者是使用JDK提供的引导类加载器来完成。

对于数组和其他引用类型来说有些区别,数组本身是不通过类加载器创建的,它是由Java虚拟机自己直接创建的。但是数组类与类加载器还是有很多关系的,具体如下:

  • 如果数组的组件类型是引用类型(就是数组去掉第一个维度的类型),那么就是使用对应的加载器加载组件类型,然后数组将会被组件类型的类加载器上被标识。
  • 如果数组的组件类型不是引用类型(比如int等基本数据类型),java虚拟机将会吧数组C标记与引导类加载器关联
  • 数组类的可见性与它的组件类型的可见性是一致的,如果组件类型不是引用类型,那么这个数组类的可见性就是public(就是数组这个类的可见性和它里面的类的可见性是一致的)

验证阶段

验证阶段是为了确保Class文件的字节流中包含的信息是符合房钱虚拟机要求的。
虽然Java本身是相对安全的,因为它有编译成Class这一步。编译器是有一定规则的,如果你写的不符合要求会拒绝编译。但我们知道Class文件并不是只靠Java源码编译过来的,它可以是通过任何途径获得(如果你自己用十六进制编辑器写了一个十六进制文件,只要符合要求也是可以运行的)。如果虚拟机没有检查输入的字节流那么可能会导致很严重的后果。(可能会有恶意的代码,或者不是恶意的但会造成程序的崩溃)

大致上验证阶段可以分为以下4个阶段:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备阶段

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些都会在方法区内分配。(这里的分配变量知识分配类变量,就是用static修饰的变量,不包括手里边了,实例变量将会在对象实例化的时候进行赋值。)
下图是基本数据类型的初始值,引用类型的初始值是null


注意:上面说的是通常的情况,有一种情况是比较特殊的。就是在用final修饰了之后会在初始化阶段直接初始化上ConstantValue的值。例如:

public static final int value =123;

这时候在编译阶段会吧value的值附上123了,准备阶段就自然会给value赋值成123

解析阶段

在解析阶段是把常量池中的符号引用转化成直接引用,符号引用是在编译成Class文件的时候生成的。
直接引用和符号引用之间的定义:

  • 符号引用:符号引用用一组符号聊描述所引用的目标,符号只需要没有重复的能定位到目标即可。这个目标是不一定会加载到内存中的,也就是说这时候目标还不存在。
  • 直接引用:直接引用是指向目标的指针、偏移量或者是句柄。直接引用和虚拟机内存布局有关系。也就是说如果有了直接引用就说明这个目标已经在内存存在了。

初始化阶段

初始化阶段必须是在前面的步骤都结束后才执行的最后一步。初始化阶段会真正的执行类中定义的java程序代码。
在初始化阶段是执行类构造器方法的过程(就是执行<clinit>类型的方法)。

  • <clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中语句合并产生的结果(static{})。这个执行顺序是由语句在源文件出现的顺序所决定的。也就是说静态语句块只能访问到定义在静态语句块之前的变量,但是定义在后边的变量可以在前面的静态语句块中被复制。但是不能访问
public class Test{
      static{
            i =0;                        
            System.out.print(i);           //这块会有编译错误非法向前引用
    }
    static int i = 1;
}
  • <clinit>方法与类的构造函数不同,它不需要显示的调用父类的构造器,因为虚拟机会保证在子类执行之前父类的<clinit>方法一定会执行成功。因此我们可以知道在虚拟机中第一个被执行的<clinit>方法一定是java.lang.Object

  • 接口中是不能使用静态语句块的,但是可以有静态变量赋值的操作。因此也会生成<clinit>方法。但接口月累不同的是执行接口不需要先执行父接口的方法,只有当付借款使用时才会初始化。

  • 虚拟机会保证一个类的<clinit>方法会被正确的加锁,也就是在多个线程初始化一个类时只会有一个线程去执行这个类的方法。注意这种可能会造成阻塞(其他线程不会在重新执行一次,一个类只会执行一次)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • JVM类加载机制 概述 类加载过程 加载 通过类的全限定名获取类的二进制流 将静态存储结构转化为方法区的运行时数据...
    东溪95阅读 3,009评论 0 15
  • 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接...
    卡巴拉的树阅读 1,878评论 1 6
  • 概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直...
    lwd45阅读 1,684评论 0 16
  • 简介 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。与那些在编译时需要...
    黄俊彬阅读 343评论 0 6
  • 一直以来从事的都是成熟的项目公司 所以这种账号的申请一般不会有机会接触到 庆幸的是现在入职一家软件公司 独立完成i...
    马铃薯蜀黍阅读 933评论 3 1