在分析JVM相关知识之前,给大家分享一段代码,非常通俗易懂的代码。
代码如下:
package com.test.util; import java.io.Serializable; public class Test implements Serializable { private static String name = "JVM"; public static void main(String[] args) { System.out.println(name); } }
我们逐个分析,首先我们有一个Test.java的源文件,源文件名称就是我们Class文件属性表中的SourceFile属性。(这个需要结合Class字节码文件结构来看)
字节码结构有:魔数,副版本号,主版本号,常量池容量计数器,访问标志,类索引,父类索引,接口索引集合,字段表,方法表,属性表等。
拿魔数来说,它是用来区分文件类型的一种标志,会占用开头的4个字节,之所以需要魔数来区分文件类型,是因为文件名后缀容易被修改,所以为了保证文件的安全性,将文件类型写在文件内部可以保证不被篡改。
魔数后面的4位就是版本号,也是4个字节,前2个字节表示次版本号,后2个字节表示主版本号,这二个版本号是为了标注jdk的一个版本,起到一个jdk版本兼容性的一个作用,比如说高版本的jdk代码不能使用低版本的jdk运行,这个时候主次版本号就起到这个作用。
版本号后二个字节就是常量池容量计数器,写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了,这是为了满足不引用任何一个常量池的项目,比如说匿名内部类,它没有类名,但是它的类名也需要存储到常量池里面,那只能指向常量池的第0号位置,又比如说Object类,它是所有类的父类,那它的父类指向的是常量池中的0的位置。
常量池后面就是访问标志,用两个字节来表示,其标识了类或者接口的访问信息,比如:这个.Class文件是类还是接口,是不是被定义成public,是不是abstract,如果是类,是不是被声明成final等。
访问标志后的两个字节就是类索引,通过类索引我们可以确定到类的全限定名。类索引后的两个字节就是父类索引,通过父类索引可以确定到父类的全限定名,通过这二个全限定名可以获取到类路径。
父类索引后的两个字节是接口索引计数器,接口索引计数器表示接口索引集合中接口的数量。
接口索引计数器后边二个字节是接口索引集合,它是按照当前类实现的接口顺序,从左到右依次排列在接口索引集合中。
接口索引集合后边二个字节是字段表计数器,用来表示字段表的容量,字段表计数器后边是字段表。我们知道,一个字段可以被各种关键字去修饰,比如:作用域修饰符(public、private、protected)、static修饰符、final修饰符、volatile修饰符等,所以也可以像类的访问标志那样,使用一些标识来标记字段。
字段表作为一个表,同样他也有自己的结构,比如说访问标志,字段名索引,描述符索引,属性计数器,属性集合。在Java语言中字段是无法重载的,两个字段的数据类型,修饰符不管是否相同,都必须要有不一样的名称,但是对于字节码文件来说,如果两个字段的描述符不一致,那这二个字段重名就是合法的。
字段表后边二个字节是方法表计数器,表示方法表的容量,方法表计数器后边紧跟的是方法表。和字段表类似,方法表里面也有自己的结构,比如说访问标志,方法名索引,描述符索引,属性计数器,属性集合。
方法表后边紧跟的是属性表计数器,属性表计数器后边紧跟的结构为属性表。属性表的两大特点:一个是限制比较宽松,没有顺序长度要求;一个是开发者可以根据自己的需求,向属性表中添加不重复的属性。通过上面一大堆的讲解,可以发现Class文件结构是以魔数开头,以属性表结尾的。
然后我们看代码的第一行,package com.test.util;这个package就是存放在常量池里面的。
接着看第二行,import java.io.Serializable;这个import后面的全限定名也是存放在常量池里面的。
接着分析第三行public class Test implements Serializable {
public表示的是访问标志
class表示的是类索引
Test表示的是类名称,存放在常量池里面
implements表示的是接口索引集合
Serializable表示的是接口名称,存放在常量池里面
接着分析第四行 private static String name = “JVM”;
private表示的是常量修饰符
static表示的是常量修饰符
String是字段表,索引指向常量池
name是字段名称,存放在常量池里面
以上这行代码没有用final修饰,在clinit中初始化
分析第五行public static void main(String[] args) {
public是方法修饰符
static方法修饰符
void方法表,索引指向常量池
main方法名,存放在常量池
String[]方法表
args方法表中的属性表中的MethodParameters
最后第六行System.out.println(name);这行表示方法代码,是方法表中的属性表中的Code属性
最后我们我们代码左边的行数在class文件中是属性表的LineNumberTable属性