内容简介:* 在之前的《浅析Java序列化和反序列化》一文的Payload构造章节中出现了一大堆的我们先重写一下这个问题的代码:通过断点调试观察变量,
* 在之前的《浅析 Java 序列化和反序列化》一文的Payload构造章节中出现了一大堆的 Class
、 Method
和 Object
,让很多代码基础较弱的同学一脸懵逼。其中一个比较诡异的逻辑 Runtime.class.getClass()
,有朋友问它的结果为什么是 java.lang.Class
。对于这个问题,有Java语言基础的同学一般会回答『对象的类型本来就是 Class
,而 Class
也是对象,它的类型当然也是 Class
』,道理没错,但仔细想想,这还真是一个挺有意思的问题。
关于
Class
的名称
我们先重写一下这个问题的代码:
Class rt = Runtime.class; Class clz = rt.getClass();
通过断点调试观察变量, rt
和 clz
同样都是 Class
对象,但 rt
无论是打印输出还是调用 getTypeName()
得到的都是『java.lang.Runtime』,而 clz
则是『java.lang.Class』。
为什么不一样?难道 Runtime
是 Class
的子类?当然不是, Runtime
可是 Object
的亲儿子。
机智的你一定会跟进 Class
中看看它的 toString()
和 getTypeName()
两个方法的代码逻辑,原来它们都是调用 getName()
返回由这个 Class
所表示的对象的名称。
关于
.class
和
getClass()
由此可知, new Object().getClass()
得到的应该是名称为『java.lang.Object』的 Class
,记作 class java.lang.Object
(以下类似)
,而 Runtime.class
拿到的 Class
作为 Object
的子类,调用 getClass()
得到的却是 class java.lang.Class
。
因此,我们需要对比一下这两种获取 Class
的方法的区别:
-
.class
,又称『类字面量』,只能作用于类的关键字,返回编译时确定的类型Object.class
-
getClass()
,Object
的实例方法,返回运行时确定的类型new Object().getClass()
在一般情况下,它俩的结果是可以相等的:
Object obj = new Object(); Object.class == obj.getClass(); // true Object.class.equals(obj.getClass()); // true
但当存在多态时,后者的区别就体现出来了:
class gyyyy {} Object obj = new Object(); Object gy = new gyyyy(); obj.getClass(); // class java.lang.Object gy.getClass(); // class gyyyy
让我们回到最初的那个问题,答案已经呼之欲出了: Runtime.class
获取的是 class java.lang.Runtime
,而该 Class
调用 getClass()
时,运行时确定的类型为 Class
而非方法拥有者 Object
,所以得到的第二个 Class
为 class java.lang.Class
。
看到这,一定有同学开始骂我又在水文章了:裤子都脱了你就给我看这个?说来说去都是一堆废话,跟没说一样。
别急,我们继续。
JVM基础
既然上面的两种方法分别提到了编译时和运行时,不妨让我们站在JVM的角度再玩深一点。
先科普几个JVM相关的基础知识,让大家有个整体概念,其他的内容如果在后续分析过程中遇到了再穿插介绍。
Classfile
每个类 (包括内部类、匿名类、接口、注解、枚举和数组等) 经过编译后,都会单独生成一个.class文件,里面是一堆用于表示和描述该类的字节码,Java规范中管它叫Classfile。
Classfile中的核心内容如下:
-
常量池 (Constant Pool)
-
访问权限标识 (Access Flags)
-
类 (This Class)
-
父类 (Super Class)
-
接口集合 (Interfaces)
-
字段集合 (Fields)
-
方法集合 (Methods)
-
属性集合 (Attributes)
其中,常量池里存放了该类编译前声明和编译中优化计算的所有值,包括原始类型和引用类型 (符号引用) ,类相关信息都以名称和描述为主,但不涉及任何具体的值或引用 (都依赖常量池索引) 。属性集合中则存放了类、字段和方法所可能需要的属性信息,如类源文件信息、方法代码段、方法代码段的本地变量表等。
运行时内存基本结构
-
运行时数据区
-
线程 (Threads)
-
帧 (Frames)
-
本地变量表 (Local Variables)
-
操作数栈 (Operand Stacks)
-
程序计数器 (Program Counter, PC)
-
JVM堆栈 (JVM Stack)
-
堆 (Heap)
-
类 (Class)
-
运行时常量池 (Run-Time Constant Pool)
-
方法区 (Method Area)
-
对象 (Objects)
-
线程共享
-
线程私有
其中,线程共享部分随JVM启动而创建,线程私有部分随线程创建而创建。Frame中存放的是方法数据而非Class数据,但一般来说,Object和方法的代码实现中都会存放它所属Class的引用。
需要注意的是,上面列出的Class和Object大致分别对应在Java代码中使用 class
或 interface
关键字声明的类和根据它们创建的类实例,而Java语言规范中所描述的 Class
和 Object
严格意义上来说都属于Class。
加载、链接和初始化
-
加载,是指根据指定名称寻找并读取Classfile,将其转换成Class的过程
-
链接,是指解析Class中的符号引用,并转换为运行时状态的过程
-
初始化,是指执行Class的
<cinit>
方法的过程
在这个阶段中,可以为Class创建一个新的 java/lang/Class
的Object,在其中定义一个字段中存放当前Class的引用,并将这个Object的引用放入Class中作为其类对象 (非JVM规范,由实现方自行决定)
,而这个所谓的类对象,就是我们最开始通过 .class
和 getClass()
获取到的那个 Class
对象。
方法执行过程
由于篇幅原因,这里只简单介绍实例方法的执行过程:
-
从常量池中取出方法引用,计算该方法参数个数
-
从操作数栈弹出当前类对象引用和其他参数,组成参数列表
-
为该方法创建新的
Frame
,将参数放入它的本地变量表中,将其压入JVM栈顶 -
解析并执行该方法代码段的指令集
方法的执行结果并不会直接返回给调用方,而是由 return
系列的指令将当前操作数栈顶元素取出,压入JVM栈中调用方所属Frame的操作数栈中。
刨根问底
现在,我们将示例代码放入 main
函数中,这段代码经过编译后会变成以下指令:
ldc #2 astore_1 aload_1 invokevirtual #3 astore_2 ...
( #x
代表常量池索引值,可能会因为示例代码差异而不同。如果使用链式结构 Runtime.class.getClass()
,第2、3条指令会省略)
大致解释一下:
-
ldc
指令会从常量池中取索引为2
的元素,此时取到的是名为java/lang/Runtime
的类引用类型常量,根据JVM规范的描述,如果是类引用类型常量,需要获取它的类对象引用 (在前面加载、链接和初始化部分提到过的那个Object) ,再将其压入操作数栈 (对应Runtime.class
) -
astore_1
指令会弹出操作数栈顶元素,放入本地变量表的1
位置 (0
位置是main
方法参数args
) ,此时该位置的变量名为rt
(对应Class rt =
) -
aload_1
指令会从本地变量表中读取元素压入操作数栈 (对应rt
) -
invokevirtual
指令会从常量池中取索引为3
的元素,此时取到的是名为java/lang/Object.getClass
的方法引用类型常量,再弹出操作数栈顶获得之前ldc
得到的类对象引用作为第一个参数,为该方法创建新的Frame
并压入JVM堆栈,执行该方法的指令集,return
时将结果压入方法调用方的操作数栈 (对应.getClass()
) -
astore_2
指令会弹出栈顶元素,放入本地变量表的2
位置,此时该位置的变量名为clz
(对应Class clz =
)
由此,我们可以明确的知道变量 rt
存放的是 java/lang/Runtime
的类对象引用,变量 clz
存放的是 java/lang/Class
的类对象引用。由于类对象是在Class的链接过程中创建的,而在JVM中每个Class又是唯一的单例,因此同一个类以及它不同的实例获取到的类对象都是同一个。
结论不变。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- jib源码分析之细节
- 深度学习 SSD的理解和细节分析
- 使用wireshark分析ssh口令登录细节
- [经典回顾] S2-045漏洞的一些分析细节
- DeFi平台Lendf.Me被黑细节分析及防御建议
- 万字精美图文,带你掌握 JVM 内存布局及细节分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机程序设计艺术・卷3
[美] 高德纳(Donald E. Knuth) / 贾洪峰 / 人民邮电出版社 / 2017-2 / 198.00元
《计算机程序设计艺术》系列被公认为计算机科学领域的权威之作,深入阐述了程序设计理论,对计算机领域的发展有着极为深远的影响。本书为该系列的第3卷,全面讲述了排序和查找算法。书中扩展了卷1中数据结构的处理方法,并对各种算法的效率进行了大量的分析。一起来看看 《计算机程序设计艺术・卷3》 这本书的介绍吧!
JS 压缩/解压工具
在线压缩/解压 JS 代码
在线进制转换器
各进制数互转换器