引子   

     本是新年,怎奈新冠肆掠,路上行人,男女老少几乎是全副口罩,形色匆匆;偶尔有一两个裸露口鼻的,估计都是没囤到口罩的,这几天药店几乎都是贴上大字:口罩没货。看着网络上病毒消息满天飞,我也响应在家做贡献的号召。上班时,都是早出晚归,几乎只有早上能看到娃,出门时,娃每次都说:see you tomorrow 。赶上疫情,天天在家带娃,终于可以多多陪伴了;别说,带娃还真比上班费神。想着小时候,特别想有一个玩具小船,动手给娃做了一个,附图一张。把娃带好了,也得思考下学习的事儿。学习java有段时间了,想起之前学习java时,看着Class<?> 这样的符号就怵,不明白其表示的含义,又重读《java编程思想》第14章, 趁着这样的时间好好整理了一下,直面当时的怵。

Class对象

  Class<?> - 类的类型,是运行时类型信息,也就是 RTTI - RTTI - RunTime Type Infomation;所谓一切皆对象,类也是一个对象,而类的类型信息,就叫做Class对象。RTTI使得我们可以在运行时发现和使用类型信息。以前觉得RTTI离我很远(java菜鸟),其实多态机制正是因为类对象携带了类的类型信息,在类型转化时可以识别到对象的类型。举个栗子,如下, ChildClassTest向上转型为 SuperClassTest时,丢失了子类类型信息,而运行时,向下转型时,又使用RTTI 获取了实际类型,从而可以正常打印出 ChildClassTest。但是,为什么向上转型丢失类型信息,再向下转型时,可以获取到实际的类型,这要从RTTI 的工作原理说起了。

public class SuperClassTest {
} public class ChildClassTest extends SuperClassTest {
}
SuperClassTest superClassTest = new ChildClassTest();
PrintTool.print(superClassTest);
#打印 

com.hj.tool.klass.ChildClassTest@685f4c2e

 

RTTI的工作原理

  前面的例子中,这种在运行时,确定类的实际类型是虚拟机的动态分派机制。 为啥对象可以找到类型信息呢,因为普通对象是被Class对象创建的,而Class对象包含了类的有关信息。下图为Class对象的加载过程,当我们在创建普通对象时,会先判断此类的Class对象是否加载(每个类都有一个Class对象),如果已经加载,就使用Class对象生成普通对象;如果未加载,就需要通过字节码创建Class对象,再生成普通对象。在虚拟机层面,则是运行时,把变量 new ChildClassTest()的引用存放于 LocalVariableTable 的 slot中,执行print时(其实就是执行toString()方法),实际是执行invokevirtual 指令,找到方法的实际接收者,再执行toString()。而 invokevirtual 解析的过程,根据《深入理解java虚拟机》中的描述过程如下:

1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
3)否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。 由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,
这个过程就是Java语言中方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

  

Class文件结构

既然Class对象来源于字节码,那就来分析下.class文件的内容,引用《java虚拟机规范》中关于classFile的格式如下:“每个class文件都由字节流组成,每个字节含有8个二进制位。所有16位,32位,64位长度的数据将通过构造成2个,4个,8个连续的8位字节来表示。”规范中定义了每个项的字节长度,以及结构,分析的过程还是挺有意思的:原来我们写的代码都被编译成那样的格式。说来也惭愧,java用了这么久,连一个简单的.class文件都没有分析过。

  每个class文件都对应如下结构(JDK 8,不同版本结构不是完全一样),其中包括两类数据类型:u(1/2/4), _info; u 后面的数字表示n个字节,而 每个_info 又有特定的格式。 具体可以参看《java虚拟机规范 se 8》第4章内容。

  

   我们来看下具体的一个类,

package com.hj.tool.klass;

/**
* @Description TODO
* @Author jijunjian
* @Date 2020-01-27 20:47
* @Version 1.0
*/
public class ByteCodeTest { private int m ; public int inc(){
return m+1;
}
}

使用xxd  ByteCodeTest.class 查看编译后的.class文件(16进制),得到如下内容。乍一看,是不是完全看不到,我们的类是如何组织的哇。等我们按class文件的格式整理后,情况就完全不一样了。

  

cafe babe 0000 0034 0016 0a00 0400 1209
0003 0013 0700 1407 0015 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 204c 636f 6d2f 686a
2f74 6f6f 6c2f 6b6c 6173 732f 4279 7465
436f 6465 5465 7374 3b01 0003 696e 6301
0003 2829 4901 000a 536f 7572 6365 4669
6c65 0100 1142 7974 6543 6f64 6554 6573
742e 6a61 7661 0c00 0700 080c 0005 0006
0100 1e63 6f6d 2f68 6a2f 746f 6f6c 2f6b
6c61 7373 2f42 7974 6543 6f64 6554 6573
7401 0010 6a61 7661 2f6c 616e 672f 4f62
6a65 6374 0021 0003 0004 0000 0001 0002
0005 0006 0000 0002 0001 0007 0008 0001
0009 0000 002f 0001 0001 0000 0005 2ab7
0001 b100 0000 0200 0a00 0000 0600 0100
0000 0900 0b00 0000 0c00 0100 0000 0500
0c00 0d00 0000 0100 0e00 0f00 0100 0900
0000 3100 0200 0100 0000 072a b400 0204
60ac 0000 0002 000a 0000 0006 0001 0000
000e 000b 0000 000c 0001 0000 0007 000c
000d 0000 0001 0010 0000 0002 0011

 

以下是整理后的结果,这个过程还是需要些耐心的。但是这个时间花得决绝物超所值。我解析了大部分内容,基本都注释了,其中常量池占了很多内容,但其实是最简单部分,method中关于code属性是比较麻烦的。不同版本编译得到的内容可能会有不同。

#魔数
cafe babe
#版本 jdk 8
0000 0034
# 常量池有21 个,第一个,是保留
0016
# 第一个常量
CONSTANT_Methodref_info{
u1 tag //
u2 class_index //指向CONSTANT_Class_info;表示类
u2 name_and_type_index //指向CONSTANT_NameAndType,表示方法名、方法描述符
} 0a tag 10
0004 class_index 指向 4
0012 name_and_type_index 指向 18 # 第二个常量 tag=9
CONSTANT_Fieldref_info{
u1 tag //
u2 class_index //指向CONSTANT_Class_info;既可以表示类、也可以表示接口
u2 name_and_type_index //指向CONSTANT_NameAndType,表示字段名、字段描述符
} 09 tag 9
0003 class_index 指向 3
0013 name_and_type_index 指向19 # 第三个常量 tag=7
CONSTANT_Class_info{
u1 tag //tag=7
u2 name_index // name_index是索引值,指向CONSTANT_Utf8_info
} 07 tag 7
0014 name_index 指向 20 com/hj/tool/klass/ByteCodeTest # 第4个常量 tag=7 07
0015 name_index 指向 21 # 第5个常量 tag=01
CONSTANT_Utf8_info{
u1 tag //
u2 length
u1 bytes[length] //长度为length的字符串数组
} 01 tag
0001 length
6d asc 109=m # 第6个常量 tag=01
01
0001 length
49 asc 73 I 表示int # 第7个常量 tag=01
01
0006
3c 69 6e 69 74 3e <init> # 第8个常量 tag=01 utf8 字符串数组
01
0003
28 29 56 ()V # 第9个常量 tag=01 utf8 字符串数组
01
0004
43 6f 64 65 Code # 第10个常量 tag=01 utf8 字符串数组
01
000f length=15
4c 69 6e 65 Line
4e 75 6d 62 65 72 number
54 61 62 6c 65 Table # 第11个常量 tag=01 utf8 字符串数组
01
0012
4c 6f 63 LocalVariableTable
61 6c 56
61 72 69
61 62 6c
65 54 61
62 6c 65 # 第12个常量 tag=01 utf8 字符串数组 01
0004
74 68 69 73 this # 第13个常量 tag=01 utf8 字符串数组 01
0020
4c 63 6f 6d
2f 68 6a 2f
74 6f 6f
6c 2f 6b 6c
61 73 73 2f
42 79 74 65
43 6f 64 65
54 65 73 74
3b
Lcom/hj/tool/klass/ByteCodeTest;
3b=; # 第14个常量 tag=01 utf8 字符串数组 01
0003
69 6e 63 inc # 第15个常量 tag=01 utf8 字符串数组
01
0003
28 29 49 ()I # 第16个常量 tag=01 utf8 字符串数组
01
000a
53 6f 75 72 63 65 46 69
6c 65
SourceFile # 第17个常量 tag=01 utf8 字符串数组
01
0011 17个
42
79 74 65 43 6f 64 65 54 65 73
74 2e 6a 61 76 61
ByteCodeTest.java # 第18个常量 tag=12 NameAndType CONSTANT_NameAndType{
u1 tag //
u2 name_index //指向CONSTANT_Utf8_info,表示名称
u2 descriptor_index //指向CONSTANT_Utf8_info,表示描述符
} 0c tag 12 nameAndType
0007 name_index 指向第7个常量 <init>
0008 descriptor_index 指向第8个常量 ()V # 第19个常量 tag=12 NameAndType
0c
0005 m
0006 I # 第20个常量 tag=01 utf8 字符串数组 01
001e
63 6f 6d 2f
68 6a 2f
74 6f 6f 6c 2f
6b
6c 61 73 73 2f 42 79 74 65 43 6f 64
65 54 65 73 74
com/hj/tool/klass/ByteCodeTest # 第21个常量 tag=01 utf8 字符串数组 01
0010
6a 61 76 61 2f 6c 61 6e
67 2f 4f 62 6a 65 63 74
java/lang/Object access_flags
0021 表示是public ,是1.2以后所以21 类索引,父类索引,接口索引
0003 类索引 2字节 指向第三个常量 class-info 又指向 和指向第20个
com/hj/tool/klass/ByteCodeTest 0004 父类索引 2字节 同理指向 java/lang/Object
0000 接口索引 无 0001 field_count u2 1个 field_info[1]
field_info{
u2 access_flags //表示字段的访问权限、属性
u2 name_index //对常量池的索引
u2 descriptor_index //对常量池的索引
u2 attributes_count //附加属性的数量
attribute_info attributes[attributes_count] //每个成员是attribute_info结构
} 0002 private
0005 name_index m
0006 descriptor_index I
0000 attributes_count 0 0002 method_count method_info{
u2 access_flags //表示方法的访问权限、属性
u2 name_index //对常量池的索引
u2 descriptor_index //对常量池的索引
u2 attributes_count//附加属性的数量
attribute_info attributes[attributes_count] //每个成员是attribute_info结构
} # 第一个 method init
0001 access_flags public
0007 name_index <init>
0008 descriptor_index ()V
0001 attributes_count 1 attribute_info{
u2 attribute_name_index //常量池索引
u4 attribute_length
u1 info[attribute_length]
} 0009 attribute_name_index Code
0000 002f attribute_length 47
0001 max_stack
0001 max_locals
0000 0005 code_attribute_length
2a
b7
0001 b100 00 00 02 00 0a 00
00 00 06 00 01 00 00 00 09 00
0b 00 00 00 0c 00 01 00 00 00
05 00 0c 00 0d 00 00 # 第二个method
0001 access_flags public
000e name_index 14 inc
000f descriptor_index 15 ()I
0001 attributes_count 1 attribute_info
0009 attribute_name_index Code
0000 0031 attribute_length 49 00 02 max_stack
00 01 max_locals 一个
00 00 00 07 code_length 7
2a aload_0 将第一个引用类型的本地变量
b4 getfield 获取指定类型的实例字段 m #下面这两个指令没弄明白是啥意思,
00 nop 不做
02 iconst_ml 将-1 推到栈顶 04 iconst_1 将1 推到栈顶
60 iadd 将栈顶两个相加,结果压入栈顶
ac ireturn 返回int 00 00 exception_table_length
00 02 attritutes_count 2 00 0a LineNumberTable
00 00 00 06 length=6
00 01 00 00 00 0e 00 0b LocalVariableTable 00 00 00 0c length =12
00 01 00
00 00 07
00 0c 00
0d 00 00 0001 attributes_count 1
0010 attribute_name_index 16 SourceFile
0000 0002 attribute_length 2
0011 sourcefile_index 17 指向常量池中 ByteCodeTest.java

结语

  文章写到这里,感觉非常艰难,一是感觉写得不知所云,估计只有自己能明白,二是感觉自己的理解还很浅显。没动手之前,感觉啥都理解了,真正开始动手吧,又感觉啥都没理解。这便是从输入到输出的真实过程;读只是输入,无法形成真正的理解,只有持续输出才能真正领悟,而这个输出的过程才是消化的过程。写得过程中,又不断翻阅资料,把原来点点的理解,连接成断断续续的线,希望以后可以再深入学习,把这些点点的东西,连成线,汇成面。

   成为一名优秀的程序员!

文章参考了很多《jjava编程思想》,《java虚拟机规范 se 8》,《深入理解java虚拟机》第二版中的内容。

认识Class -- 终于不在怂的更多相关文章

  1. 终于等到你:CYQ.Data V5系列 (ORM数据层)最新版本开源了

    前言: 不要问我框架为什么从收费授权转到免费开源,人生没有那么多为什么,这些年我开源的东西并不少,虽然这个是最核心的,看淡了就也没什么了. 群里的网友:太平说: 记得一年前你开源另一个项目的时候我就说 ...

  2. eclipse启动优化,终于不那么卡了!

    eclipse启动优化,终于不那么卡了! 网上找了好多都是myEclipse的优化的,跟eclipse有点区别,找了很多方法还是不能让这个eclipse(Version: Kepler Release ...

  3. C#终于支持可选参数了!

    今天偶然看了一下C#4.0的新特性, 第一个新特性就令我兴奋不已, 曾经一度令我使用C#很不习惯的"死参数"问题终于搞定了.实在太爽了! 过去用C++, VB.NET的时候都很爽, ...

  4. 终于遇到app不兼容,你遇到了么?

    题记: 如果支付宝和QQ不兼容,要二选一,你会怎么选择? 首先了解一下背景: 笔者最近发现,微众银行的app升级到1.7.4, 而患有轻度强迫症的人是迫不及待的点了升级. 第一次,居然安装包安装不成功 ...

  5. 2016最后一贴,终于调通一个测试示例,并发现一个BUG???

    真的难点在于第一次调通.纠结五天,终于搞出界面. 也发现了一个书上代码,编辑用户时死活不通的情况,我将Links去了,改在data里,我X,,全OK了.. 原来的代码: onAdd: function ...

  6. ASP.NET Boilerplate终于发布v1.0了

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:ABP经过2年多的开发,终于发布第一个主要版本了,谨此提醒ABP的使用者. ASP.N ...

  7. 体验了微信小程序,发现安卓用户终于把果粉“碾压”了一次

    今天早上,张小龙在微信公开课上分享了小程序的理念,并且公布了小程序将于1月9日上线. 为了体现张小龙对未来程序形态的理解,小程序有四个特定:无需安装.触手可及.用完即走.无需卸载.今天,36氪刚好有机 ...

  8. 终于开始用github了

    一直以来,github的大名就如雷贯耳.虽然我半年多前就从了解到了这个神奇的网站,而且趁着当时的一时兴趣注册了账户,但是对于那时候的我来说这个网站还是太复杂了点,毕竟半年前的我还没有开始写代码啊,所以 ...

  9. [ASP.NET 5]终于解决:Unable to load DLL 'api-ms-win-core-localization-obsolete-l1-2-0.dll'

    11月12日,惊喜地发现SqlClient(System.Data.SqlClient.dll)跨平台了(对应的nuget包包是runtime.unix.System.Data.SqlClient), ...

随机推荐

  1. Qt串行化的输入和输出(使用QDataStream读写QByteArray,对QIODevice直接起作用)

    参考https://lug.ustc.edu.cn/sites/qtguide/ 今天看了一个介绍Qt串行化的介绍,感觉很受益,就记录了下来. 串行化(Serialization)是计算机科学中的一个 ...

  2. Android应用框架中的四个核心要点

    Android应用框架中的四个核心要点:活动(Activity).消息(Intent).视图(View).任务(Task) (一)活动Activity Android系统内部有专门的Activity堆 ...

  3. Docker容器Centos容器安装openssh

    前面在部署容器,使用docker容器作为jenkins的Slave节点时,会发现在使用centos作为镜像源拉去容器,不能正常连接,最后是因为centos的sshd的问题 下面专门是centos容器安 ...

  4. 一次 kafka 消息堆积问题排查

    收到某业务组的小伙伴发来的反馈,具体问题如下: 项目中某 kafka 消息组消费特别慢,有时候在 kafka-manager 控制台看到有些消费者已被踢出消费组. 从服务端日志看到如下信息: 该消费组 ...

  5. 小小知识点(二十六)关于5G发展的28个核心问题,来自华为内部的深度解读

    本文来自微信公众号“腾讯深网”(ID:qqshenwang),作者 马关夏.36氪经授权转载. 一.5G先进性与行业应用 1.5G到底是什么?和4G比有什么不一样? 从国际电信联盟(ITU)的定义来看 ...

  6. 谁再问elasticsearch集群Red怎么办?把这篇笔记给他

    前言 可能你经历过这些Red. ...等等 那ES的Red是神么意思? 这里说的red,是指es集群的状态,一共有三种,green.red.yellow.具体含义: 冷静分析 从上图可知,集群red是 ...

  7. Vim区块选择

    区块选择的按键意义: 区块选择的按键意义 v 字符选择,光标经过的地方反白 V 列选择,光标经过的列反白 [ctrl]+v 区块选择,可以用长方形的方式选择资料 d 将发白的地方删除掉 y 将反白的地 ...

  8. 欧拉-拉格朗日方程 The Euler-Lagrange Equation

    在 paper: Bounded Biharmonic Weights for Real-Time Deformation 中第一次接触到 Euler-Lagrange 方程,简单记录一下. 泛函的定 ...

  9. Render函数详解

    一.虚拟dom DOM是文档对象模型(Document Object Model)的简写,在浏览器中通过js来操作DOM的操作性能很差,于是虚拟Dom应运而生.虚拟Dom就是在js中模拟DOM对象树来 ...

  10. NameError:name ‘xrange’ is not defined

    原因: 在Python 3中,range()与xrange()合并为range( ). 我的python版本为python3.5. 解决办法: 将xrange( )函数全部换为range( ).