前言

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

引用

虚拟机的启动

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

案例

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

  • 仍然以上面的代码为例,非法向前引用这种编译检查,是未了防止静态字段在被初始化之前就被独走默认值,这会导致问题难以诊断。可能有细心的读者会发现在方法体中不会出现该问题,比如下面的这段代码:
  • 这段代码不会抛错,最后执行结果为0.为什么方法和类可以消除向前引用,而变量不可以呢?这个是因为java运行时为了实现向前引用,在初始化所有字段之前会把所有的字段添加到符号表中,以便可以调用这些字段。不过由于还没有初始化这些字段,所以符号表中所有字段都使用默认的值。上图中的代码最后返回的额结果也是0.

案例二

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

java虚拟机规范-加载、链接与初始化的更多相关文章

  1. Java类的加载 链接 初始化

    原文地址 Java类的加载.链接和初始化.Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象.一个Java类从字节代码到能够 ...

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

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

  3. Java类的加载、链接和初始化

    一.Java的类加载机制回顾与总结: 我们知道一个Java类要想运行,必须由jvm将其装载到内存中才能运行,装载的目的就是把Java字节代码转换成JVM中的java.lang.Class类的对象.这样 ...

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

    本篇的话题,讨论Java类的加载.链接和初始化.Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象.一个Java类从字节代码 ...

  5. java类从加载、连接到初始化过程

    类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理解java其它机制将有重要作用. 每个类编译后产生一个Class对象,存储在.class文件中,JV ...

  6. JAVA类的加载、连接与初始化

    JAVA类的加载.连接与初始化 类的声明周期总共分为5个步骤1.加载2.连接3.初始化4.使用5.卸载 当java程序需要某个类的时候,java虚拟机会确保这个类已经被加载.连接和初始化,而连接这个类 ...

  7. Java中类的加载、连接和初始化

    Java中类的加载.连接和初始化 类的加载.连接和初始化 先介绍一下JVM和类 JVM和类: 当我们调用Java命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多 ...

  8. 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

    目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...

  9. jvm系列(一):java类的加载机制

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

随机推荐

  1. springBoot 静态变量@value取不到值

    在工具类中给静态变量初始化值,使用了springBoot的@Value注解,但是没有赋值成功,得到的是null @Value("${jdbc.url}")private stati ...

  2. 增强 Jupyter Notebook的功能

    增强 Jupyter Notebook的功能 Jupyter Notebook 是所有开发者共享工作的神器,它为共享 Notebooks 提供了一种便捷方式:结合文本.代码和图更快捷地将信息传达给受众 ...

  3. HDUSTOJ-1559 Vive la Difference!(简单题)

    1559: Vive la Difference! 时间限制: 3 Sec  内存限制: 128 MB提交: 18  解决: 14[提交][状态][讨论版] 题目描述 Take any four po ...

  4. PHP实现支付宝小程序用户授权的工具类

    背景 最近项目需要上线支付宝小程序,同时需要走用户的授权流程完成用户信息的存储,以前做过微信小程序的开发,本以为实现授权的过程是很简单的事情,但是再实现的过程中还是遇到了不少的坑,因此记录一下实现的过 ...

  5. nodejs遍历文件夹下并操作HTML/CSS/JS/PNG/JPG

    需求描述,由于工作的需要,需要将原本用于1280 720的网页改为1920 1080的网页(电视端页面).需求可以拆分为两部分,代码部分的修改以及图片的修改.在代码部分,需要将所有位置以及大小相关的值 ...

  6. 计算机系统结构总结_Multiprocessor & cache coherence

    Textbook:<计算机组成与设计——硬件/软件接口>  HI<计算机体系结构——量化研究方法>          QR 最后一节来看看如何实现parallelism 在多处 ...

  7. Spark Streaming整合Flume + Kafka wordCount

    flume配置文件 flume_to_kafka.conf a1.sources = r1 a1.sinks = k1 a1.channels = c1 a1.sources.r1.type = sp ...

  8. IIC通信协议详解

    IIC通信详解 IIC概述 IIC:两线式串行总线,它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据. 在CPU与被控IC之间.IC与IC之间进行双向传送,高速IIC总线一般可达400 ...

  9. Python 进程之间共享数据(全局变量)

    进程之间共享数据(数值型): import multiprocessing def func(num): num.value=10.78 #子进程改变数值的值,主进程跟着改变 if __name__= ...

  10. python基础知识之数据类型

    一.与用户的交互 古时候,我们去银行取钱,需要有一个银行业务员等着我们把自己的账号密码输入给他, 然后他去进行验证,成功后,我们再将取款金额输入/告诉他 骄傲的现代人,会为客户提供一台ATM机(就是一 ...