【JVM内存结构】String在JVM中的深度剖析(一)

面试题:

String a = “ab”; String b = “a” + “b”; a == b 是否相等?

面试考察点
考察目的: 考察对JVM基础知识的理解,涉及到常量池、JVM运行时数据区等。
考察范围: 工作2到5年。

背景知识
要回答这个问题,需要搞明白两个最基本的问题
• String a=“ab”,在JVM中发生了什么?
• String b=“a”+“b”,底层是如何实现?

一、JVM的运行时数据

首先,我们一起来复习一下JVM的运行时数据区。
为了让大家有一个全局的视角,我从类加载,到JVM运行时数据区的整体结构画出来,如下图所示。
对于每一个区域的作用,在我之前的面试系列文章中有详细说明,这里就不做复述了。
在这里插入图片描述

在上图中,我们需要重点关注几个类容:
• 字符串常量池
• 封装类常量池
• 运行时常量池
• JIT编译器
这些内容都和本次面试题有非常大的关联关系,这里对于常量池部分的内容,先保留一个疑问,先跟随我来学习一下JVM中的常量池。

1、JVM中都有哪些常量池

大家经常会听到各种常量池,但是又不知道这些常量池到底存储在哪里,因此会有很多的疑问:JVM中到底有哪些常量池?
JVM中的常量池可以分成以下几类:
• Class文件常量池
• 全局字符串常量池
• 运行时常量池

1.1、Class文件常量池

每个Class文件的字节码中都有一个常量池,里面主要存放编译器生成的各种字面量和符号引用。为了更直观的理解,我们编写下面这个程序。

public class StringExample {  
  private int value = 1;
  public final static int fs=101;  
  public static void main(String[] args)     {        
      String a="ab";        
      String b="a"+"b";        
      String c=a+b;    
    }
 }

上述程序编译后,通过javap -v StringExample.class查看该类的字节码文件,截取部分内容如下。

Constant pool:   
#1 = Methodref          #9.#32         // java/lang/Object."<init>":()V 
#2 = Fieldref           #8.#33         //org/example/cl07/StringExample.value:I
#3 = String            #34            // ab   
#4 = Class             #35            // java/lang/StringBuilder   
#5 = Methodref          #4.#32         //java/lang/StringBuilder."<init>":()V   
#6 = Methodref          #4.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StrvalueingBuilder;   
#7 = Methodref          #4.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;   
#8 = Class              #38            // org/example/cl07/StringExample   
#9 = Class             #39            // java/lang/Object  
#10 = Utf8               value  
#11 = Utf8               I  
#12 = Utf8               fs  
#13 = Utf8  

ConstantValue  
#14 = Integer            101  
#15 = Utf8               <init>  
#16 = Utf8               ()V  
#17 = Utf8               Code  
#18 = Utf8  

LineNumberTable  
#19 = Utf8    

LocalVariableTable  
#20 = Utf8               this  
#21 = Utf8               Lorg/example/cl07/StringExample;  
#22 = Utf8               main  
#23 = Utf8               ([Ljava/lang/String;)V  
#24 = Utf8               args  
#25 = Utf8               [Ljava/lang/String;  
#26 = Utf8               a  
#27 = Utf8               Ljava/lang/String;  
#28 = Utf8               b  
#29 = Utf8               c  
#30 = Utf8               SourceFile  #31 = Utf8               StringExample.java  
#32 = NameAndType       #15:#16        // "<init>":()V  
#33 = NameAndType       #10:#11        // value:I  
#34 = Utf8               ab  
#35 = Utf8               java/lang/StringBuilder  
#36 = NameAndType       #40:#41        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
#37 = NameAndType       #42:#43        // toString:()Ljava/lang/String;  
#38 = Utf8               org/example/cl07/StringExample  
#39 = Utf8               java/lang/Object  
#40 = Utf8               append  
#41 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;  
#42 = Utf8               toString  
#43 = Utf8               ()Ljava/lang/String;

我们关注一下Constant pool描述的部分,表示Class文件的常量池。在该常量池中主要存放两类常量。
• 字面量。
• 符号引用。

1.1.1、字面量

字面量,给基本类型变量赋值的方式就叫做字面量或者字面值。 比如:String a=“b” ,这里“b”就是字符串字面量,同样类推还有整数字面值、浮点类型字面量、字符字面量。
在上述代码中,字面量常量的字节码为:

#3 = String       #34     // ab
#26 = Utf8                a
#34 = Utf8               ab

用final修饰的成员变量、静态变量、实例变量、局部变量,比如:

#11 = Utf8                I  
#12 = Utf8               fs  
#13 = Utf8              ConstantValue  #14 = Integer            101

从上面的字节码来看,字面量和final修饰的属性是保存在常量池中,这些存在于常量池的字面量,指得是数据的值,比如ab,101。
对于基本数据类型,
比如private int value=1,
**在常量池中只保留了他的字段描述符(I)和字段名称(value),**它的字面量不会存在与常量池。

  #10 = Utf8               value  
  #11 = Utf8               I

另外,对于String c=a+b;,c这个属性的值也没有保存到常量池因为在编译期间,a和b的值时不确定的

#29 = Utf8               c
#35 = Utf8     java/lang/StringBuilder
#36 = NameAndType        #40:#41     // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#37 = NameAndType       #42:#43        // toString:()Ljava/lang/String;
#39 = Utf8               java/lang/Object
#40 = Utf8               append
#41 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;

如果,我们把代码修改成下面这种形式

public static void main(String[] args) {  
    final String a="ab";  
    final String b="a"+"b";  
    String c=a+b;}

重新生成字节码之后,可以看到字节码发生了变化,c这个属性的值abab也保存到了常量池中。

#26 = Utf8               c
#27 = Utf8               SourceFile
#28 = Utf8               StringExample.java
#29 = NameAndType        #12:#13        // "<init>":()V
#30 = NameAndType        #7:#8          // value:I
#31 = Utf8               ab
#32 = Utf8               abab
1.1.2、符号引用

符号引用主要设涉及编译原理方面的概念,包括下面三类常量:
1、类和接口的全限定名(Full Qualified Name),也就是Ljava/lang/String;,主要用于在运行时解析得到类的直接引用。

#23 = Utf8               ([Ljava/lang/String;)V  
#25 = Utf8               [Ljava/lang/String;  
#27 = Utf8               Ljava/lang/String;

2、字段的名称和描述符(Descriptor),字段也就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。

#1 = Methodref          #9.#32         // java/lang/Object."<init>":()V
#2 = Fieldref           #8.#33         // org/example/cl07/StringExample.value:I
#3 = String            #34            // ab
#4 = Class             #35            // java/lang/StringBuilder
#5 = Methodref          #4.#32         // java/lang/StringBuilder."<init>":()V
#6 = Methodref          #4.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StrvalueingBuilder;
#7 = Methodref          #4.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;
#8 = Class             #38            // org/example/cl07/StringExample
#24 = Utf8               args
#26 = Utf8               a
#28 = Utf8               b
#29 = Utf8               c

3、方法的名称和描述符,方法的描述类似于JNI动态注册时的“方法签名”,也就是参数类型+返回值类型,比如下面的这种字节码,表示main方法和String返回类型。

 #19 = Utf8               main  
 #20 = Utf8               ([Ljava/lang/String;)V

小结:
在Class文件中,存在着一些不会发生变化的东西,比如一个类的名字、类的字段名字/所属数据类型、方法名称/返回类型/参数名、常量、字面量等。
这些在JVM解释执行程序的时候非常重要,所以编译器将源代码编译成class文件之后,会用一部分字节分类存储这些不变的代码,而这些字节我们就称为常量池。


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值