• 运行时栈帧结构


    栈帧(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()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
     
  • 相关阅读:
    2020总结来了,文末有福利
    CentOS 用户请关注,你期待的 CentOS Linux 9 再也不会来了
    全球最好的10款波尔多葡萄酒,拉菲居然垫底
    eth以太坊合约开发工具集
    html格式的文字去掉html tag转为纯text文字
    如何隐藏X-Powered-By
    aiXcoder3.0呼之欲出,代码补全 AI,带你进入「个性化智能编程时代」
    windows故障群集修改心跳网卡【原创】
    keepalived非争抢机制不生效报错Received lower prio advert, forcing new election
    MySQL主从复制,启动slave时报错1872 Slave failed to initialize relay log info structure from the repository
  • 原文地址:https://www.cnblogs.com/snow-man/p/10476555.html
Copyright © 2020-2023  润新知