class字节码结构(一)(字节码结构和字节常量池的结构)

时间:2023-01-22 09:19:08

《Java虚拟机原理图解》 1.1、class文件基本组织结构

关于变量的几个叫法:

局部变量/全局变量:很好区分根据所在位置。
类变量:静态的全局变量。
类常量:全局的final修饰的变量
静态常量:static final 的字段
常量:这个有几种说法:
,final 修饰的变量
,基本类型和字面值(比如变量,方法,类的名字)也被称为常量(一般在字节码)

class字节码结构(一)(字节码结构和字节常量池的结构)

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

magic:魔数,文件类型是字节码的一个数字标志,以便jvm识别。
minor_version:次版本号
major_version:主版本号(每个大的版本之间,jdk和jvm可能是不兼容的,以便jvm提前发现)
constant_pool_count:常量计数器,常量的个数
constant_pool[constant_pool_count-]:常量,该字节码中字面量和引用类型:基本类型和字符串的值;类,变量,方法,接口,属性的名字和类型
access_flags:表示类和接口访问权限和属性:比如public,final,super,enum,annotation,abstract
this_class:标识当前字节码是哪个类,对应常量池的一个索引(指向具体的类名称和类型)
super_class:是否有父类,用索引对应常量池中记录的类
interfaces_count:当前类有多少个接口
interfaces[interfaces_count-]:继承了那些接口
fields_count:有多少个字段
fields[fields_count]:具体的字段
methods_count:有多少方法
methods[methods_count]:具体哪些方法
attrributes_count:有多少个属性(属性不是字段)
attributes[attributes_count]:具体属性,(只认识有个内部类的属性,别的都没见过)

具体说下字节码常量池:

《Java虚拟机原理图解》 1.2.2、Class文件中的常量池详解(上)

《Java虚拟机原理图解》 1.2.3、Class文件中的常量池详解(下)

class字节码结构(一)(字节码结构和字节常量池的结构)

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

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

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

字面量:存放的是确切内容的数据,包括了:基本类型(类型和值)和字符串的值,还包括了:类,方法,字段的名字和他们的描述符(类型)的文本表示。
符号引用:存放的是指向具体内容的索引和索引的索引,
包括了:指向字符串值,类名,方法名and类型,字段名and类型,的索引,还包括了指向这些索引的索引项。比如:字段的引用项,方法的引用项。

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

class字节码结构(一)(字节码结构和字节常量池的结构)

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

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

class字节码结构(一)(字节码结构和字节常量池的结构)

类型标志表:

class字节码结构(一)(字节码结构和字节常量池的结构)

常量项可以划分的种类:

class字节码结构(一)(字节码结构和字节常量池的结构)

具体怎么存储:

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

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

class字节码结构(一)(字节码结构和字节常量池的结构)

class字节码结构(一)(字节码结构和字节常量池的结构)

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

class字节码结构(一)(字节码结构和字节常量池的结构)

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

特别注意的是:

int类型:
有final标识才会把放到常量池中。并且还会当成字段处理。
有static final 标识只会把值放到常量池中,不会当作字段处理
没有final修饰,只会当作字段处理。(即使字段赋值了,值也是不会放到常量池) float类型:
却不需要final标识值也会放到常量池中。并且当作字段处理。

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

字段:
只要没有 static和final同时修饰的变量都当字段处理。
常量:
被final修饰的变量。如果只是final修饰,那么还会当作字段处理。
被final和static同时修饰的变量才会只当作常量处理。 什么是字段处理和常量处理?
常量处理就是:变量和变量值,分别作为常量项存放。
字段处理就是:不管有没有值,都是不存放在常量池中。一个字段包括了多个常量项:名字项,类型项,名字和类型的索引项,字段引用项(索引的索引项)

例子:

public class TestClass{
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."<init>":()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 <init>
#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 // "<init>":()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 class StringTest {
private String s1 = "JVM原理";
private String s2 = "JVM原理";
private String s3 = "JVM原理";
private String s4 = "JVM原理";
}

class字节码结构(一)(字节码结构和字节常量池的结构)class字节码结构(一)(字节码结构和字节常量池的结构)

class字节码结构(一)(字节码结构和字节常量池的结构)

字节码的表示:

class字节码结构(一)(字节码结构和字节常量池的结构)

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

package com.jvm;
import java.util.Date;
public class ClassTest {
private Date date =new Date();
}

class字节码结构(一)(字节码结构和字节常量池的结构)

字节码的表现形式:

class字节码结构(一)(字节码结构和字节常量池的结构)

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

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

public  class Other{
private Date date;
public Other()
{
Date da; }

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

public  class Other{
public Other()
{
new Date();
}

字节码表现:

class字节码结构(一)(字节码结构和字节常量池的结构)

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

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

CONSTANT_UTF8_info:字段的名字常量项;

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

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

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

例子:

package com.louis.jvm;
public class Person {
private String name;
private int age; public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
....

class字节码结构(一)(字节码结构和字节常量池的结构)

class字节码结构(一)(字节码结构和字节常量池的结构)

整个结构:

class字节码结构(一)(字节码结构和字节常量池的结构)

字节码的结构表示:

class字节码结构(一)(字节码结构和字节常量池的结构)

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

class字节码结构(一)(字节码结构和字节常量池的结构)

比如:

基本类型是首字母大写除了long和boolean.
long是:J;boolean是:Z
引用类型要加"L"(字符串类型是:Ljava/lang/String);
数组类型要加“["(字符串数组是:[Ljava/lang/String);

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

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

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

字段是没有final修饰的。
常量是有final修饰的,

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

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

class字节码结构(一)(字节码结构和字节常量池的结构)

与字段的区别是:

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

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

class字节码结构(一)(字节码结构和字节常量池的结构)

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

class字节码结构(一)(字节码结构和字节常量池的结构)

注意:

方法的描述符(类型)和字段的描述是一样的,但是格式有区别:参数在括号中返回类型在括号后
比如,参数是字符串返回类型是字符串:(Ljava/lang/string)Ljava/lang/string
如果没有参数和返回类型(用“v”表示void):()v

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

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

package com.louis.jvm;
public interface Worker{
public void work(); }
public class Boss {
public void makeMoney(Worker worker)
{
worker.work();
} }

字节码显示:

class字节码结构(一)(字节码结构和字节常量池的结构)

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

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

探秘Java 7:JVM动态语言支持详解

执行篇:解析JDK 7的动态类型语言支持

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