前言

java虚拟机是java跨平台的基石,本文的描述以jdk7.0为准,其他版本可能会有一些微调。java代码本身并不能为jvm识别,实际上在jvm中的表现形式为Class对象,一个java类从字节码到能够在jvm中正常运行,需要经过加载-》链接-》初始化三个步骤。

引用

  • java虚拟机规范
  • 非法向前引用讨论

虚拟机的启动

  • java虚拟机的启动是通过引导类加载器(Bootstrap Class Loader)创建一个初始类来完成,这个类是由虚拟机的具体实现指定。紧接着,JAVA虚拟机链接这个初始类,初始化并调用它的main方法。之后整个执行过程都是由对此方法的调用开始。
  • 启动过程如图所示:

加载

类加载器层次结构图

  • 在java中,所有的类都是对其第一次使用时,动态加载到JVM中。当程序创建第一个对类的静态成员(方法、变量)的引用时,就会加载这个类。这个证明了构造器也是类的静态方法,即使在构造器之前并没有使用static关键字,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。

定义

  • 注意加载只是类加载中的一个阶段,在加载阶段虚拟机主要做以下三件事情:

    • 通过一个类的全限定名来获取此类的二进制字节流,(例如class文件中的部分数据、zip包、applet等,执行该步骤的模块称为类加载器
    • 将该字节流所代表的静态存储结构转化为方法区的运行时数据结构
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  • 在加载阶段完成之后,虚拟机外部的二进制字节流就按照虚拟机所需要的格式存储在方法区中,然后在内存中实例化一个java.lang.class对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口。
  • java类的加载是由类加载器来完成的。类加载器分为两类:
    • 启动类加载器(bootstrap),JVM原生提供,使用C++实现(注意这里特指hotspot虚拟机,有一些不是)
    • 用户自定义类加载器(user-defined),用户自定义实现,继承自java.lang.ClassLoader类。
  • 类的加载方式分为两种:显式加载和隐式加载,这两种方式都是调用classloader类中的loadClass方法来完成类的实际加载工作的。直接调用Classloader中的loadClass方法是另外一种不常用的显式加载类的技术。
    • 显式加载:使用Class.forname的方式就是显式加载
    • 隐式加载:使用new创建实例就是隐式加载。
  • 类加载器有很多用途,例如java热替换技术,jvm中相同类的隔离等。

链接

  • 链接类或接口包括验证、准备、解析。其中解析是可选的部分。
  • java虚拟机规范允许灵活的选择链接发生的时机,但是必须符合以下规范:
    • 在类或者接口被链接之前,它必须被成功的加载过
    • 在类或者接口初始化之前,它必须被成功的验证及准备过
    • 程序的直接或者间接行为可能会导致链接发生,链接过程中检查到的错误应该在请求链接的程序处被抛出。

验证

  • 验证(verification)阶段用于确保类或者接口的二进制表示结构是正确的。验证过程中可能会导致某些额外的类或者接口被加载进来,但是不应该导致它们也需要验证或者准备。

准备

  • 准备(preparation)阶段的任务是为类或者接口的静态字段分配空间,并用默认值初始化这些字段,这个阶段不会执行任何的虚拟机字节码指令。(注意在初始化阶段会有显式的初始化器来初始化这些静态字段,所以准备阶段不做这些事情),注意以下几点:

    • 准备阶段进行内存分配的仅包括类变量(被static修饰),不包括实例变量。实例变量会在对象实例化时随着对象一起分配到java堆中。
  • 示例:
    • 这里的初始值“通常情况下”是数据类型的零值,例如public static int val=23;,那么在准备阶段过后,val的值将设置为0,而不是23。而把val值赋值为23的putstatic指令是程序被编译后,存放于类的构造器方法之中,所以把val值赋值为23的动作将在初始化阶段才会执行,但是如果类的字段属性表中存在ConstantValue属性(final修饰符),那么在准备阶段就会初始化完成,例如public static final int val=23,那么在准备阶段的val值就为23.
  • 具体的代码和字节码参加下图:

解析

  • 解析(Resolution)是根据运行时常量池的符号引用来动态决定具体值的过程,java虚拟机指令(anewarray,checkcast,getfield,getstatic,instanceof,putstatic,new,invokespecial,invokevirutal)等将符号引用指向运行时常量池。执行上述任何一条指令都需要对它的符号引用进行解析。
  • 本步骤是符号引用,它与直接引用的区别在于:
    • 符号引用只能告诉你怎么无歧义的定位到目标,该值明确规定在class文件中
    • 直接引用是直接指向目标,(指针、偏移量、句柄)。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般都不相同

初始化

  • 初始化是类加载的最后一步,在前面的类加载过程中,基本上动作都是由虚拟机主导和控制(除了用户自定义类加载器)。到了初始化阶段才开始真正的执行类定义中的JAVA程序代码(或者说是字节码)。初始化阶段可以看做是执行类构造器<cinit()>方法的过程

    • 该方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static)合并而成,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到之前定义的变量,但是可以赋值。比如下面的这段代码:
    • 虚拟机会保证父类的方法一定在自雷之前执行,因此第一个执行该方法的类一定是java.lang.Object。因此父类的赋值操作一定是在子类之前,即必须把父类中的所有赋值操作执行完毕后才会执行子类的赋值操作
    • 如果一个接口/类中没有静态语句块,也没有对变量的赋值操作,那么编译器就不会为该类生成方法。
    • 多个线程去初始化同一个类,那么只有一个线程去执行该类的方法,其他线程都需要阻塞等待,注意同一个类加载器中,一个类型只会被初始化一次。

案例

案例一-关于非法向前引用

案例二

  • 上述这段代码其实是有问题的,真正运行的时候会抛NPE:

    • 在java虚拟机启动的过程前期,加载和链接过程都是没有问题的。但是在初始化的步骤(执行方法)中,由于编译器收集顺序是语句在源文件中出现的顺序,而res变量的赋值操作在add静态语句块之后。因此在执行add方法的时候,res变量只有一个默认值null。因此会导致该段代码抛NPE

码字不易,如有建议请扫码

java虚拟机规范-加载、链接与初始化相关推荐

  1. java虚拟机预先加载哪些类_Java虚拟机JVM学习02 类的加载概述

    Java虚拟机JVM学习02 类的加载概述 类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对 ...

  2. java虚拟机及加载class文件的原理机制

    2019独角兽企业重金招聘Python工程师标准>>> (点击获取更多资料的下载) java 虚拟机 ‎2007‎年‎4‎月‎23‎日,‏‎14:18:00 | chenweicai ...

  3. JVM详解之:类的加载链接和初始化

    文章目录 简介 加载 运行时常量池 类加载器 链接 验证 准备 解析 初始化 总结 简介 有了java class文件之后,为了让class文件转换成为JVM可以真正运行的结构,需要经历加载,链接和初 ...

  4. 《java虚拟机规范SE7》整理——第四章:Class文件格式

    按照<java虚拟机规范SE7>章节顺序整理的笔记. 目录: ClassFile格式(注:也就是class文件的总结构) 描述符和签名 常量池 字段 方法 属性 Java虚拟机代码约束 C ...

  5. 《java虚拟机规范SE7》整理——第五章:加载,链接与初始化

    按照<java虚拟机规范SE7>章节顺序整理的笔记. 目录: 运行时常量池 虚拟机启动 创建和加载 链接 初始化 绑定本地方法实现 Java虚拟机退出 第四章:加载,链接与初始化 java ...

  6. Java深度历险(二)——Java类的加载、链接和初始化

    在上一篇文章中介绍了Java字节代码的操纵,其中提到了利用Java类加载器来加载修改过后的字节代码并在JVM上执行.本文接着上一篇的话题,讨论Java类的加载.链接和初始化.Java字节代码的表现形式 ...

  7. java类的加载,链接,初始化

    Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象.一个Java类从字节代码到能够在JVM中被使用,需要经过加载.链接和初始 ...

  8. 28 Java类的加载机制、什么是类的加载、类的生命周期、加载:查找并加载类的二进制数据、连接、初始化、类加载器、双亲委派模型、自定义类加载器

    28Java类的加载机制 28.1.什么是类的加载 28.2.类的生命周期 28.2.1.加载:查找并加载类的二进制数据 28.2.2.连接 28.2.3.初始化 28.3.类加载器 28.4.类的加 ...

  9. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

最新文章

  1. 使用PlanAhead查看Virtex-7系列FPGA的底层架构
  2. 日常生活小技巧 -- word中插入visio和视频
  3. python求扇形面积_Python随机生成均匀分布在单位圆内的点代码示例
  4. oracle事物的传播属性,spring事务的隔离级别和传播属性
  5. 嵌入式系统文件系统比较 jffs2, yaffs, cramfs, romfs, ramdisk, ramfs/tmpfs
  6. XMLhttp学习应用
  7. 51NOD-1028 大数乘法V2【大数】
  8. 文件与目录的默认权限与隐藏权限
  9. 这几个好用的简单流程图模板,你可不能错过
  10. 苹果怎样添加无线网络连接服务器,教你iphone8 icloud连接服务器时出错怎么办及苹果手机如何设置成指定名称的WiFi热点...
  11. mysql 磁盘已满_记一次 mysql 磁盘满解决过程
  12. win7上搭建ftp站点
  13. 怎样修心?不乱于心,不困于情。
  14. Geosoft Oasis.Montaj.v7.1.1简介
  15. 哨兵2号数据的下载(各种方法)
  16. SpringSecurity安全认证之:数据库方式权限认证
  17. 如何设计网页?快速制作网页秘诀!
  18. centos7安装mysql5.7.16_Linux学习三Centos7安装mysql5.7.16数据库的详细教程
  19. 【论文笔记】DEEP FEATURE SELECTION-AND-FUSION FOR RGB-D SEMANTIC SEGMENTATION
  20. 好不好用户说了算,看OPPO R11如何赢得一致好评?

热门文章

  1. php 语法 条件变量,C ++核心准则:注意条件变量的陷阱
  2. 优秀平面设计师必须拥有的设计思维
  3. 项目服务路由保存不成功_汽车延保服务有哪些项目?不了解的小伙伴快看过来...
  4. jndi连接池链接mysql_使用容器提供的数据库连接池--JNDI用法
  5. android studio使用nodejs本地服务器json数据_使用Node.js的Alexa技巧
  6. java 百分比相加_2019年Java面试题基础系列228道(5),快看看哪些你还不会?
  7. 图像分段线性变化_暗光也清晰的图像增强算法
  8. 正点原子探索者原理图_正点原子【STM32-F407探索者】第二十六章 DAC 实验
  9. Linux操作系统安装LAMP环境
  10. Windows Server 2008服务器管理新技巧6则第1/4页