字节码文件的跨平台性:
Java语言:跨平台的语言 ①当Java源代码成功编译为字节码后,如果想在不同的平台上运行,则无须再次编译 ②这个优势不再那么吸引人了。Python PHP perl ruby lisp等有强大的解释器 ③跨平台似乎已经快成为一门语言必须的特征Java虚拟机:跨语言的平台: (Java虚拟机不和包括java在内的任何语言绑定,它只与class文件这种二进制文件格式所关联。无论使用何种语言进行软件开发,只要将源文件编译为正确的Class文件,那么这种语言就可以在Java虚拟机上执行,可以说,统一而强大的Class文件结构,就是Java虚拟机的基石、桥梁)想要让一个Java程序正确地运行在JVM中,Java源码就必须要编译为符合JVM规范的字节码。 ①前端编译器的主要任务就是负责将符合Java语言规范的Java代码转换为符合JVM规范的字节码文件 ②javac是一种能够将Java源码编译为字节码的前端编译器 ③javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤:分别是词法解析、语法解析、语义解析以及生成字节码透过字节码指令看代码细节:
Integer:透过字节码可以看出,当Integer的范围在-128 - +127之间会在数组中直接拿取数值,超过这个范围会重新new对象 public class IntegerTest { public static void main(String[] args) { /* * 透过字节码可以看出,当Integer的范围在-128 - +127 之间会在数组中直接拿取数值 * 超过这个范围会重新new对象 * */ Integer x = 5; int y = 5; System.out.println(x == y); Integer i1 = 10; Integer i2 = 10; System.out.println(i1 == i2);//true Integer i3 = 128; Integer i4 = 128; System.out.println(i3 == i4);//false } 在父类中调用方法,如果子类有重写,那么调用的是子类的方法,如代码中的this.print( ) /* 成员变量(非静态的)的赋值过程: ① 默认初始化 - ② 显式初始化 /代码块中初始化 - ③ 构造器中初始化 - ④ 有了对象之后,可以“对象.属性”或"对象.方法" 的方式对成员变量进行赋值。 */ 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; // float x = 30.1F; public Son() { this.print(); x = 40; } public void print() { System.out.println("Son.x = " + x); } } public class SonTest { public static void main(String[] args) { Father f = new Son(); System.out.println(f.x); } } 静态变量随着类的加载就加载了,在链接(准备阶段)会为静态变量赋予默认的初始化值,在初始化阶段会显示的赋值 /* 输出结果: 父类的静态成员属性 父类静态代码块 子类的静态成员属性 子类静态代码块 父类构造方法 子类构造方法 */ public class People { private final static String parentStaitc = "父类的静态成员属性"; public static void main(String[] args) { People people = new Child(); } public People() { System.out.println("父类构造方法"); } static{ System.out.println(parentStaitc); System.out.println("父类静态代码块"); } } class Child extends People { private final static String childStaitc = "子类的静态成员属性"; public Child() { System.out.println("子类构造方法"); } static{ System.out.println(childStaitc); System.out.println("子类静态代码块"); } }机器码和字节码:
机器码就是说计算机能读懂的代码,简单点说就是给计算机执行的二进制代码.
字节码,是JAVA语言专有的,它是让JVM来执行的二进制代码
虽然都是二进制代码,但是由于执行它的环境不一样,所以它们存在一些指令集上的区别
查看字节码时一般转化成十六进制来看, 一个字节对应两个十六进制数 ①一个字节包含8个二进制位, ②一个十六进制位可表示4个二进制位, ③所以,一个字节可以由2个十六进制表示
纵观Class文件结构:
class文件是二进制文件,它的内容具有严格的规范,文件中没有任何空格,全是连续的0/1。class文件中的所有内容被分为两种类型:无符号数 和 表。无符号数 ①它表示class文件中的值,这些值没有任何类型,但有不同的长度。根据这些值长度的不同分为:u1、u2、u4、u8,分别代表1字节的无符号数、2字节的无符号数、4字节的无符号数、8字节的无符号数。表 ②class文件中所有数据(即无符号数)要么单独存在,要么由多个无符号数组成二维表。即class文件中的数据要么是单个值,要么是二维表。Class文件结构 class文件的组织结构:
魔数本文件的版本信息常量池访问标志类索引父类索引接口索引集合字段表集合方法表集合属性表的集合Class文件的构成1:魔数:
class文件的头4个字节称为魔数,用来表示这个class文件的类型。魔数的作用就相当于文件后缀名,只不过后缀名容易被修改,不安全,因此在class文件中标示文件类型比较合适。class文件的魔数是用16进制表示的“CAFEBABE”,非常具有浪漫主义色彩,谁说程序员的情商都很低!Class文件的构成2:版本信息:
紧接着魔数的4个字节是版本号。它表示本class中使用的是哪个版本的JDK。在高版本的JVM上能够运行低版本的class文件,但在低版本的JVM上无法运行高版本的class文件,即使该class文件中没有用到任何高版本JDK的特性也无法运行!Class文件的构成3:常量池:
常量池中存放两种类型的常量:字面值常量 ①字面值常量即我们在程序中定义的字符串、被final修饰的值。符号引用 ②符号引用就是我们定义的各种名字: 1、类和接口的全限定名 2、字段的名字 和 描述符 3、方法的名字 和 描述符常量池的特点: ①常量池长度不固定 常量池的大小是不固定的,因此常量池开头放置一个u2类型的无符号数,用来存储当前常量池的容量。JVM根据这个值就知道常量池的头尾来。 注:这个值是从1开始的,若为5表示池中有4个常量。 ②常量池中的常量由而为表来表示 常量池开头有个常量池容量计数器,接下来就全是一个个常量了,只不过常量都是由一张张二维表构成,除了记录常量的值以外,还记录当前常量的相关信息。 ③常量池是class文件的资源仓库 ④常量池是与本class中其它部分关联最多的部分 ⑤常量池是class文件中空间占用最大的部分之一Class文件的构成4:访问标志:
在常量池之后是2字节的访问标志。访问标志是用来表示这个class文件是类还是接口、是否被public修饰、是否被abstract修饰、是否被final修饰等。由于这些标志都由是/否表示,因此可以用0/1表示。访问标志为2字节,可以表示16位标志,但JVM目前只定义了8种,未定义的直接写0.Class文件的构成5:类索引、父类索引、接口索引集合:
类索引、父类索引、接口索引集合是用来表示当前class文件所表示类的名字、父类名字、接口们的名字。它们按照顺序依次排列,类索引和父类索引各自使用一个u2类型的无符号常量,这个常量指向CONSTANT_Class_info类型的常量,该常量的bytes字段记录了本类、父类的全限定名。由于一个类的接口可能有好多个,因此需要用一个集合来表示接口索引,它在类索引和父类索引之后。这个集合头两个字节表示接口索引集合的长度,接下来就是接口的名字索引。Class文件的构成6:字段表的集合:
字段表集合用于存储本类所涉及到的成员变量,包括实例变量和类变量,但不包括方法中的局部变量。每一个字段表只表示一个成员变量,本类中所有的成员变量构成了字段表集合。什么是描述符? ①成员变量(包括静态成员变量和实例变量) 和 方法都有各自的描述符。 ②对于字段而言,描述符用于描述字段的数据类型; ③对于方法而言,描述符用于描述字段的数据类型、参数列表、返回值。 ④在描述符中,基本数据类型用大写字母表示,对象类型用“L对象类型的全限定名”表示,数组用“[数组类型的全限定名”表示。 ⑤描述方法时,将参数根据上述规则放在()中,()右侧按照上述方法放置返回值。而且,参数之间无需任何符号。字段表集合的注意点 ①一个class文件的字段表集合中不能出现从父类/接口继承而来字段; ②一个class文件的字段表集合中可能会出现程序猿没有定义的字段 ③如编译器会自动地在内部类的class文件的字段表集合中添加外部类对象的成员变量,供内部类访问外部类。 ④Java中只要两个字段名字相同就无法通过编译。但在JVM规范中,允许两个字段的名字相同但描述符不同的情况,并且认为它们是两个不同的字段。Class文件的构成7:方法表的集合:
在class文件中,所有的方法以二维表的形式存储,每张表来表示一个函数,一个类中的所有方法构成方法表的集合。方法表的结构和字段表的结构一致,只不过访问标志和属性表集合的可选项有所不同。方法表的属性表集合中有一张Code属性表,用于存储当前方法经编译器编译过后的字节码指令。方法表集合的注意点 ①如果本class没有重写父类的方法,那么本class文件的方法表集合中是不会出现父类/父接口的方法表; ②本class的方法表集合可能出现程序猿没有定义的方法,编译器在编译时会在class文件的方法表集合中加入类构造器和实例构造器。 ③重载一个方法需要有相同的简单名称和不同的特征签名。JVM的特征签名和Java的特征签名有所不同: 1、Java特征签名:方法参数在常量池中的字段符号引用的集合 2、JVM特征签名:方法参数+返回值Class文件的构成8:属性表的集合: 方法表的code属性 :方法体的指令描述
JVM字节码指令集大全及其介绍 概述:
i++和++i问题:
i++: ++i:类的生命周期:
加载完成的操作: 二进制流获取方式: 类模型和Class实例的位置:
数组类的加载:
链接:
验证(Verify): ①目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。 ②主要包括四种验证,文件间格式验证,元数据格式,字节码验证,符号引用用验证。
准备(Prepare): ①为类变量分配内存并且设置该类变量的默认初始值,即零值。 ②这里不包含final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化。 ③这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
解析(Resolve): ①将常量池内的符号引用转换为直接引用的过程。 ②事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行。 ③符号引用就是一组符号来描述苏引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中,直接引用就是直接指向目标的指针、相对偏移量或一个简介定位到目标的句柄。 ④解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
初始化:
初始化阶段就是执行类构造方法()的过程。此方法不需要定义,是Javac编译器自动收集类中的所有类变量的复制动作和静态代码块中的语句合并而来。构造器方法中指令按语句在源文件中出现的顺序执行。()不同于类的构造器。(关联:构造器是虚拟机视角下的())若该类有父类,JVM会保证子类的()执行前,父类的()已经执行完毕。虚拟机必须保证一个类的()方法在多线程下被同步加锁。Java静态内部类加载:
静态内部类在使用时才会被加载。也就是说静态内部类在外部类被使用时并不会被加载。一、非静态内部类: 1、变量和方法不能声明为静态的。(类的编译顺序:外部类–静态方法或属性–内部类) 2、实例化的时候需要依附在外部类上面。比如:B是A的非静态内部类,实例化B,则:A.B b = new A().new B(); 3、内部类可以引用外部类的静态或者非静态属性或者方法。二、静态内部类: 1、属性和方法可以声明为静态的或者非静态的。 2、实例化静态内部类:比如:B是A的静态内部类,A.B b = new A.B();(可以看出需要new()才可以使用因此证明静态内部类在使用时才会被加载) 3、内部类只能引用外部类的静态的属性或者方法。 4、如果属性或者方法声明为静态的,那么可以直接通过类名直接使用。比如B是A的静态内部类,b()是B中的一个静态属性,则可以:A.B.b();主动使用和被动使用:
类的使用:
类的卸载:
类的加载的分类: 类加载器的重要性: 命名空间: 类加载器的基本特征:
ClassLoader:
双亲委派机制:
热替换:
热替换也叫热部署。程序不停地运行简称热。沙箱安全机制: 自定义类的加载器: