java 字节码 常量池_class字节码结构(一)(字节码结构和字节常量池的结构)...

关于变量的几个叫法:

局部变量/全局变量:很好区分根据所在位置。

类变量:静态的全局变量。

类常量:全局的final修饰的变量

静态常量:staticfinal 的字段

常量:这个有几种说法:1,final 修饰的变量2,基本类型和字面值(比如变量,方法,类的名字)也被称为常量(一般在字节码)

a84275ef9a91d5c73d0285f76fe2b72b.png

简单来说:class文件结构是魔数、副版本号、主版本之后,常量池等:

magic:魔数,文件类型是字节码的一个数字标志,以便jvm识别。

minor_version:次版本号

major_version:主版本号(每个大的版本之间,jdk和jvm可能是不兼容的,以便jvm提前发现)

constant_pool_count:常量计数器,常量的个数

constant_pool[constant_pool_count-1]:常量,该字节码中字面量和引用类型:基本类型和字符串的值;类,变量,方法,接口,属性的名字和类型

access_flags:表示类和接口访问权限和属性:比如public,final,super,enum,annotation,abstractthis_class:标识当前字节码是哪个类,对应常量池的一个索引(指向具体的类名称和类型)

super_class:是否有父类,用索引对应常量池中记录的类

interfaces_count:当前类有多少个接口

interfaces[interfaces_count-1]:继承了那些接口

fields_count:有多少个字段

fields[fields_count]:具体的字段

methods_count:有多少方法

methods[methods_count]:具体哪些方法

attrributes_count:有多少个属性(属性不是字段)

attributes[attributes_count]:具体属性,(只认识有个内部类的属性,别的都没见过)

具体说下字节码常量池:

e42b678e5d30db41a70321e0b496badb.png

字面量:就是8种基本类型和字符串的值

符号引用:类名的全称也就包括类的包名,字段,方法的名字和类型的名字

注意,以上理解的片面的!不正确的。

字面量:存放的是确切内容的数据,包括了:基本类型(类型和值)和字符串的值,还包括了:类,方法,字段的名字和他们的描述符(类型)的文本表示。

符号引用:存放的是指向具体内容的索引和索引的索引,

包括了:指向字符串值,类名,方法名and类型,字段名and类型,的索引,还包括了指向这些索引的索引项。比如:字段的引用项,方法的引用项。

常量池区域的数据结构:计数器和常量项

24e648f18bba9f0f6eb827bc94ed0ade.png

常量池计数器(constant_pool_count),它记录着常量池的组成元素常量池项(cp_info)的个数。

常量项的结构:一个类型标志和具体内容

2ef5a94ee0fc75aa800deaa810f003d7.png

类型标志表:

44ef3c15f60eddefc6ae6dc125fdc663.png

常量项可以划分的种类:

7c4a923c36a44fb051e4c1a659007e63.png

具体怎么存储:

1,比如常量(常量是带有final的变量):

package com.louis.jvm;public classIntAndFloatTest {private final int a = 10;private final int b = 10;private float c =11f;private float d =11f;private float e =11f;

}

fc93cf71d08db7d76ab7bb70735373e0.png

581e0cc849b6f4edd1480dfe6e264ec4.png

对应的字节码:可以直接看字节码,也可以通过javap -v IntAndFloatTest指令

1d8f923fc553971994a73793fe273984.png

我们发现,对于数值即使定义了多个,常量池也只会存在一个。

特别注意的是:

int类型:

有final标识才会把放到常量池中。并且还会当成字段处理。

有static final 标识只会把值放到常量池中,不会当作字段处理

没有final修饰,只会当作字段处理。(即使字段赋值了,值也是不会放到常量池)

float类型:

却不需要final标识值也会放到常量池中。并且当作字段处理。

字段(字段的处理后面有具体介绍)和常量的区别:

字段:

只要没有 static和final同时修饰的变量都当字段处理。

常量:

被final修饰的变量。如果只是final修饰,那么还会当作字段处理。

被final和static同时修饰的变量才会只当作常量处理。

什么是字段处理和常量处理?

常量处理就是:变量和变量值,分别作为常量项存放。

字段处理就是:不管有没有值,都是不存放在常量池中。一个字段包括了多个常量项:名字项,类型项,名字和类型的索引项,字段引用项(索引的索引项)

例子:

public classTestClass{int a=11;int aa=11;final int b=22;static final int c=33;float f=44f;

String e="字符串变量";

String ee="字符串变量";final String eee="常量字符串变量";public static void main(String[] arg){

字节码如下:

Constant pool:

#1 = Methodref #13.#40 //java/lang/Object."":()V

#2 = Fieldref #12.#41 //TestClass.a:I

#3 = Fieldref #12.#42 //TestClass.aa:I

#4 = Fieldref #12.#43 //TestClass.b:I

#5 = Float 44.0f#6 = Fieldref #12.#44 //TestClass.f:F

#7 = String #45 //字符串变量

#8 = Fieldref #12.#46 //TestClass.e:Ljava/lang/String;

#9 = Fieldref #12.#47 //TestClass.ee:Ljava/lang/String;

#10 = String #48 //常量字符串变量

#11 = Fieldref #12.#49 //TestClass.eee:Ljava/lang/String;

#12 = Class #50 //TestClass

#13 = Class #51 //java/lang/Object

#14 =Utf8 a

#15 =Utf8 I

#16 =Utf8 aa

#17 =Utf8 b

#18 =Utf8 ConstantValue

#19 = Integer 22#20 =Utf8 c

#21 = Integer 33#22 =Utf8 f

#23 =Utf8 F

#24 =Utf8 e

#25 = Utf8 Ljava/lang/String;

#26 =Utf8 ee

#27 =Utf8 eee

#28 =Utf8 eeee

#29 = String #52 //静态常量字符串变量

#30 = Utf8 #31 =Utf8 ()V

#32 =Utf8 Code

#33 =Utf8 LineNumberTable

#34 =Utf8 main

#35 = Utf8 ([Ljava/lang/String;)V

#36 =Utf8 getA

#37 =Utf8 ()I

#38 =Utf8 SourceFile

#39 =Utf8 TestClass.java

#40 = NameAndType #30:#31 //"":()V

#41 = NameAndType #14:#15 //a:I

#42 = NameAndType #16:#15 //aa:I

#43 = NameAndType #17:#15 //b:I

#44 = NameAndType #22:#23 //f:F

#45 =Utf8 字符串变量

#46 = NameAndType #24:#25 //e:Ljava/lang/String;

#47 = NameAndType #26:#25 //ee:Ljava/lang/String;

#48 =Utf8 常量字符串变量

#49 = NameAndType #27:#25 //eee:Ljava/lang/String;

#50 =Utf8 TestClass

#51 = Utf8 java/lang/Object

#52 =Utf8 静态常量字符串变量

{

2,字符串类型:

虽然多个字符串也只会存储一份,但是存储结构不同,会分成2个常量项来存:1,字符串类型标志和内容的索引;2,文本类型标志和文本内容。

字符也和常量一样分为3种情况(见上面字节码效果):没有final,有final,和final和static

package com.louis.jvm;public classStringTest {private String s1 = "JVM原理";private String s2 = "JVM原理";private String s3 = "JVM原理";private String s4 = "JVM原理";

}

5004d641eb98db80c144e3e38e361699.png

606e7fbc80653f90f8ddad9ed1212886.png

0280c6278027ba4f4dec1d2c3b0cb47f.png

字节码的表示:

2fb6bb5ca80fb55486667a0b402a3f8a.png

3,本类和使用到的类型是如何存储:和字符串类似,也许需要两个常量项,只是类型标志不同。

package com.jvm;

import java.util.Date;public classClassTest {private Date date =newDate();

}

60c775216bf733a494deea5bfbdeb664.png

字节码的表现形式:

f329448e358c8ab08fa3f26874fcab44.png

补充一点就是:Object类型会被自动添加,因为所有的类是其子类。

3.1注意:类型只是被声明不会记录到常量池

public classOther{privateDate date;publicOther()

{

Date da; }

Date类型是不会被记录的,需要改成这样才会:

public classOther{publicOther()

{newDate();

}

字节码表现:

3893254da65bf4ffb598811282c98410.png

特别补充一点:对于文本内容的字符编码是可以设置(之前几个是ask码这个utf8)

4,对字段的存储:需要至少4个常量项(因为有对字段所在的类的指向,所在类的存储需要2个常量项)

CONSTANT_UTF8_info:字段的名字常量项;

CONSTANT_UTF8_info:字段的类型常量项(术语,不叫类型叫字段描述符!!!),

CONSTANT_Name_Type_info(名字和类型的索引):指向名字和类型的索引的常量项;

CONSTANT_Fieldref_info(字段的引用也是索引):指向所在类和【名字和类的索引常量项】的索引的常量项

例子:

package com.louis.jvm;public classPerson {privateString name;private intage;publicString getName() {returnname;

}public voidsetName(String name) {this.name =name;

}

....

516ffc5cde02ca9bfa375e9e85dc6979.png

c2245f4661bad76d17bb7aca61026415.png

整个结构:

52d453ff53ac70e90a4ff43232bdf6c5.png

字节码的结构表示:

17df5dae5f8c567b59f9575ce2deed7d.png

4.1字段还有一个特别之处是,字段的类型(也就是字段描述符)的表示形式有自己的规则:

0f66ff523403df21c1794afe10eeacbd.png

比如:

基本类型是首字母大写除了long和boolean.

long是:J;boolean是:Z

引用类型要加"L"(字符串类型是:Ljava/lang/String);

数组类型要加“["(字符串数组是:[Ljava/lang/String);

4.2类中定义了field 字段,在类中的其他地方没用到这些字段,它是不会被编译器放到常量池中的。

比如:私有并且没有方法使用的情况

个人理解常量池对常量和字段的划分是:

字段是没有final修饰的。

常量是有final修饰的,

补充:少了字段的权限(权限不在常量池中)和字段的值(字段值也是不在常量池中)

5,方法的存储:和字段类似,并且方法还要被其他方法使用才会加入到常量池,区别是方法的描述符有多个(包括了参数类型和返回类型)

cff9f806b68fa09b51208bafb1cf7fe1.png

与字段的区别是:

1,方法引用的索引代替了字段引用的索引常量项。2,方法描述符,多个是在一起的格式如:(Lxxx/String,Lxxx/String)Lxxx/String

5.1方法描述符的组成(方法的返回和参数类型):

5a9a4b181471a41cb2abb6467207c1fa.png

5.2,字节码中的显示效果:

c45426407f0960a3a59f6ed01d0d94f4.png

注意:

方法的描述符(类型)和字段的描述是一样的,但是格式有区别:参数在括号中返回类型在括号后

比如,参数是字符串返回类型是字符串:(Ljava/lang/string)Ljava/lang/string

如果没有参数和返回类型(用“v”表示void):()v

6,如何存类中使用到的接口中的方法,和类方法类似,只有一个常量项有区别:

接口方法引用(CONSTANT_InterfaceMethodref_info)替换了方法引用项

package com.louis.jvm;public interfaceWorker{public voidwork();

}public classBoss {public voidmakeMoney(Worker worker)

{

worker.work();

} }

字节码显示:

30ea4d335dca18bdc271437b8fe0dc5f.png

7,java7中的常量项:CONSTANT_MethodType_info,CONSTANT_MethodHandle_info,CONSTANT_InvokeDynamic_info

这三项主要是为了让Java语言支持动态语言特性

总结:常量池的意义就是把那些经常使用到放到常量池。主要就是文本和值还有引用项(对应文本和值的索引)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值