整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

「面向 Boss 编程」JVM 链接阶段之符号引用与

「面向 Boss 编程」JVM 链接阶段之符号引用与直接引用

VM 架构简图

JVM 的类加载子系统(Class Load SubSystem) 是 JVM 架构的第一个重要阶段,我们最常见的 JVM 架构简图是下面这样事儿的,只瞅一眼就跟明镜儿似的,类加载子系统的责任那是明明白白的,负责从文件系统或网络中加载 class 文件到 JVM 运行时数据区。


JVM 架构简图

类的加载过程

既然都这么明确了,那只能不绕圈子了,其实类加载子系统还是有点复杂的,并不像上图那样跟一个青春妹子一样单纯、可爱、简单,她的内心你得多少了解点,不然我讲不下去了,想留住单纯美好时光的肥宅们,可以到此打住了。

类加载子系统虽然只负责将 class 文件加载到 JVM 的运行时数据区,其实她也不管最后能不能执行,反正是符合她的“择偶”条件就会加载,虽然单纯可爱简单,但是该有的流程还是要有的。

有好事者总结如下:

  1. 加载(Loading): 在内存中生成一个代表该 class 类的 java.lang.Class 对象
  2. 链接(Linking): 其实这个阶段还分为验证、准备、解析三个阶段
  3. 初始化(Resolution):执行类构造器方法 <clinit>() 完成初始化操作


类加载子系统

还是有点小复杂,但今天不能重点介绍,说多了就没有距离感了,还是先保留一点单纯可爱简单吧。

接下来说今天要探讨的重点:链接(Linking)阶段的解析过程(Resolution)

链接(Linking)阶段

链接(Linking)阶段分为了三个阶段:验证(Verification)、准备(Preparation)、解析(Resolution)。

验证阶段,简单来说,确保 class 文件包含的信息是符合当前虚拟机要求的,确保加载类的正确性,确保其不会危害虚拟机的自身安全;这个阶段主要包括四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证。

著名的魔数【CAFEBABE】(谐音:咖啡宝贝)就是在这个阶段验证的,凡是 JVM 标准的 class 文件都是以这个魔数开始的,名字听着就那么个感觉!

准备阶段,简单来说,就是类变量分配内存并且设置该类的默认初始值,也就是零值;比如定义了一个 static 变量 a=1,在这个阶段会默认赋值 a=0,在后面的初始化阶段会将 a 赋值为 1。

需要注意的是:

1. 这个阶段不包含用 final 修饰的 static 变量,因为 final 会在编译阶段分配

2. 这个阶段不包含实例变量的分配与初始化,因为它们会在堆区创建对象时初始化

好了,终于到整体了,接下来就是解析(Resolution)阶段啦。

解析阶段,简单来说,就是常量池中的符号引用转换为直接引用的过程,解析的主要是类或接口、字段、类方法、接口方法、方法类型等,对应到常量池中的CONSTANT_Class_info、 CONSTANT_Fieldref_info、 CONSTANT_Methodref_info 等。

千呼万唤,终于出来了,接下来就是本篇核心知识点了:符号引用、直接引用了。

符号引用与直接引用

解析阶段的主要操作是将类常量池中的符号引用替换为直接引用。啥?(黑人问号脸)

符号引用,简单说,就是一个字符串,这个字符串定义了被引用项的唯一识别信息,根据这个信息可以无歧义地定位到一个类、字段、方法等。

一句话:就是一个唯一标识符

直接引用,简单说,就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

一句话:就是一个直接内存地址的表示

为什么会有符号引用?

符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中,一个 java 类编译为 class 文件时,java 类并不知道所引用的类的实际地址,因此只能使用符号引用代替。

初始化阶段后,已经可以知道被引用项的真实地址(也就是,直接引用)了,解析阶段会将常量池内的符号引用替换为直接引用。

各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。

Boss:来一个小示例

Boss 说太干了,来点实际的,要想成功就得让我听懂。

有好事者总结了:

(1)我们先写个 java 版本的 Hello World 小程序(PS :这个 Boss 再不懂就辞职啦)

public class Hello {
    private static int a=1;  

    public static void main(String[] args) {
        System.out.println(a);
    }
}       

(2)写完后我们编译一下,有 IDE 的直接自动编译,没有的使用 javac 搞一下

(3)编译成功后可以找到 Hello.class,哎,对就是这个!

(4)然后在 class 文件所在目录打开命令行,输入一个神奇命令:javap -v Hello.class

(5)接下来,见证一下奇迹,找寻一下常量池(Constant Pool)

Boss,请看下图:

这就是编译后常量池中定义的符号引用了,看看 Ljava/lang/String; 就是代表 java.lang.String 类。

常量池的符号引用

符号引用的常见描述符

本着服务 Boss 的宗旨,这么多描述符,鬼知道是怎么回事啊,Boss 教导说,工作要有总结。

那么,好事者又总结了。

JNI 字符描述符是类描述符、方法描述符、字段描述符的具体的实现规则,描述规则如下:

(1)类描述符 : 以 L 开头,其后跟着该类的全限定名,并将其中中的 “.” 改为 “/” ,最后分号“;”结束

 类:Java.lang.String
 对应描述符:Ljava/lang/String;

(2)基本类型描述符:基本类型不存在全限定名,故只需要一个符号就可以表达该基本类型

boolean Z
byte    B
char    C
short   S
int     I
long    J
float   F
double  D
void    V

(3)字段描述符 :格式为 字段名:类型

 int num=3;
 字段描述符: num:I
 字段名: num

(4)数组描述符 : 原描述符前加一个 [ 表示一维数组,加两个[[ 表示二维数组

 String[][] num=null;
 字段描述符:[[Ljava/lang/String;
 字段名:num

(5)方法描述符: (第一个参数的字段描述符第二个参数的字段描述符.....)返回类型的描述符

 # 示例 1
 String test();
 方法描述符:()Ljava/lang/String;
 方法名:test
 
 # 示例 2
 long test(int i, Object c);
 方法描述符:(ILjava/lang/Object;)J
 方法名:test
 
 # 示例 3
 void test(byte[] bytes);
 方法描述符:([B)V
 方法名:test

Boss:来一个小总结

Boss 说得对,来个小总结吧,好事者又开始了表演。

  1. 类加载子系统是 JVM 运行数据的第一个阶段,这个阶段是这种的目标是将 class 文件加载到运行时数据区,创建出对应的 java.lang.Class 对象;
  2. 类加载子系统的加载过程又包括三个大阶段:加载、链接、初始化;
  3. 加载阶段的作用是在内存中生成一个代表这个类的 java.lang.Class 对象
  4. 链接阶段的作用是完成 class 文件的验证、分配内存并赋默认值、最后替换常量池的符号引用,也就是验证、准备、解析三个阶段;
  5. 符号引用就是一个唯一标识被引用项的字符串,直接引用就是一个直接内存地址的表示,可以是指向目标的指针、相对偏移量或一个间接定位目标的句柄;
  6. 之所以会有符号引用,是因为在编译阶段,并不知道变量的真实地址(也就是直接引用),而且每个虚拟机的实现各有不同,因此需要前期定义,后期再进行替换,也就是上面的解析阶段;
  7. 符号引用在 java 虚拟机规范的 Class 文件格式中有明确定义,JNI 字符描述符是类描述符、方法描述符、字段描述符等描述符的具体的实现规则。

参考资料

感谢以下文章或视频提供的学习资料,受益匪浅:

  1. 尚硅谷《宋红康-JVM 从入门到精通》课程 : http://www.atguigu.com/
  2. java -- JVM的符号引用和直接引用 : https://www.cnblogs.com/shinubi/articles/6116993.html
  3. JVM 符号引用(Symbolic References) : https://blog.csdn.net/u014296316/article/details/83066436

写在最后的话

内容为学习相关资料后的总结,一字一码,姑且算是原创吧,欢迎转载,公众号或商业转载请联系本人。

谢谢各位读完本文,初涉写作,内容不佳,敬请见谅,如果对您有帮助,欢迎转发、评论、关注。


网站是使用HTML等制作的用于展示特定内容相关的网页集合
网页是网站中的一“页”,通常是HTML格式的文件,需要通过浏览器来阅读。
网页是构成网站的基本元素,它通常由图片、连接、文字、声音、视频等元素组分。一般以.htm或.HTML为后缀名的文件,都是HTML文件。
什么是html
html是超文本标记语言,它是用来描述网页的一种语言。
1、可以加入图片、声音、动画、多媒体文件等内容。
2、还可以从一盒文件跳转到另一个文件,与世界各地主机的文件连接。
网页是由网页元素组成,这些元素是利用html标签描述出来的,通过浏览器显示给用户。
静/动态区别:
静态:
XX.htm、xx. Html
区分方式:网页中数据的提取来源方式
动态:
Xx.jsp、xx.php、xx.asp、xx.cgi
页面中的数据可以通过和数据库等其他来源提取
动静态交互使用

Html语句的语法的基本单位:

标签、标记

tag<标签单词><标签单词 属性名1=”属性值” 属性名1=”属性值”>

多数标签成对使用,相对少数标签非成对(独立)使用<img>独立使用

<a> ... </a> 制作超级链接

1. 创建文本文件

2. 书写html代码(标签)

3. 保存文件

4. 文件名后缀改为.Html或.html

5. 浏览器打开以上网页文件

标签不能书写中文,只能是英文

代码中“空格”为缩进

嵌套

<html>

<head> </head>

<body> </body>

</html>

<font></font>修饰文字

字形face Font size=“1--7”字体大小 color颜色

<em></em>强调,斜体字

<p>段落文字</p>

<img src=”图片文件名”alt=”xxxxx”width=”像素宽度”height=”像素高度”>

绝对路径:”file:///D:/html/11.jpg”

相对路径:”../src/1.html/11.jpg”

<a>超链接</a>

页间跳转

<a href=”http://www.baidu.com”>文字、图片</a>

<a href=”../src/1.html”>文字、图片</a>

页内跳转(标签需要成对出现)href=作为起点name=作为结尾需要去到的地方

<a Href=”#变量名”>....</a>

..................

<a name=”变量名”>xxx</a>

<a href=”..../#t1”

两者结合使用<a href=”http://www.baidu.com/news/#变量名”>

...............

<a name=”变量名”></a>

“弹幕”滚动标签

标签在不同浏览器当中打开时的内容是不一样的

可用GRB单词进行颜色查找

独立使用标签:<img>< hr>< br换行>在需要换行的地方输入即可

空格在html中作为特殊符号使用:作为分隔符“ ”

Html中汉字不作为标签符

<;&abc>;作为括号使用

字符集不同会出现乱码

Align水平对齐属性;left、right、center

注释 屏蔽代码

Dos批处理 在注释前加上“rem”

Html <!-- 注释的内容...... - ->

扩展<META>元数据标签

标签名称、独立使用、主要作用

放在<head></head>中执行 部分功能在浏览器打开时就开始工作

Charset 字符集

Set 赋值

Set 集合

Th 保存表格数据的单位,等同于<td>。但自带居中加粗显示

Td、th单元格合并:

同行左右单元格合并colspan=”n”

同列上下单元格合并rowspan=”n”

谓操作符,就是用来操作数据值的符号,在JavaScript中包括算术操作符、位操作符、关系操作符和相等操作符。这些操作符可以操作所有类型的数据,比如字符串、数字、布尔值,甚至对象。

一元操作符

所谓一元操作符就是只能对一个数据值进行操作,比如(递增、递减)操作符。

递增、递减操作符是直接借鉴C语言的,它分前置型和后置型。前置就是操作符在要操作的变量之前,后置在变量之后。

如下示例:

// 前置型
let age=20;
++age; // 递增
console.log(age); // 结果输出21
--age;//递减
console.log(age); // 结果输出20

如上面例子,age通过++操作符递增变成21,又通过--操作符递减变成20;上面的操作等同下面的操作:

// 后置型
let age=20;
age=age + 1; // 加1
age=age -1;  // 减1

同理使用后置操作符会得到上面同样的结果,但是前置和后置有区别。前置操作符在语句被求值以前改变,后置是在语句被求值后改变。通过下面的例子看下其区别:

// 前置
let age=20;
let anotherAge=--age + 5;
alert(age); // 输出19
alert(anotherAge); // 输出24

由于前置操作符的优先级和执行语句相等,因此会从左到右依次求值。上面的--age 会先进行递减操作,再继续后面的 + 5 运算,所以结果是24。

// 后置时
let age=20;
let anotherAge=age-- + 5;
alert(age); // 输出19
alert(anotherAge); // 输出25

但是后置的最终结果却是25,因为age-- 使用了递减前的值继续和后面进行+5运算。

如果使用一个加号或减号时,加号代表正值、减号代表负值。

加减乘除操作符

操作多个数据值,比如加减乘除等:

let a=1,b=2;
let c=a + b; // 加
let d=c - a; // 减
let e=d * b; // 乘
let f=e/d; // 除

注意加减乘除主要用来操作数字类型的数据,如果操作数不是数字类型,会先进性强制转换再进行计算,这样结果会不确定。

位操作符

位操作符,是指按内存中的表示的数值位来操作数值,通俗讲就是用来操作二进制的数据。二进制数据都是由0、1组成的,在JavaScript中所有数值都是64位的格式存储,但位操作符不直接在64位的值上进行计算,会先转化成32位后再运算。位操作符有以下几种:

按位非(NOT)

按位非操作符是(~)符号,就是将二进制中每位数值进行反码操作。其规则如下:

操作符

数值

结果

~

1

0

~

0

1

如下示例:

let a=25;
let b=~a;
alert(b); // 输出-26

按位与(AND)

使用(&)符号表示,它有2个操作数,当2个数对应的位都是1时返回1,任何一位是0则返回0。如下规则:

数值1

操作符

数值2

结果

1

&

1

1

1

&

0

0

0

&

1

0

0

&

0

0

示例:

let a=25 & 3;
alert(a); // 输出结果是1

按位或(OR)

用(|)符号表示,同样也是2个操作数。其规则是只要有一位是1其结果就是1,负则结果是0;

数值1

操作符

数值2

结果

1

|

1

1

1

|

0

1

0

|

1

1

0

|

0

0

示例:

let a=25 | 3;
alert(a); // 输出结果是27

按位异或(XOR)

由(^)符号表示,也是操作2个操作数,其当2个操作数的位值相同时返回0,负则返回1。

数值1

操作符

数值2

结果

1

^

1

0

1

^

0

1

0

^

1

1

0

^

0

0

示例:

let a=25 ^ 3;
alert(a); // 输出结果是26

左移

使用(<<)两个小于号表示,这个操作符会将数值每一位向左移动指定位数。如下示例:

let a=2;  // 二进制 10
let b=b << 5; // 二进制的 1000000,十进制64

上面,将二进制10向左移动5位,注意左移会多出5个空位,用0来填充,这样就会得到一个完整的32位二进制数据。

注意,左移不会影响符号位(二进制位中第一位表示数的正负),如-2 向左移5位结果是-64。

有符号的右移

使用(>>)两个大于号表示,会将每位向右移动指定位数,但保留符号位(即正负号标记)。如下示例:

let a=64;  // 二进制 1000000
let b=b >> 5; // 二进制的 10,十进制的2

在移位过程,原数中也会出现空位,只不过这次空位出现在原数值左侧、符号位右侧。空位使用符号位值填充。

有符号的整数,指32位中前31位表示整数的值,第32位表示数值的符号,0正数,1负数。这个表示符号的位就是符号位。

无符号的右移

使用(>>>)三个大于号表示,这个操作会将所有32位都向右移动。对于正数其结果和有符号的右移一样,如下示例:

let a=64;  // 二进制 1000000
let b=b >>> 5; // 二进制的 10,十进制的2

但是负数就不一样了,无符号的右移是以0来填充空位,不像有符号右移使用符号位填充。所以其结果相差很大,如下示例:

let a=-64;  // 二进制 1111 1111 1111 1111 1111 1111 1100 0000
let b=b >>> 5; // 二进制 0000 0111 1111 1111 1111 1111 1111 1110 ,十进制的134217726

布尔操作符

在任何编程语言中,布尔操作符都是非常重要的,它是用来判断逻辑的关键,布尔操作符一共有三种:非(NOT)、与(AND)、或(OR)。

逻辑非

使用(!)感叹号表示逻辑非,其规则就是:

操作符

逻辑值

结果

true

false

false

true

逻辑与

使用(&&)表示,操作两个数,如下示例:

let a=ture && false;

其规则如下:

逻辑值1

操作符

逻辑值2

结果

ture

&&

ture

ture

ture

&&

false

false

false

&&

ture

false

false

&&

false

false

也就是只有当2个数值都是true时其结果才是true。

逻辑或

使用(||)符号表示,也是有两个操作数,其示例:

let a=true || false;

规则如下:

逻辑值1

操作符

逻辑值2

结果

ture

||

ture

ture

ture

||

false

ture

false

||

ture

ture

false

||

false

false

也就是2个操作数中有一个true,结果就是true,负则是false。

注意布尔操作符,不仅仅可以操作布尔类型值,对于其它数据类型同样适用,只不过会先将其它数据类型转换成布尔值,再进行布尔运算。如下示例:

let a=!1; //  输出false
let b=!'string'; // 输出false
let c=1 || 0; // 输出true
let e=1 && 0;// 输出false
let d=''&& 2; // 输出true

关系操作符

关系操作符用来比较2个操作数,有小于(<)、大于(>)、小于等于(<=)和大于等于(>=)。其比较的结果返回一个布尔值,true或false。

如下示例:

let a=5 > 3; // true
let b=5 < 3; // false

同样,关系操作符也可以适用其它类型的数据,比如字符串比较大小时,会按照字符的编码大小比较。如下示例:

let a="Brick" < "alphabet"; // true,

上面中因为B字符编码是66,a的编码是97,所以返回true。

相等、不等操作符

在编程中,确定2个值是否相等是一个非常重要的操作。在JavaScript中分相等(==)和全等(===)、不等(!=)和不全等(!==)四种。

相等(==)和不相等(!=)

如下示例:

let a=1==1; // true
let b=1==0;// false
let c=1!=1; // false
let d=1!=0; //true

注意相等和不相等的操作前会先对操作性进行强制转换,如下示例:

let a=true==1; // 先将true转换成1再比较,结果是true
let b=false==1; // 先将false转换成0再比较,结果是false

全相等(===)和不全相等(!==)

全等和不全等不同之处是,它在比较数据前,不进行数据类型转换,是对原始数值比较,所以它的结果更加严格准确,如下示例:

let a=1===1; // true
let b=1==='1';// false
let c=1!==1; // false
let d=1!=='1'; //true

注意和之前相等和不相等的例子比较,其结果非常不一样。

赋值操作符

使用(=)表示赋值操作,其作用就是把等号右侧的值赋值给左边的变量或属性,如下示例:

let a=10; // 给a变量赋值10

如果在等号前面加上其它操作符,就组成了复合型赋值操作,如下示例:

let a=10;
a +=5; // 结果是 15

上面的等同于下面:

let a=10;
a=a + 5; // 结果是 15

当然也可以使用其它操作符,如(*=)、(/=)、(%=)、(-=)、(<<=)等等。

条件操作符

也称三目运算符,它是一种简便的条件运算,可以把它看成是if else的简化,其语法如下:

变量=布尔表达式 ? true_value  : false_value

先求出问号前面的布尔表示结果,如果是true,变量使用冒号前面的值,负则使用冒号后面的值。如下示例:

let a=5 > 3 ? '好' :  '不好'; // 结果是 '好'

逗号操作符

使用(,)符号,表示可以执行多个操作,常用于变量定义或函数参数,如下示例:

var a=0,b=1,c=2; // a、b、c使用逗号隔开
let a,b,c;

// 函数中的参数a、b、c使用逗号隔开
function test(a,b,c){
	// 函数主体
}
// 调用函数
test(1,2,3)

结论

本节主要讲述了JavaScript中所有的操作符概念,这些都是最基本的知识,需要完全掌握。在平常工作中其中除了位操作符不常用外,其它操作符使用频率很高,尤其是布尔操作符,算术操作符,比较操作符等。

本篇只是大概讲述了操作符的概念和使用方法,还有一些细节没有讲到,作为入门课程已经足够了,你可以自己搜索每个知识点详细内容,比如关于二进制数据、位操作、数据类型强制转换等,这里不再详细介绍。

参考资料:

《JavaScript 高级程序设计》

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

https://www.w3cschool.cn/javascript/js-operators.html