JVM四种常量池全方位细致讲解 这一篇就够了~

Posted anditty

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM四种常量池全方位细致讲解 这一篇就够了~相关的知识,希望对你有一定的参考价值。

JAVA四种常量池

Class文件常量池和运行时常量池在方法区,字符串常量池在1.7之后被迁移到了堆区,封装类常量池在堆区(在各自的内部类实现)。

一、Class文件常量池

每个Class文件都会有一个单独的常量池,我们称为Class文件常量池,我们可以用javap命令反汇编Class文件,可以查看java编译器为我们生成的字节码。通过它,我们可以对照源代码和字节码,从而了解很多编译器内部的工作。而在得到的结果中的Constant pool字段就是class文件常量池.

这个CLass文件常量池将会存储以下内容:

  • 字面量:

    字面量是什么? 在计算机科学中, 字面量(literal)是用于表达源代码中一个固定值的表示法(notation).字面量可以理解为实际的值,通过源代码直接给出的值.具体来说,Java支持int,long,float,double,boolean,char,String,null八种类型的字面量

    • 文本字符串(代码中用双引号包裹的字符串部分的值)
    • 被声明为finnal的常量
    • 基本数据类型的值
    • 其他
  • 符号引用

    • 类符号引用:类的完全限定名,一个用于缓存解析后的类结构体指针,指向运行时常量池的指针
    • 字段的名称和描述符
    • 方法的名称和描述符

二、运行时常量池

运行时常量池主要存放两类信息:分别是字面量符号引用.

回顾一下,JVM在加载一个类的时候会做哪些事情:

  • 通过全限定名读取一个类的字节码文件
  • 讲读入的字节流从静态结构转换成为JVM方法区的运行时数据结构
  • 在JVM堆中生成对应的类对象(java.lang.Class)

运行时常量池是方法区的一部分,因此在上述第二部操作中,就涉及了讲Class文件常量池的内容导入运行时常量池的操作.Class文件中含有类的版本,字段,方法,接口等信息,这部分信息将会被存放到运行时常量池中.同时JVM还会把原来Class文件中描述的符号引用转换成直接引用也储存在运行时常量池中.

什么是符号引用?

符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可.在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,多以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。

什么是直接引用?

直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在虚拟机 的内存中存在。

什么是句柄?

JVM中有两种主流的访问对象的方式:直接指针访问和句柄访问

  • 直接指针访问:Java中的引用类型变量储存的将会直接就是堆中对象的地址,而这个对象的地址应该包含一个指向方法区中对象类型的数据,这样的优点是直接就能访问到对象本身,速度更快,HotSpot也是采用这样的方式实现的.
  • 句柄访问:在Java堆中会划分出一个区域储存句柄池,句柄池将会储存对象的地址和对象类型数据的地址,而Java中引用类型变量储存的也将会是句柄池的地址,这样的好处是,当对象发生移动时,只需要改变句柄池中的内容就好了.

运行时常量池和Class文件常量池不同,每个Class文件都对应一个Class常量池,但是运行时常量池只有一份,多个Class文件常量池中相同的字面量只会对应运行时常量池里的一个字面量.

运行时常量池可以在运行时通过程序动态生成一些常量放入其中,例如:String.intern().

三、String常量池

String常量池也是整个JVM只有一个

前文说了,文本字符串会作为字面量放入Class文件常量池和运行时常量池(运行时常量池只有一份这样的数据),那么String常量池存放什么呢?
答案是会存放String类型的对象的引用,并且String常量池底层是用C++的HashMap实现的,key是字符串的字面量,value是字符串对象的引用(HOTSPOT)。

当一个String对象被创建的时候,JVM首先根据运行时常量池里的相关字面量去String常量池里寻找是否有相关字符串对象的引用。如果有那么直接返回该对象的引用即可。如果没有找到,那么会在JAVA堆里创建一个String对象,并且把这个对象的引用保存到String常量池,然后返回这个引用。

几种创建String对象的方式

  • 1.char[] —> new String(chars : char[]);
    以char数组为蓝本创建String对象, 不会查询字符串常量池,不会加入字符串常量池

  • 2.byte[] —> new String(bytes : byte[], charset);
    以byte数组为蓝本创建String对象,不会查询字符串常量池,不会加入字符串常量池

  • 3.String —> new String(str : String)
    以另一个String对象为蓝本,在heap新创建一个String对象。

    	public String(String original) {
    	 // value为char[] 或 byte[] 
    	this.value = original.value; 
    	this.hash = original.hash; 
    	}
    

    如果这里的String是字面量而不是String对象,例如String a=new String("abc");那么,这个“abc”根据字面量创建的规则,也会返回一个String对象的引用,那么也相当于用一个String对象创建另一个String对象
    举个例子:

    	String x="Hello,zzz!";
    	String m=new String(x);
    	String n=new String("Hello,zzz!")
    

    我们得到的结果在堆中大概如下图所示(注意,String对象中其实只保存了这个value数组的引用,真实的value数组存在在heap中):
    在这里插入图片描述

  • 4.组合创建

    • 不含连续两个字面值的组合
      • String str = “qwe” + str1 或者 String str = str1 + str2
        会调用new StringBuilder.append方法
        例如String s = new String("a") + new String("b");会创建5个对象:两个"a",其中一个的 引用被保存在字符串常量池;两个"b",其中一个的引用被保存在字符串常量池;一个"ab",其引用没有被保存在字符串常量池。
    • 连续的字面量组合
      • String str = “str1”+“str2”
        编译器会自动优化,组合这两个字面量变成“str1str2”,之后这个字面量“str1str2”和在堆中创建的String对象的引用会被放到String常量池内。
  • 5.字面量直接创建

    • String a=“abc”;
      那么会去String常量池匹配是否有此字面量,如果有则返回相关String的引用,如果没有则创建后再返回相关的引用。

懒加载

String的字面量被导入JVM的运行时常量池时,并不会马上试图在字符串常量池加入对应String的引用, 而是等到程序实际运行时,要用到这个字面量对应的String对象时,才会去字符串常量池试图获取或者 加入String对象的引用。

String.intern()

判断String的字面量是否在String常量池存在,如果不存在就直接把这个String的引用保存进去。

四、封装类常量池

以下几个封装类Byte,Short,Integer,Long,Character,Boolean在Java中实现了常量池(Double和Float没有)

不同封装类常量池的范围有限:

Byte,Short,Integer,Long : [-128~127]
Character : [0~127]
Boolean : [True, False]

因此会产生以下差异

Integer i1 = 127; 
Integer i2 = 127; 
System.out.println(i1 == i2);

Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);

结果是 True Flase

以上是关于JVM四种常量池全方位细致讲解 这一篇就够了~的主要内容,如果未能解决你的问题,请参考以下文章

关于JVM,看这一篇就够了

Executor线程池只看这一篇就够了

JVM内存模型,你看这一篇就够了

跟着阿里p7一起学java高并发 - 第18天:玩转java线程池,这一篇就够了

Tcpdump 看这一篇就够了

给力!Python配置文件,这一篇就够了!