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

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

关于变量的几个叫法:

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

简单来说: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,abstract
this_class:标识当前字节码是哪个类,对应常量池的一个索引(指向具体的类名称和类型)
super_class:是否有父类,用索引对应常量池中记录的类
interfaces_count:当前类有多少个接口
interfaces[interfaces_count-1]:继承了那些接口
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文件中的常量池详解(下)

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

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

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

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

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

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

 

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

类型标志表:

常量项可以划分的种类:

具体怎么存储:

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

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

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

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

特别注意的是:

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原理";  
}  

 

 字节码的表示:

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

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

字节码的表现形式:

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

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

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

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

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

字节码表现:

特别补充一点:对于文本内容的字符编码是可以设置(之前几个是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;  
    }  
....

整个结构:

字节码的结构表示:

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

比如:

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

 

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

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

 

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

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

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

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

 

与字段的区别是:

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

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

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

注意:

方法的描述符(类型)和字段的描述是一样的,但是格式有区别:参数在括号中返回类型在括号后
比如,参数是字符串返回类型是字符串:(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();  
    }    } 

字节码显示:

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

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

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

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

 

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

posted @ 2018-01-20 23:16  假程序猿  阅读(385)  评论(0编辑  收藏  举报