前言

后面泛滥文章无关解说的都是些JVM的内存与垃圾回收器相干信息,那么对于本篇开始咱们将把眼光转移到Class文件与加载器身上去,去看看字节码文件里到底有些什么信息?是怎么加载到咱们内存里?

一、Class文件的概述


字节码文件的跨平台性

================================

Java 语言:跨平台性(write one run anywhere)

当Java源代码胜利编译成字节码后,如果想在不同的平台下面运行,则毋庸再次编译

这个劣势不再那么吸引人了。Python、PHP、Per1、Ruby、Lisp等有弱小的解释器

跨平台仿佛曾经快成为一门语言必选的个性

java 虚拟机:跨语言的平台

Java虚拟机不和包含Java 在内的任何语言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联。

无论应用何种语言进行软件开发,只有能将源文件编译为正确的Class文件,那么这种语言就能够在Java虚拟机上执行。能够说,对立而弱小的Class文件构造,就是Java虚拟机的基石、桥梁。

JAVA语言和JVM的标准

可拜访官网入口查看具体的标准:拜访地址

恪守Java虚拟机标准,也就是说所有的JVM环境都是一样的,这样一来字节码文件能够在各种Jw上运行。

Java的前端编译器

================================

JAVA源代码遵循JVM标准可失常运行在JVM中

前端编译器的次要工作就是负责将合乎Java语法标准的Java代码转换为合乎JVM标准的字节码文件

javac是一种可能将Java源码编译为字节码的前端编译器

Javac编译器在将Java源码编译为一个无效的字节码文件过程中经验了4个步骤别离是

  • 词法解析
  • 语法解析
  • 语义解析
  • 生成字节码

前端编译器VS后端编译器

HotSpot VN并没有强制要求前端编译器只能应用javac来编译字节码,其实只有编译后果合乎JVW标准都能够被JVM所辨认即可

在Java的前端编译器畛域除了javac之外,还有一种被大家常常用到的前端编译器,那就是内置在Eclipse中的ECJ(EclipseCompiler for Java)编译器

和Javac的全量式编译不同,EC是一种增量式编译器

在Eclipse中,当开发人员编写完代码后,应用“Ctrl+S”快捷键时,ECJ编译器所采取的编译计划是把未编译局部的源码逐行进行编译,而非每次都全量编译

因而ECJ的编译效率会比javac更加迅速和高效,当然编译品质和javac相比大抵还是一样的

EC不仅是Eclipse的默认内置前端编译器,在Tomcat中同样也是应用ECJ编译器来编译jsp文件

因为ECJ编译器是采纳GPLv2的开源协定进行源代码公开,所以大家能够登录eclipse官网下载ECJ编译器的源码进行二次开发。

默认状况下,IntelliJ IDEA应用javac编译器。(还能够本人设置为Aspect]编译器ajc)

前端编译器并不会间接波及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的3IT编译器负责

透过字节码指令看代码细节

================================

先来看看几个常见的面试题

  • 类文件构造分几个局部?
  • 字节码都有哪些?Integer x = 5,int y = 5 比拟 x == y都通过哪些步骤

在面对这些问题的时候,接下来先看上面这个示例代码是怎么回事

public class IntegerTesti        public static void main( String[] args) {                   Integer x = 5;        int y = 5;                System.out.print1n(x == y);                      Integer i1 = 10;        Integer i2 = 10;                System.out.print1n(i1 == i2);                Integer i3 = 128;        Integer i4 = 128;        System.out.println(i3 == i4);    }}

依照咱们后面的常识,咱们能够回顾一下看看操作数栈与局部变量表里是怎么样操作的?

接下来咱们能够在应用一个示例代码来领会这种过程

public class stringTest {    public static void main( string[] args) i        string str = new string("hello") + new String("world");        String str1 ="hellowor1d";        system.out.println(str == str1);        }}

咱们回顾回顾后面知识点,看看第一行代码 + 符号做了些什么事件

接下来咱们能够在应用一个示例代码来领会这种过程

class Father{    int x = 10;        public Father(){        this.print();        x = 20;    }        public void print() {        system.out.println( "Father.x = " + x);    }}
class son extends Father{    int x = 30;        public son(){       this.print();       x = 40;    }        //重写父类的办法    public void print(){        system.out.print1n( "Son.x = ” +x);    }}

此时咱们用一个Test类调用这里两个类,看看将程序运行后会输入什么呢?

public class SonTest{    public static void main(String[] args) {        Father f = new Son();        system.out.println(f.x);    }}//运行后果如下:Son.x = 0; Son.x = 30; 20

这时会有小伙伴好奇了,为什么会是这样的一个输入后果呢?咱们先看简略的是什么状况

public class SonTest{    public static void main(String[] args) {        Father f = new Father();        system.out.println(f.x);    }}//运行后果如下:Father.x = 1020

对于非动态的成员变量的赋值过程次要分为以下几种:

  • 默认初始化
  • 显示初始化/代码块初始化
  • 结构器中初始化
  • 有对象后,可通过对象.属性名调用(权限容许状况下)

接下来咱们通过字节码指令的形式看看是不是与咱们说的步骤是差不多的?

此时咱们回到刚刚Test调用Son类的状况,进行剖析看看

public class SonTest{    public static void main(String[] args) {        Father f = new Son();        system.out.println(f.x);    }}//运行后果如下:Son.x = 0; Son.x = 30; 20

接下来咱们通过字节码指令的形式看看Son做了哪些事件

之前咱们剖析了Father的结构器办法有哪些,以及做了哪些事件

此时调用Father的结构器时,应该执行print办法,然而Son类重写了所以调用Son类的办法

二、虚拟机的基石:CLass文件


字节码文件里是什么?

================================

源代码通过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是jVM的指令,而不像C、C++经由编译器间接生成机器码

什么是字节码指令(byte code)?

================================

JAVA虚拟机的指令由一个字节长度的、代表着某种特定操作含意的操作码(opcode)以及追随其后的零至多个代表此操作所需参数的操作数(operand)所形成

虚拟机中许多指令并不蕴含操作数,只有一个操作码,比如说咱们能够看看下面示例代码的

如何解读供虚拟机解释执行的二进制字节码

================================

形式一:采纳notepad++,装置HEX-Edirot插件或者Binary Viewer插件

形式二:IDEA插件: jclasslib 或jclasslib bytecode viewer客户端工具

形式三:应用Javap指令:jdk自带反解析工具

三、Class文件的构造


官网文档地位

================================

可拜访官网入口查看具体的内容:拜访地址

Class 类的实质

================================

任何一个Class文件都对应着惟一一个类或接口的定义信息,但反过来说,Class文件实际上它并不一定以磁盘文件的模式存在(可网络传)。Class 实质是一组以8位字节为根底单位的二进制流。

Class 文件格式

================================

Class 的构造不像XML等描述语言,因为它没有任何分隔符号。

所以在其中的数据项,无论是字节程序还是数量,都是被严格限定的,哪个字节代表什么含意,长度是多少,先后顺序如何,都不容许扭转。

文件格式采纳一种相似于c语言构造体的形式进行数据存储,这种构造中只有:无符号数和表

  • 无符号数属于根本的数据类型,以u1、u2、u4、u8来别离代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数能够用来形容数字、索引援用、数量值或者依照UTF-8编码形成字符串值。
  • 表是由多个无符号数或者其余表作为数据项形成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于形容有档次关系的复合构造的数据,整个Class 文件实质上就是一张表。因为表没有固定长度,所以通常会在其后面加上个数阐明

Class 文件构造

================================

Class文件的构造并不是变化无穷的,随着Java虚拟机的一直倒退,总是不可避免地会对Class文件构造做出一些调整,然而其根本构造和框架是十分稳固的。

Class文件的总体构造如下:
  • 魔数
  • class文件版本常量池
  • 拜访标记
  • 类索引,父类索引,接口索引汇合
  • 字段表汇合
  • 办法表汇合
  • 属性表汇合

魔数构造解说:

用四个字节来示意魔数,次要对应辨认以后文件是不是一个Class文件的标识

Class文件版本构造解说:

以后字节码文件是在那个版本下运行的编译,分为大版本/小版本(主版本/副版本)

常量池构造解说:

通过两个字节通知我常量池多长,上面是该常量池的数组

拜访标识标识解说:

用于以后标识是否为一个类还是接口、权限是什么?有没有abstract润饰、final润饰等

类索引、父类索引、接口索引汇合解说:

用于指明以后类是什么名、父类是什么名、以后类实现的接口长度、寄存的数组等信息

字段表汇合解说:

一个类可有多个字段、所以这里也要表明长度、以及寄存字段的数组信息

办法表汇合解说:

以后类定义的办法,也有一个办法表的长度、以及寄存办法的数组信息

属性表汇合解说:

咱们进行一个类的字节码的时候,个别办法里Code这些属性,就指的这