引言

我们知道java代码编译后生成的是字节码,那虚拟机是如何加载这些class字节码文件的呢?加载之后又是如何进行方法调用的呢?

一 类文件结构

无关性基石

java有一个口号叫做一次编写,到处运行。实现这个口号的就是可以运行在不同平台上的虚拟机和与平台无关的字节码。这里要注意的是,虚拟机也是中立的,只要是符合规范的字节码,都可以被虚拟机接受,例如Groovy,JRuby等语言,都会生成符合规范的字节码,然后被虚拟机所运行,虚拟机不关心字节码由哪种语言生成。

类文件结构

class类文件是一组以8位为基础的二进制流,它包含以下几个部分:

魔数和class文件版本:类文件开头的四个字节被定义为CAFEBABE,只有开头为CAFEBABE的文件才可以被虚拟机接受,接下来四个字节为class文件的版本号,高版本JDK可以兼容以前版本的class文件,但不能运行以后版本的class文件。

常量池:可以理解为class文件中的资源仓库,它包含两大类常量:字面量和符号引用,字面量包含文本字符串,声明为final的常量值等,符号引用包含类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。

访问标志:常量池结束后,紧接着两个字节表示访问标志,用于识别一些类或接口层次的访问信息,例如是否是public,是否是static等。

类索引,父类索引,和接口索引集合:类索引用来确定这个类的全限定名,父类为父类的全限定名,接口索引集合为接口的全限定名。

字段表集合:用于描述接口或者类中声明的变量,但不包含方法中的变量。

方法表集合:用于表述接口或者类中的方法。

属性表集合:class文件,字段表,方法表中的属性都源自这里。

二  类加载机制

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换分析和初始化,最终形成可以被虚拟节直接使用的JAVA类型,这就是虚拟机的类加载机制。

类从被加载到虚拟机内存到卸载出内存的生命周期包括:加载->连接(验证->准备->解析)->初始化->使用->卸载。

初始化的5种情况:

  1. 使用new关键字实例化对象时,读取或设置一个类的静态字段,除被final修饰经编译结果放在常量池的静态字段,调用类的静态方法时。

  2. 使用java.lang.reflect包方法对类进行反射调用时。(Class.forName())。

  3. 初始化子类时,如果父类没有初始化。

  4. 虚拟机启动时main方法所在的类。

  5. 当使用JDK1.7动态语言支持时,java.lang.invoke.MethodHandle实例解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,且对应类没有进行初始化。

类加载过程

加载

加载是类加载的第一个阶段,虚拟机要完成以下三个过程:1)通过类的全限定名获取定义此类的二进制字节流。2)将字节流的存储结构转化为方法区的运行时结构。3)在内存中生成一个代表该类的Class对象,作为方法区各种数据的访问入口。

验证

目的是确保class文件字节流信息符合虚拟机的要求。

准备

为static修饰的变量赋初值,例如int型默认为0,boolean默认为false。

解析

虚拟机将常量池内的符号引用替换成直接引用。

初始化

初始化是类加载的最后一个阶段,将执行类构造器<init>()方法,注意这里的方法不是构造方法。该方法将会显式调用父类构造器,接下来按照java语句顺序为类变量和静态语句块赋值。

类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性。举一个例子:

package com.sinaapp.gavinzhang.bean;
import java.io.InputStream;public class App
{public static void main( String[] args ){ClassLoader myClassLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {try{String fileName = name.substring(name.lastIndexOf(".")+1)+".class";InputStream is = getClass().getResourceAsStream(fileName);if(is == null){System.out.println(fileName+ "is not find");return super.loadClass(name);}System.out.println("fileName: "+fileName);byte[] b = new byte[is.available()];is.read(b);return defineClass(name,b,0,b.length);}catch (Exception E){throw new ClassCastException(name);}}};try {Object obj = myClassLoader.loadClass("com.sinaapp.gavinzhang.bean.Resource").newInstance();Object obj1  = Class.forName("com.sinaapp.gavinzhang.bean.Resource").newInstance();System.out.println(obj instanceof com.sinaapp.gavinzhang.wesound.bean.Resource);System.out.println(obj1 instanceof com.sinaapp.gavinzhang.wesound.bean.Resource);}catch (Exception e){e.printStackTrace();}}
}

结果为:

可以看到,由自定义的加载类只能获取同包下的class,而系统的class不能被加载,而且由Class.forName()获取的类与自定义加载类得到的类不是同一个类。

根据五种初始化的条件,父类也会被初始化,但是,上边的代码运行结果显示,父类和接口都没有被初始化,这又是怎么回事呢?

系统提供了三种类加载器,分别是:启动类加载器(Bootstrap ClassLoader),该加载器会将<JAVA_HOME>\lib目录下能被虚拟机识别的类加载到内存中。扩展类加载器(Extension ClassLoader),该加载器会将<JAVA_HOME>\lib\ext目录下的类库加载到内存。应用程序类加载器(Application ClassLoader),该加载器负责加载用户路径上所指定的类库。

我们自定义的ClassLoader继承自应用程序类加载器,当自定义类加载器找不到所加在的类时,会使用启动类加载器进行加载,当启动类加载器加载不到时,由扩展类加载,扩展类加载不到时有应用程序类加载。这也是为什么上边的代码能够成功运行的原因。

三  字节码执行引擎

运行时栈帧结构

http://my.oschina.net/jiangmitiao/blog/470426  中讲到虚拟机栈是线程私有的,线程中会为运行的方法创建栈帧。

栈帧是虚拟机栈的栈元素,栈帧存储了局部变量表,操作数栈,动态连接,返回地址等信息。每一个方法的调用都对应着一个栈帧在虚拟机栈中的入栈和出栈。

局部变量表由方法参数,方法内定义的局部变量组成,容量以变量槽(Slot)为最小单位。如果该方法不是static方法,则局部变量表的第一个索引为该对象的引用,用this可以取到。

操作数栈最开始为空,由字节码指令往栈中存数据和取数据,方法的返回值也会存到上一个方法的操作数栈中。

动态连接含有一个指向常量池中该栈帧所属方法的引用,持有该引用是为了进行动态分派。

方法返回地址存放的是调用该方法的pc计数器值,当方法正常返回时,就会把返回值传递到上层方法调用者。当方法中发生没有可被捕获的异常,也会返回,但是不会向上层传递返回值。

方法调用

java是一门面向对象的语言,它具有多态性。那么虚拟机又是如何知道运行时该调用哪一个方法?

静态分派是在编译期就决定了该调用哪一个方法而不是由虚拟机来确定,方法重载就是典型的静态分派。

动态分派是在虚拟机运行阶段才能决定调用哪一个方法,方法重写就是典型的动态分派。

动态分派的实现:当调用一个对象的方法时,会将该对象的引用压栈到操作数栈,然后字节码指令invokevirtual会去寻找该引用实际类型。如果在实际类型中找对应的方法,且访问权限足够,则直接返回该方法引用,否则会依照继承关系对父类进行查找。实际上,如果子类没有重写父类方法,则子类方法的引用会直接指向父类方法。

基于栈的字节码执行引擎

不管是解释型语言还是编译型语言,机器都无法理解非二进制语言。高级语言转化成机器语言都遵循现代经典编译原理。即执行前对程序源码进行词法和语法分析,构建抽象语法树。C语言等编译型语言会由单独的执行引擎做这些工作,而Java语言等解释型语言语法抽象树由jvm完成。jvm可以选择通过解释器来解释字节码执行还是通过优化器生成机器代码来执行。

常用的两套指令集架构分别是基于栈的指令集和基于寄存器的指令集。

基于栈的指令集更多的通过入栈出栈来实现计算功能,例如1+1

    iconst_1  ;将1入栈iconst_1  ;将1入栈iadd      ;将栈顶两个元素取出相加并将结果入栈

基于寄存器的指令集更多的是使用寄存器来进行操作,例如1+1

mov eax,1 ;向eax中存1add eax,1 ;eax<-eax+1

总体来说,基于栈的指令集会慢一些,但是它与寄存器无关,更容易实现到处运行的目标。

总结

又到了该总结的时候了,类加载机制面试中很容易被问到,不幸的是,当时我并没有看这方面的知识。

class类文件结构的每一个部分都可以再深入下去,类文件结构是采用结构体的方式存储的,那么怎么知道集合的长度,各个属性又是怎么被标记的。

类加载机制中有且仅有的五种触发初始化的情况。类加载器的分类。

栈帧的结构,以及方法调用。

java语言的方法调用分为静态多分派,动态单分派。

JAVA 虚拟机类加载机制和字节码执行引擎相关推荐

  1. 《深入理解Java虚拟机》笔记5——类加载机制与字节码执行引擎

    第七章 虚拟机类加载机制 7.1 概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在J ...

  2. JVM类加载机制_字节码执行引擎_Java内存模型

    类加载机制: 类加载生命期:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Usi ...

  3. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  4. Class类文件结构、类加载机制以及字节码执行

    一.Class类文件结构 Class类文件严格按照顺序紧凑的排列,由无符号数和表构成,表是由多个无符号数或其他数据项构成的符合数据结构. Class类文件格式按如下顺序排列: 类型 名称 数量 u4 ...

  5. JAVA类加载对字节码的处理_深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)...

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 周志明的< ...

  6. 深入理解Java虚拟机(周志明第三版)- 第八章:虚拟机字节码执行引擎

    系列文章目录 第一章: 走近Java 第二章: Java内存区域与内存溢出异常 第三章: Java垃圾收集器与内存分配策略 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言 ...

  7. java虚拟机调用linux_Java虚拟机字节码执行引擎

    定义 Java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.在不同的虚拟机实现里,执行引擎在执行java代码 ...

  8. java 虚拟机 字节码,JAVA虚拟机:虚拟机字节码执行引擎

    "虚拟机"是一个相对"物理机"的概念,这两种机器都有代码执行能力. 物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的. 虚拟机的执行引擎由自己 ...

  9. 深入理解java虚拟机-第五章:虚拟机字节码执行引擎

    本章将介绍虚拟机如何调用方法 一.java虚拟机字节码执行引擎 执行引擎在执行代码的时候可能有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种. 执行流程:输入的是字节码文件 ...

最新文章

  1. 第一次接觸sbt會遇到的
  2. 为啥channel能做到线程安全
  3. 检查点(Checkpoint)过程如何处理未提交的事务
  4. let 和const
  5. SpringMVC+FreeMarker
  6. 【leetcode】Majority Element
  7. 如何将商业策略与项目管理相关联
  8. 每天CookBook之JavaScript-073
  9. paip.输出内容替换在Apache 过滤器filter的设置
  10. nginx80转443
  11. 多场景双师课堂解决方案
  12. 计算机桌面蓝字,教你电脑桌面图标有蓝色阴影怎么去掉
  13. 【释义详解】Software License (软件许可证)是什么?
  14. Centos8安装报错Error setting up base repository
  15. 突发!ST再发涨价函!6月1日起全线涨价!
  16. R-loop及两种测序技术的介绍
  17. 文本分类半监督学习--UDA
  18. 阿里mns超大消息传输
  19. linux如何安装中文字体库
  20. 跟我学XML (01)全网最简洁XML教程

热门文章

  1. android6.0权限管理工具EasyPermissionUtil
  2. Android--JNI编程详解
  3. Quartz2D之绘制一个简单的机器猫
  4. 为view添加约束constraints
  5. svn 合并问题 MERGE of '/svn/web': 200 OK (http://xx.xx.xx.xx)
  6. 今天提交了一个patch开心,呵呵
  7. JBookManager v1.00.2008314 (编辑管理您的Jar电子书)
  8. Algs4-2.3.8Quick.sort()在处理N个全部重复的元素时比较次数
  9. 第六章 核心API (二)
  10. jquery使用原型