前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM篇【Java源文件和Class字节码文件对比】

JVM篇【Java源文件和Class字节码文件对比】

作者头像
Java廖志伟
发布2022-09-29 14:39:35
3160
发布2022-09-29 14:39:35
举报
文章被收录于专栏:高级开发进阶

在分析JVM相关知识之前,给大家分享一段代码,非常通俗易懂的代码。

代码如下:

代码语言:javascript
复制
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属性

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档