深入理解 Class--- 常量池
一, 概念
1,jvm 生命周期
启动: 当启动一个 java 程序时, 一个 jvm 实例就诞生了, 任何一个拥有 main 方法的 class 都可以作为 jvm 实例运行的起点.
运行: main()函数作为程序初始线程起点, 其它线程由该线程启动, 包括守护线程 (daemon) 和 non-daemon(普通线程). 守护线程是 JVM 自己使用的线程比如 GC 线程就是个守护线程, 只要这个 jvm 实例还有普通线程执行, 就不会停止, 但是可以用 exit()强制终止程序.
消亡: 所有非守护线程退出时, JVM 实例结束生命, 若安全管理器允许, 程序也可以使用 java.lang.Runtime 类或者 System.exit(0)来退出. 实际上 exit 也是用到 Runtime 类来退出, Runtime 是个神奇的类, 它还可以用于启动和关闭非 java 进程.
2,JVM 与 Class 文件
我们一直说 java 虚拟机实现的与语言是无关的, java 虚拟机不和包含 java 在内的任何语言绑定, 它只和与 class 文件这种特殊的二进制文件格式所关联, class 文件中包含了 java 虚拟机指令集和符号表以及若干其他辅助信息. 基于安全方面的考虑, Java 虑拟机规范要求在
Class 文件中使用许多强制性的语法和结构化约束
, 但任一门功能性语言都可以表示为一个能被 Java 虚拟机所接受的有效的 Class 文件. 作为一个通用的, 机器无关的执行平台, 任何其他语言的实现者都可以将 Java 虚拟机作为语言的产品交付媒介. 例如, 使用 Java 编译器可以把 Java 代码编译为存储字节码的 Class 文件, 使用 JRuby 等其他语言的编译器同样可以把程序代码编译成 Class 文件, 虚拟机并不关心 Class 的来源是何种语言, 如图.
3, 什么是 Class 文件
Java 字节码类文件 (.class) 是 Java 编译器编译 Java 源文件 (.java) 产生的 "目标文件". 它是一种 8 位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得 class 文件非常紧凑, 体积轻巧, 可以被 JVM 快速的加载至内存, 并且占据较少的内存空间(方便于网络的传输).
class 文件是一组以 8 位字节为基础单位的二进制流.
class 文件中的信息是一项一项排列的, 每项数据都有它的固定长度, 有的占一个字节, 有的占两个字节, 还有的占四个字节或 8 个字节, 数据项的不同长度分别用 u1, u2, u4, u8 表示, 分别表示一种数据项在 class 文件中占据一个字节, 两个字节, 4 个字节和 8 个字节.
4, 什么是魔数
当我们把 class 文件转成 16 进制, 我们可以看到文件的头四个字节是 cafe babe, 这个就称为魔数., 它唯一作用就告诉虚拟机当前的文件就是 class 文件.
使用魔数而不是用扩展名来进行识别主要是基于安全考虑, 因为扩展名我们可以随意通过重命名等方式改动. 而通过魔数就算你把结尾改成. clss. 但它同样还能在 JVM 运行, 因为它的头部还是 cafe babe 没变.
很多文件存储标准中都用魔数进行身份标识, 如图片 gif,jpeg 都在文件头部中存储着魔数.
5,jvm 常量池
我先讲下概念, 接下来我会将 class 文件转为 16 进制流后, 在举例说明.
常量池中每一项常量都是一个表, jdk1.8 有 14 种结构不同的表结构, 这 14 个表有个共同特点, 就是表开始的第一位都是一个 u1 类型的标志位, JVM 根据这个标志位 [tag] 来确定某个常量池项表示什么类型的字面量, 比如 tag 为 1 就是指 CONSTANT_utf8_info
再看常量池类型表:
这 14 种常量项结构还有一个特点是, 其中 13 表占用得字节固定, 只有 CONSTANT_Utf8_info 占用字节不固定, 其大小由 length 决定. 为什么呢? 因为从常量池存放的内容可知, 其存放的是字面量和符号引用, 最终这些内容都会是一个字符串,
这些字符串的大小是在编写程序时才确定, 比如你定义一个类, 类名可以取长取短, 所以在没编译前, 无法确定大小不固定, 编译后, 通过 utf-8 编码, 就可以知道其长度.
在看每一项常量表对应的说明:
二, 16 进制 class 文件解析
先看 java 代码
- package com.jincou.demo.domain;
- public class XiaoXiao {
- private String father;
- public String fatherName() {
- return "小小她爹";
- }
- }
通过命令自动生成 class 文件(会在同一目录生成)
javac XiaoXiao.java
在将 class 文件拖入文本编辑器里, 显示自然就是 16 进制流了, 如下:
上面的表其实可以划分为以下七个部分,.class 字节码文件包括:
魔数与 class 文件版本
常量池
访问标志
类索引, 父类索引, 接口索引
字段表集合
方法表集合
属性表集合
这篇博客只讲到常量池, 其它的下篇讲, 接下来我们一行一行解释, 首先是:
cafe babe: 上面说过了这个是魔数, 告诉 JVM 虚拟机我就是 class 文件.
0000 0034: 次版本号组成 u2 + 主版本号 u2. 共占 4 个字节. 0034 转 10 进制为 52, 代表当前 JDK 版本为 1.8.
0013 : 说明有 19-1 即 18 个常量.
上面这些位置是固定的. 接下来就是说明每一个常量:
0a: 这就是 tag 代表一个标志, 0a 代表 10, 去找常量池列表.
得知它是一个接口中方法的符号引用, 然后去找 CONSTANT_Methodref_info 对应常量列表描述:
从常量列表我们可以知道该类型一共占了 5u, 即 0a00 0400 0f, 那么下一个 tag 就是 08 代表字符串类型常量, 以此类推就可以知道一共 18 个常量的信息.
三, class 反编译
通过上面看 16 进制的却太麻烦了, 现在我们可以通过 JDK 自带反编译工具查看会更加清晰.
javap -verbose 文件名
通过反编译看去就很直观, 比如第一个字符常量很明显告诉你是 CONSTANT_Methodref_info, 而且对于的就是 4 和 15 和上面完美对应.
最后思考, 到底哪些会放到常量池?
1. 常量池可以理解为 class 文件中的资源仓库, 有很多种类型, 主要存放两大常量
1. 字面量
字面量就是通俗理解的 java 常量, 如文本字符串, 8 大基本数据类型, final 修饰的常量值等
2. 符号引用
符号引用属于编译原理的概念, 主要包含以下三种
1)类和接口的全限定名
2)字段的名称和描述符
3)方法的名称和描述符
参考
1, 深入了解 java 虚拟机第 2 版第六章
2, 深入理解 JVM-Class 文件结构和类加载 https://www.jianshu.com/p/88a34f2959e0
3, 深入理解 JVM 之 Java 字节码 (.class) 文件详解 https://www.jianshu.com/p/247e2475fc3a
只要自己变优秀了, 其他的事情才会跟着好起来(少将 3)
posted on 2019-04-03 00:49 雨点的名字 阅读(...) 评论(...) 编辑 收藏
来源: https://www.cnblogs.com/qdhxhz/p/10646088.html