【JVM】---class常量池、运行时常量池、字符串常量池

java文件经过编译期生成class文件【描述信息和class常量池】,class文件再经过加载到内存【对应相关的运行时常量池和字符串常量池】,供java应用程序使用。

一、基本介绍

在这里插入图片描述
一个class文件包括的信息:
在这里插入图片描述

1、class常量池和运行时常量池

java文件被编译成class文件,class文件除了包含类的版本、字段、方法、接口等信息,还包括class常量池【存放字面量(数字和字符串)和符号引用】。
class文件被加载后,放在方法区,每个类都有一个运行时常量池【对应的数字字面值在方法区的常量池中,字符串的被移到堆的一个共享的字符串常量池】。
类的解析阶段,将符号引用替换成直接引用【和字符串常量池的引用值保持一致。即相当于变量指向了字符串常量池】。

2、字符串常量池

其实字符串常量池是包含在运行时常量池,在jdk 1.8之后,将字符串常量池移到堆中。
在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

3、三者关系

编译期--加载--验证--准备--解析---初始化

编译期产生,类常量池在class文件,一个class文件对应一个类常量池。存储的内容是:符号引用和字面量
类class文件进行加载,准备阶段后,在堆内存中生成字符串对象实例,这个字符串对象实例的引用值存放在字符串常量池中。具体的字符串对象在堆内存另外开辟的区域,字符串常量池是共享的。
类在加载到内存时,每个类常量池中内容存放到**(方法区/元数据区)运行时常量池**,一个类对应一个运行时常量池,在解析阶段,将符号引用替换为直接引用,指向的(堆)字符串常量池

二、实例讲解

1、static变量、final常量所在内存位置

public class MainTest1 {

    static int a;
    static int b = 4;
    static final int c = 4;
    static String s1;
    static String s2 = "abc";
    static final String s3 = "abc";
    static String s4 = "321";
    public static void main(String[] args){

       if(s2.equals(s3))  System.out.println("1");
       if(s2 == s3) System.out.println("2");
       s1 = "abc";
      if(s1 == s2) System.out.println("3");
      if(s1.equals(s2)) System.out.println("4");
     s1 = new String("abc");
     if(s1 == s2) System.out.println("5");
     if(s1.equals(s2)) System.out.println("6");
     s2 = "321";
     if(s2 == s4) System.out.println("7");
    if(s2.equals(s4)) System.out.println("8");
    String s5 = s1.intern();
    if(s5 == s3) System.out.println("9");
    if(s5.equals(s3)) System.out.println("10");
    s1 = "awwq";
	  //输出结果是:1 2 3 4 6 7 8 9 10,没有5
	}
}

相应的分析如下:
编译期后,MainTest1类生成.class文件。
MainTest1类的class常量池拥有a、b、c、s1、s2、s3、s4。其中,4、“abc”、“321” 这些常量值和文本字符串也是在这个类的class常量池中。s1、s2、s4是static变量,可以指向任何String对象实例。
加载后,“abc”、“321” 会在堆产生一块内存空间,字符串常量池有对应的引用值。s1、s2、s4是static变量会指向堆的一个内存地址或字符串常量池的引用值。

        s1 = "abc";
        if(s1 == s2) System.out.println("3");
        if(s1.equals(s2)) System.out.println("4");

这里,"abc"属于字符串常量池,在main方法中,将s1=“abc”,其实是将运行时常量池的s1的指向字符串常量池的"abc"引用值,所以s1和s2不仅指向内存地址和值都是一样。

        s2 = "321";
        if(s2 == s4) System.out.println("7");
        if(s2.equals(s4)) System.out.println("8");

同理,s2也是如此,“321"属于字符串常量池。s2指向字符串常量池的"321”。

        s1 = new String("abc");
        if(s1 == s2) System.out.println("5");
        if(s1.equals(s2)) System.out.println("6");

首先在字符串常量池存储一个(“abc”)的引用值,这个字符串常量池是所有线程共享的,其他的类如果也是 new String(“abc”),那么(“abc”)在字符串常量池是只有一份引用值。然后,是new String(“abc”)操作,这是该main方法在heap中创建一个对象实例,将这个实例地址指向s1【s1指向实例地址,实例地址有变量指向字符串常量池】。

String s5 = s1.intern();
if(s5 == s3) System.out.println("9");
if(s5.equals(s3)) System.out.println("10");

s1.intern()对应字符串是"abc",会去字符串常量池查找是否有,有返回引用,没有的话先添加后再返回引用。所以s5和s3都是指向同一个字符串常量池的应用值,但此时的s1其实还是实例内存地址。

s1 = "awwq";

在编译期时会class常量池有"awwq",加载后在字符串常量池有引用值。

2、Java.lang.String.intern()方法

String的intern()方法会查找在字符串常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

public static void main(String[] args){
    String str1 = "abc";
    String str2 = new String("def");
    String str3 = "abc";
    String str4 = str2.intern();
    String str5 = "def";
    System.out.println(str1 == str3);//true
    System.out.println(str2 == str4);//false
    System.out.println(str4 == str5);//true
}

— "abc"和 "def"是在字符串常量池。所以str1和str3相同。 new String(“def”)是一个实例对象地址, str2.intern()只是将对应def放入字符串常量池且str4指向这个引用值,但str2的实例对象地址并不变。所以str2和str4不相同。str5也是指向字符串常量池的引用值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamBoy_W.W.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值