精华内容
下载资源
问答
  • 谈谈java字符串常量池符号引用

    千次阅读 2018-06-25 10:32:42
    字符串常量池是指在JVM运行时会维护一块内存区域,专门存放字符串常量或者说字面量,字符串常量池的内容实在变化的,其中重要的来源之一就是类加载时会有一部分常量进入字符串常量池。JVM的类加载机制:加载 准备 ...

    字符串常量池是指在JVM运行时会维护一块内存区域,专门存放字符串常量或者说字面量,字符串常量池的内容实在变化的,其中重要的来源之一就是类加载时会有一部分常量进入字符串常量池。

    JVM的类加载机制:加载 准备 验证 解析 初始化 使用 卸载,其中解析 即 将符号引用解析为直接引用,因为对一些类和类的字段,方法的引用,在编译时不知道其具体的位置,所以会使用符号引用,在加载时再具体的解析(注意符号引用必须是能具体的找到需要引用的内容)

    下面举例:

    String s1 = "abc"

    String s2 = new String("abc")

    String s3 = new String("abc")

    String s4 = new String("abc").intern()

    问 s1 == s2 ,s1 == s4, s2==s3,s2 == s4的结果

    考虑一下 分别为 false true false false

    究竟是为什么呢

    首先在类编译时,会产生4个符号引用 s1,s2,s3,s4和一个字面量“abc”,这些内容 会在类加载时解析符号引用并把字面量进入到字符串变量池中,

    s1是java栈中的变量,存储的是对字符串常量池中的“abc”的引用即地址,

    s2,s3会先在java堆中创建一个对象,存储对字符串常量池中的“abc”的引用,然后在java栈中的引用变量指向这个对象,

    s4 intern()方法直接返回“abc”常量的引用



    展开全文
  • 运行时常量池,之前放在方法区(永久代),1.8之后被转移到元空间,放到了native memory。 具体的数据结构是:(看对象的内存布局,句柄访问还是对象头保存指向类的元数据的指针,这里以对象头markword之后...

    运行时常量池,之前放在方法区(永久代)中,1.8之后被转移到元空间,放到了native memory中。

    具体的数据结构是:(看对象的内存布局,句柄访问还是对象头中保存指向类的元数据的指针,这里以对象头markword之后保存指向元数据指针为例)对象有一个指向类元数据的指针,指向的这个数据结构InstanceClass,

    InstanceKlass有一个指针指向一个constantPool数据结构(运行时常量池),这个数据结构是一个数组,数组里面元素的排列方式和class文件中是一样的(从1号常量到N号常量),只不过值有变化,7-18里面保存的是数字,根据规则拆开指向的是1-6类型的序号,而1-6类型保存的指向symbol对象的指针,在class文件中,1-6指向的内容是class文件单独的,而在运行时常量池中,指向的symbol对象是可以共享的,能做到这一点是因为在引用symbol对象之前,要经过一个hashTable存取(这个和我们从hashmap中get push的原理差不多),这个hashTable叫symbolTable,存放在元空间,而每个symbol对象存放在堆中。

    在ldc命令(以类型1的utf-8为例)中,如果ldc后面的两个字节指向的符号引用没有被解析过,指向的必然是symbol对象在常量池中的序号,那么需要首先去StringTable(和symbolTable一样的一个hashTable,存放在元空间中,保存着指向堆中的String对象的引用)找equal自己指向的symbol对象的String对象,如果找到,把自己后面两个字节保存这个String对象的引用,然后把这个string对象的引用放入栈顶。如果没找到,从刚才说的symbol对象中(下面的红色字体)解析出String对象(这里要先创建一个char数组对象,棕色字体),并放到字符串常量池(StringTable)中(粉色字体,intern方法),然后两个字节替换成引用,入栈。

    而String.intern()方法是根据一个String对象去StringTable中找,找到了,方法返回里面对象的引用,找不到,放进去,返回引用。所以返回值是不是原String看之前有没有

     

    oop StringTable::intern(Symbol* symbol, TRAPS) {
      if (symbol == NULL) return NULL;
      ResourceMark rm(THREAD);
      int length;
      jchar* chars = symbol->as_unicode(length);  // 从 Symbol 中解析出字符串字面量
      Handle string;
      oop result = intern(string, chars, length, CHECK_NULL); // 调用又一个同名方法
      return result;
    }

     字符串拼接,字节码分析可以见这篇文章:https://www.cnblogs.com/Kidezyq/p/8040338.html

    转载于:https://www.cnblogs.com/chuliang/p/8447698.html

    展开全文
  • 常量池符号解析

    2012-10-10 12:05:29
     在符号解析的过程当中,常量池扮演着非常重要的工作。JVM会在常量池中定义如下信息: 字符型数据:utf-8,包括使用常量定义、方法名称、类名称、属性名称等等,这个类型一般用于定义其他类型所关联的字串信息...
    1.常量池
          在符号解析的过程当中,常量池扮演着非常重要的工作。JVM会在常量池中定义如下信息:
    • 字符型数据:utf-8,包括使用常量定义、方法名称、类名称、属性名称等等,这个类型一般用于定义其他类型所关联的字串信息
    • 数字型常量:long、integer、double、float,包括使用到的一些常量定义
    • String常量:string,包括字串常量定义
    • 类和引用信息:包括Class、MethodRef、InterfaceMethodRef、Fieldref、NameAndType信息

          关于常量池中的信息如何组织,看下面的例子就会明白

    Java代码 复制代码 收藏代码
    1. public class Test   
    2. {   
    3. private String name;   
    4.   
    5. private int age = 21;   
    6.   
    7. public Test(String name, int age)   
    8. {   
    9. super();   
    10. this.name = name;   
    11. this.age = age;   
    12. }   
    13.   
    14. public void sayHello(String name)   
    15. {   
    16. System.out.println("hello, " + name);   
    17. }   
    18.   
    19. public String getName()   
    20. {   
    21. return name;   
    22. }   
    23.   
    24. public int getAge()   
    25. {   
    26. return age;   
    27. }   
    28. }   
    public class Test
    {
    private String name;
    
    private int age = 21;
    
    public Test(String name, int age)
    {
    super();
    this.name = name;
    this.age = age;
    }
    
    public void sayHello(String name)
    {
    System.out.println("hello, " + name);
    }
    
    public String getName()
    {
    return name;
    }
    
    public int getAge()
    {
    return age;
    }
    } 

          这么简单的一个例子,符号信息就已经非常地多了

    const #1 = class #2; // test/Test
    const #2 = Utf-8 test/Test;
    const #3 = class #4; // java/lang/Object
    const #4 = Utf-8 java/lang/Object;
    const #5 = Utf-8 name;
    const #6 = Utf-8 Ljava/lang/String;;
    const #7 = Utf-8 age;
    const #8 = Utf-8 I;
    const #9 = Utf-8 <init>;
    const #10 = Utf-8 (Ljava/lang/String;I)V;
    const #11 = Utf-8 Code;
    const #12 = Method #3.#13; // java/lang/Object."<init>":()V
    const #13 = NameAndType #9:#14;// "<init>":()V
    const #14 = Utf-8 ()V;
    const #15 = Field #1.#16; // Test.name:Ljava/lang/String;
    const #16 = NameAndType #5:#6;// name:Ljava/lang/String;
    const #17 = Field #1.#18; // Test.age:I
    const #18 = NameAndType #7:#8;// age:I
    const #19 = Utf-8 LineNumberTable;
    const #20 = Utf-8 LocalVariableTable;
    const #21 = Utf-8 this;
    const #22 = Utf-8 LTest;;
    const #23 = Utf-8 sayHello;
    const #24 = Utf-8 (Ljava/lang/String;)V;
    const #25 = Field #26.#28; // java/lang/System.out:Ljava/io/PrintS
    tream;
    const #26 = class #27; // java/lang/System
    const #27 = Utf-8 java/lang/System;
    const #28 = NameAndType #29:#30;// out:Ljava/io/PrintStream;
    const #29 = Utf-8 out;
    const #30 = Utf-8 Ljava/io/PrintStream;;
    const #31 = class #32; // java/lang/StringBuilder
    const #32 = Utf-8 java/lang/StringBuilder;
    const #33 = String #34; // hello,
    const #34 = Utf-8 hello, ;
    const #35 = Method #31.#36; // java/lang/StringBuilder."<init>":(Lj
    ava/lang/String;)V
    const #36 = NameAndType #9:#24;// "<init>":(Ljava/lang/String;)V
    const #37 = Method #31.#38; // java/lang/StringBuilder.append:(Ljav
    a/lang/String;)Ljava/lang/StringBuilder;
    const #38 = NameAndType #39:#40;// append:(Ljava/lang/String;)Ljava/lang/String
    Builder;
    const #39 = Utf-8 append;
    const #40 = Utf-8 (Ljava/lang/String;)Ljava/lang/StringBuilder;;
    const #41 = Method #31.#42; // java/lang/StringBuilder.toString:()L
    java/lang/String;
    const #42 = NameAndType #43:#44;// toString:()Ljava/lang/String;
    const #43 = Utf-8 toString;
    const #44 = Utf-8 ()Ljava/lang/String;;
    const #45 = Method #46.#48; // java/io/PrintStream.println:(Ljava/l
    ang/String;)V
    const #46 = class #47; // java/io/PrintStream
    const #47 = Utf-8 java/io/PrintStream;
    const #48 = NameAndType #49:#24;// println:(Ljava/lang/String;)V
    const #49 = Utf-8 println;
    const #50 = Utf-8 getName;
    const #51 = Utf-8 getAge;
    const #52 = Utf-8 ()I;
    const #53 = Utf-8 SourceFile;
    const #54 = Utf-8 Test.java;

           从上面我们可以看出各种类型的大概组织结构

    • Utf-8格式:1个字节的tag标志,表明类型、2个字节的长度信息length,表示字串的长度,length个字节的字串,譬如如上的#2
    • Integer/float格式:1个字节的tag标志,4个字节的内容,对于在class文件中出现的大于2个字节能够表示的整型数值,会在常量池中出现,并使用ldc指令,小于等于1个字节的值,会使用bipush指令,大于1个字节小于2个字节的值,会使用sipush,在后面例子中我们会看到
    • Long/double格式:1个字节的tag标志,8个字节的内容
    • String格式:1个字节的tag标志,2个字节的常量池utf-8常量的偏移量
    • Class格式:1个字节的tag标志,2个字节的常量池utf-8常量的偏移量,见如上的#1,会引用到#2
    • FieldRef/MethodRef(InterfaceMethodRef)格式:1个字节的tag标志,2个字节的常量池class常量偏移量,表明所属的类,2个字节的NameAndType常量偏移量,表明属性名称和类型/方法名称和类型,例如如上的#17/#12
    • NameAndType:1个字节的tag标志,2个字节的utf-8常量偏移量,表明名称,2个字节的utf-8常量偏移量,表明类型信息,例如如上的#16

          常量池定义了所有在字节码执行过程当中我们需要使用到的所有的符号的信息,实际上对于在加载器解析JVM,只需要获得常量池,就可以知道需要去处理哪些符号的解析。符号解析的过程实际就是将常量池中的符号转换成实际入口地址的过程
           2.方法调用
           我们这里重点关注方法调用的指令

    • invokestatic:调用静态方法
    • invokespecial:调用特定的方法,指的是不会根据对象实例的变化而变化的方法,譬如调用父类的方法等
    • invokevirtual:调用虚方法,具体调用的方法与调用的对象有关
    • invokeinterface:与invokevirtual一样,只是方法是在接口中声明的

           我们看下面的例子

    Java代码 复制代码 收藏代码
    1. public class BaseTest   
    2. {   
    3.     public void sayHello2()   
    4.     {   
    5.         System.out.println("sayHello2");   
    6.     }   
    7. }   
    8.   
    9. public class Test extends BaseTest   
    10. {   
    11.     public void sayHello()   
    12.     {   
    13.         super.sayHello2();//使用invokespecial   
    14.         sayHello3();//使用invokestatic   
    15.         sayHello4();//使用invokevirtual   
    16.     }   
    17.   
    18.     public static void sayHello3()   
    19.     {   
    20.         System.out.println("sayHello3");   
    21.     }   
    22.   
    23.     public void sayHello4()   
    24.     {   
    25.         System.out.println("sayHello4");   
    26.     }   
    27. }  
    public class BaseTest
    {
        public void sayHello2()
        {
            System.out.println("sayHello2");
        }
    }
    
    public class Test extends BaseTest
    {
        public void sayHello()
        {
            super.sayHello2();//使用invokespecial
            sayHello3();//使用invokestatic
            sayHello4();//使用invokevirtual
        }
    
        public static void sayHello3()
        {
            System.out.println("sayHello3");
        }
    
        public void sayHello4()
        {
            System.out.println("sayHello4");
        }
    }

         看看sayHello的字节码

    public void sayHello();
    Code:
    Stack=1, Locals=1, Args_size=1
    0: aload_0 //将方法第一参数的地址推入栈顶,方法第一个参数就是this
    1: invokespecial #15; //Method sayHello2:()V,这里字节码由操作码和常量池的
    //偏移量组成,这里使用invokespecial是因为可以确定调用的就是BaseTest的sayHello2方法,另外调用的时候会根据方法需要的参数从栈顶弹出相应的参数
    4: invokestatic #18; //Method sayHello3:()V,static方法不需要this参数
    7: aload_0
    8: invokevirtual #21; //Method sayHello4:()V,这里使用invokevirtual是因为Test有可能被继承,如果Test被继承而且继承类重载了sayHello4方法,调用的则会是继承类的sayHello方法
    11: return

          如上,在性能上,invokestatic和invokespecial在加载期的时候就可以确定要调用的方法的入口地址,因此性能上是最高的,invokevirtual和invokeinterface必须在运行期根据调用的对象,才能确定要调用的方法。
          实际上我们无法获知虚拟机会如何去实现,我们可以选择使用如下的方式实现:
          每个类型会定义一个方法表,按声明的顺序+父声明在前,我们可以得到如下的表,我们可以看到,对于toString方法,无论调用的对象是哪一个,其偏移数量总是7,在进行方法解析的时候,我们只需要知道每个类方法表的入口和该方法偏移量,则可以知道invokevirtual下一步该进入到哪里。但这种方法无法处理接口的问题,实际上接口的顺序是不确定的,所以对于invokeinterface,可能就需要维持一个map<string,int>,来映射方法声明和偏移量的对应关系了。从这一点上看,大概可以猜测invokevirtual性能上会比invokeinterface要高。
          另外,出于性能的考虑,Sun JVM不希望每次都需要去判断引用的常量池是否已经解析成对应的入口指针,因此在解析过之后,会把相应的方法调用置换成对应的quick指令,这些quick指令不需要判断引用的常量池是否已经解析成对应的入口指针。需要注意的是,这些quick指令是Sun JVM在在运行期替换成的指令,编译的class是不会出现这些指令的

    0xd6 invokevirtual_quick
    0xd7 invokenonvirtual_quick
    0xd8 invokesuper_quick
    0xd9 invokestatic_quick
    0xda invokeinteface_quick
    0xdb invokevirtualobject_quick

     

     

    展开全文
  • 从String的不同创建方式谈起 字符串常量池、运行时常量池和class文件常量池理解 符号引用和直接引用
    展开全文
  • 3.常量池保存了各种字面量和对类型、域和方法的符号引用。 4.常量池可以看做是一张表,虚拟机指令根据这张表找到要执行的方法名、类名、参数类型、字面量等类型。 2.为什么需要常量池? 1.一个java源文件的类、...
  • 简介: 这几天在看Java虚拟机方面的知识时,看到了有几种不同...在Java的内存分配,总共3种常量池:1.字符串常量池(String Constant Pool):1.1:字符串常量池在Java内存区域的哪个位置? 在JDK6.0及之前版本,字符串
  • 在JDK1.7 字符串常量池被从方法区拿到了堆, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot的永久代 在JDK1.8 hotspot移除了永久代用元空间(Me....
  • ·每一个栈帧内部都包含一个指向运行时...例如:描述一个方法调用了其他方法时,就是通过常量池中指向方法的符号引用来表示的,所以说动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。 在此我们创建
  • 常量池中有字面量(数量值、字符串值)和符号引用(类符号引用、字段符号引用、方法符号引用),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型 运行时常量池 每个class一份,存在于方法区...
  • JVM - Class常量池 || 运行时常量池

    千次阅读 2020-07-13 00:40:27
    文章目录Preclass常量池字面量符号引用符号引用 Pre JVM - 深入剖析字符串常量池 JVM - 基本类型的包装类和对象池 class常量池 Class常量池我们可以理解为是Class文件的资源仓库。 Class文件主要由两大部分 ...
  • Java常量池分为三种类型: 类文件中常量池(The Constant Pool) 运行时常量池(The Run-Time Constant Pool) ...class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。 常量...
  • 写在前面:博主是一位普普通通的19届二本大学生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的...常量池与Class常量池 2.运.
  • 在Java常量池的概念想必很多人都听说过。这也是面试比较常考的题目之一。在Java有关的面试题,一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的String面试题难倒了无数的开发者...
  • 常量池是当class字节码文件被Java虚拟机加载后存放在方法区各种字面量(Literal)和符号引用 java源码编译成class字节码文件, class字节码文件记录了当前这个类的所有相关信息, 其中有一个很重要的部分被称为常量池 ...
  • 5.Class文件常量池

    2020-12-10 16:35:35
    常量池概述1.1 `constant_pool_count` 常量池计数器1.2 `constant_pool` 常量池表1.2.1 字面量和符号引用1.2.2 1.常量池概述 常量池是Class文件内容最为丰富的区域。常量池对应Class文件字段和方法的解析有着...
  • 在编译阶段,存放的是常量的符号引用。 [在class文件.] 字符串常量池在每个VM只有一份,存放的是字符串常量的引用值。 [在堆.] 运行时常量池是在类加载完成之后,将每个class常量池中的符号引...
  • Class常量池 局部变量的讨论 拓展知识 直接引用和符号引用简述: 举个例子:现在我要在A类引用到B类,符号引用就是我只要知道B类的全类名是什么就可以了,而不用知道B类在内存的那个具体位置(有可能B类还...
  • 字符串常量池(String Poll) java6: 存在于永久代中。 字符串常量池保存的是字符串常量。...用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。 字面量:文本字符串int long 等基本类型...
  • 储存内容:符号引用和字面量。 字符串常量池 诞生时间:编译时 所处区域:堆 储存内容:堆内的字符串对象的引用和字符串常量。 运行时常量池 诞生时间:当类加载到内存后 所处区域:本地内存(每个class都有一个...
  • Class常量池、运行时常量池、字符串常量池

    千次阅读 热门讨论 2021-05-14 19:41:26
    文章目录1、引言2、Class常量池3、运行时常量池3.1 基本类型的包装类3.2 String.intern()4、字符串常量池4.1 直接用双引号...jvm的方法区里存放着类的版本,字段,方法,接口和常量池常量池里存储着字面量和符号引用
  • 常量池

    2021-01-01 21:59:39
    目录Class文件常量池字符串常量池运行时常量池符号引用 直接引用 参考链接 深入了解Java字符串常量池 彻底弄懂java的常量池 Class文件常量池 每一个Java类被编译后,就会形成一份class文件 class文件除了包含类...
  • Java常量池分为三种类型: 类文件中常量池(The Constant Pool) 运行时常量池(The Run-Time Constant Pool) ...class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用...
  • class常量池、字符串常量池和运行时常量池的区别

    千次阅读 多人点赞 2020-04-03 20:42:03
    文章目录一、概念1、Class 常量池(Class Constant Pool)1.1、常量池中数据项类型2、字符串池(String Pool、String Literal Pool)2.1、参考文章:3、运行时常量池(Runtime Constant Pool)4、总结二、方法区的...
  • class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用(class文件:常量池中的14种常量项的结构总表),这部分内容将在类加载后进入方法区的...
  • 编译器生成的各种字面量和符号引用 静态文件 每个类独有 运行时常量池 类加载时 将class常量池的内容加载进来 存储在方法区(底层实现:jdk1.8前永久代,之后元空间,机器的直接内存) 每个类...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,101
精华内容 21,640
关键字:

常量池中的符号引用