前面众多文章有关讲解的都是些JVM的内存与垃圾回收器相关信息,那么对于本篇开始咱们将把目光转移到Class文件与加载器身上去,去看看字节码文件里到底有些什么信息?是怎么加载到咱们内存里?html
================================前端
当Java源代码成功编译成字节码后,若是想在不一样的平台上面运行,则无须再次编译java
这个优点再也不那么吸引人了。Python、PHP、Per一、Ruby、Lisp等有强大的解释器面试
跨平台彷佛已经快成为一门语言必选的特性后端
Java虚拟机不和包括Java 在内的任何语言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联。数组
不管使用何种语言进行软件开发,只要能将源文件编译为正确的Class文件,那么这种语言就能够在Java虚拟机上执行。能够说,统一而强大的Class文件结构,就是Java虚拟机的基石、桥梁。网络
可访问官方入口查看详细的规范:访问地址oracle
遵照Java虚拟机规范,也就是说全部的JVM环境都是同样的,这样一来字节码文件能够在各类Jw上运行。框架
================================eclipse
前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVM规范的字节码文件
javac是一种可以将Java源码编译为字节码的前端编译器
Javac编译器在将Java源码编译为一个有效的字节码文件过程当中经历了4个步骤分别是
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编译器负责
================================
先来看看几个常见的面试题
在面对这些问题的时候,接下来先看下面这个示例代码是怎么回事
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 = 10 20
对于非静态的成员变量的赋值过程主要分为如下几种:
接下来咱们经过字节码指令的方式看看是否是与咱们说的步骤是差很少的?
此时咱们回到刚刚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类的方法
================================
源代码通过编译器编译以后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是jVM的指令
,而不像C、C++经由编译器直接生成机器码
。
================================
JAVA虚拟机的指令由一个字节长度的、表明着某种特定操做含义的操做码(opcode)以及跟随其后的零至多个表明此操做所需参数的操做数(operand)所构成
。
虚拟机中许多指令并不包含操做数,只有一个操做码,好比说咱们能够看看上面示例代码的
================================
================================
可访问官方入口查看详细的内容:访问地址
================================
任何一个Class文件都对应着惟一一个类或接口的定义信息,但反过来讲,Class文件实际上它并不必定以磁盘文件的形式存在(可网络传)。Class 本质是一组以8位字节为基础单位的二进制流。
================================
Class 的结构不像XML等描述语言,因为它没有任何分隔符号。
因此在其中的数据项,不管是字节顺序仍是数量,都是被严格限定的,哪一个字节表明什么含义,长度是多少,前后顺序如何,都不容许改变。
文件格式采用一种相似于c语言结构体的方式进行数据存储,这种结构中只有:无符号数和表
描述有层次关系的复合结构的数据
,整个Class 文件本质上就是一张表。因为表没有固定长度,因此一般会在其前面加上个数说明================================
Class文件的结构并非一成不变的,随着Java虚拟机的不断发展,老是不可避免地会对Class文件结构作出一些调整,可是其基本结构和框架是很是稳定的。
用四个字节来表示魔数,主要对应识别当前文件是否是一个Class文件的标识
当前字节码文件是在那个版本下运行的编译,分为大版本/小版本(主版本/副版本)
经过两个字节告诉我常量池多长,下面是该常量池的数组
用于当前标识是否为一个类仍是接口、权限是什么?有没有abstract修饰、final修饰等
用于指明当前类是什么名、父类是什么名、当前类实现的接口长度、存放的数组等信息
一个类可有多个字段、因此这里也要代表长度、以及存放字段的数组信息
当前类定义的方法,也有一个方法表的长度、以及存放方法的数组信息
咱们进行一个类的字节码的时候,通常方法里Code这些属性,就指的这