运行时栈帧结构

栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素。每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程。

每一个栈帧在编译程序代码的时候所需要多大的局部变量表,多深的操作数栈都已经决定了,并且写入到方发表的 Code 属性之中,一次一个栈帧需要多少内存,不会受到程序运行期变量数据的影响,仅仅取决于具体的虚拟机实现。

典型的栈帧主要由 局部变量表(Local Stack Frame)、操作数栈(Operand Stack)、动态链接(Dynamic Linking)、返回地址(Return Address)组成,如下图所示:

    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
     //s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,
String s3 = "Hel" + "lo"; //Hel被包装为对象,lo被包装为对象, //然后调用 StringBuilder.append方法, //然后调用String.toStringui方法, //存入变量s4 String s4 = "Hel" + new String("lo"); //调用new 生成新对象 String s5 = new String("Hello"); String s6 = s5.intern(); String s7 = "H"; String s8 = "ello"; String s9 = s7 + s8; System.out.println(s1 == s2); // true System.out.println(s1 == s3); // true System.out.println(s1 == s4); // false System.out.println(s1 == s9); // false System.out.println(s4 == s5); // false System.out.println(s1 == s6); // true }

其对应指令集如下: 

         0: ldc           #5                  // String Hello
         2: astore_1
         3: ldc           #5                  // String Hello
         5: astore_2
         6: ldc           #5                  // String Hello
         8: astore_3
         9: new           #6                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
        16: ldc           #8                  // String Hel
        18: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: new           #10                 // class java/lang/String
        24: dup
        25: ldc           #11                 // String lo
        27: invokespecial #12                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        30: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        33: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        36: astore        4
        38: new           #10                 // class java/lang/String
        41: dup
        42: ldc           #5                  // String Hello
        44: invokespecial #12                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        47: astore        5
        49: aload         5
        51: invokevirtual #14                 // Method java/lang/String.intern:()Ljava/lang/String;
        54: astore        6
        56: ldc           #15                 // String H
        58: astore        7
        60: ldc           #16                 // String ello
        62: astore        8
        64: new           #6                  // class java/lang/StringBuilder
        67: dup
        68: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
        71: aload         7
        73: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        76: aload         8
        78: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        81: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        84: astore        9

常量池:

Java中的常量池,实际上分为两种形态:静态常量池运行时常量池

     所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。

  这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),

  字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,

 符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

     而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。
 
      常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
 
原文地址:https://www.cnblogs.com/snow-man/p/10476555.html