JVM常量池

图片alt

Class文件常量池

class文件是以字节为单位的二进制数据流,java编译器将java源码文件编译成.class字节码文件存放在磁盘上,.class中就包含文件常量池(非运行时常量池),在编译期间就确定了,.class文件遵循jvm虚拟机规范.

Java源码:

  1. package com.rumenz;
  2. public class rumenz{
  3. public Integer id=10;
  4. public String name="入门";
  5. public final int age=100;
  6. public void setId(Integer id){
  7. this.id=id;
  8. }
  9. }

查看字节码

  1. > javac rumenz.java
  2. > javap -v rumenz
  1. 警告: 二进制文件rumenz包含com.rumenz.rumenz
  2. Classfile /code/rumenz.class
  3. Last modified 2020-10-17; size 542 bytes
  4. MD5 checksum 6a8a73fb6327c1a64e9ad54e53e94afd
  5. Compiled from "rumenz.java"
  6. public class com.rumenz.rumenz
  7. minor version: 0
  8. major version: 52
  9. flags: ACC_PUBLIC, ACC_SUPER
  10. Constant pool:
  11. #1 = Methodref #9.#24 // java/lang/Object."<init>":()V
  12. #2 = Fieldref #8.#25 // com/rumenz/rumenz.id:I
  13. #3 = String #26 // 入门
  14. #4 = Fieldref #8.#27 // com/rumenz/rumenz.name:Ljava/lang/String;
  15. #5 = Methodref #28.#29 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  16. #6 = Fieldref #8.#30 // com/rumenz/rumenz.age:Ljava/lang/Integer;
  17. #7 = Methodref #28.#31 // java/lang/Integer.intValue:()I
  18. #8 = Class #32 // com/rumenz/rumenz
  19. #9 = Class #33 // java/lang/Object
  20. #10 = Utf8 id
  21. #11 = Utf8 I
  22. #12 = Utf8 name
  23. #13 = Utf8 Ljava/lang/String;
  24. #14 = Utf8 age
  25. #15 = Utf8 Ljava/lang/Integer;
  26. #16 = Utf8 <init>
  27. #17 = Utf8 ()V
  28. #18 = Utf8 Code
  29. #19 = Utf8 LineNumberTable
  30. #20 = Utf8 setId
  31. #21 = Utf8 (Ljava/lang/Integer;)V
  32. #22 = Utf8 SourceFile
  33. #23 = Utf8 rumenz.java
  34. #24 = NameAndType #16:#17 // "<init>":()V
  35. #25 = NameAndType #10:#11 // id:I
  36. #26 = Utf8 入门
  37. #27 = NameAndType #12:#13 // name:Ljava/lang/String;
  38. #28 = Class #34 // java/lang/Integer
  39. #29 = NameAndType #35:#36 // valueOf:(I)Ljava/lang/Integer;
  40. #30 = NameAndType #14:#15 // age:Ljava/lang/Integer;
  41. #31 = NameAndType #37:#38 // intValue:()I
  42. #32 = Utf8 com/rumenz/rumenz
  43. #33 = Utf8 java/lang/Object
  44. #34 = Utf8 java/lang/Integer
  45. #35 = Utf8 valueOf
  46. #36 = Utf8 (I)Ljava/lang/Integer;
  47. #37 = Utf8 intValue
  48. #38 = Utf8 ()I
  49. {
  50. public int id;
  51. descriptor: I
  52. flags: ACC_PUBLIC
  53. public java.lang.String name;
  54. descriptor: Ljava/lang/String;
  55. flags: ACC_PUBLIC
  56. public final java.lang.Integer age;
  57. descriptor: Ljava/lang/Integer;
  58. flags: ACC_PUBLIC, ACC_FINAL
  59. public com.rumenz.rumenz();
  60. descriptor: ()V
  61. flags: ACC_PUBLIC
  62. Code:
  63. stack=2, locals=1, args_size=1
  64. 0: aload_0
  65. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  66. 4: aload_0
  67. 5: bipush 10
  68. 7: putfield #2 // Field id:I
  69. 10: aload_0
  70. 11: ldc #3 // String 入门
  71. 13: putfield #4 // Field name:Ljava/lang/String;
  72. 16: aload_0
  73. 17: bipush 100
  74. 19: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  75. 22: putfield #6 // Field age:Ljava/lang/Integer;
  76. 25: return
  77. LineNumberTable:
  78. line 11: 0
  79. line 12: 4
  80. line 13: 10
  81. line 14: 16
  82. public void setId(java.lang.Integer);
  83. descriptor: (Ljava/lang/Integer;)V
  84. flags: ACC_PUBLIC
  85. Code:
  86. stack=3, locals=3, args_size=2
  87. 0: aload_0
  88. 1: iconst_3
  89. 2: aload_1
  90. 3: invokevirtual #7 // Method java/lang/Integer.intValue:()I
  91. 6: iadd
  92. 7: putfield #2 // Field id:I
  93. 10: return
  94. LineNumberTable:
  95. line 17: 0
  96. line 18: 10
  97. }
  98. SourceFile: "rumenz.java"

什么是常量

文本字面量

  1. #29 = Utf8 入门

final修饰成员变量(静态变量,实例变量,局部变量)

对于基本类型public Integer id=10;常量池中只保留了他的字段描述符I和字段名称value,他们的字面量不会存在于常量池。

符号引用

符号引用主要设涉及编译原理方面的概念,包括下面三类常量:

  • 类和接口的全限定名,也就是java/lang/String,将原来的.替换成/,主要用于在运行时解析得到类的直接引用。
  1. #8 = Class #32 // com/rumenz/rumenz
  • 字段的名称和描述符:就是类或者接口中声明的变量,包括类级别的变量和实例级的变量
  1. #14 = Utf8 age
  • 方法中的名称和描述符:参数类型+返回值
  1. #20 = Utf8 setId

运行时常量池

运行时常量池是方法区的一部分,所以也是全局共享的,JVM在执行某个类的时候会经过加载,链接(验证,准备,解析),初始化,在加载的时候需要做:

  • 通过一个类的全类限定名获取此类的二进制字节流。
  • 在堆内存生成一个java.lang.Class对象,代表加载这个类,做为这个类的入口。

普通对象类对象的区别:普通对象是通过new创建出来的。类对象是JVM创建的单例对象。

  • 将字节流的静态存储结构转化成方法区的运行时数据结构

class文件常量池进入运行时常量池,所有类共同使用一个运行时常量池,在进入运行时常量的过程中,多个class常量池中相同的字符串,只会在运行时常量池存在一份,这是一种优化。

运行时常量池的作用是存储class文件常量池中的符号引用,同时运行时常量池保存着class文件中描述的符号引用,在类的解析阶段会把这些符号引用转换成直接引用(实例对象的内存地址),翻译出来的直接引用也是存储在运行时常量池中。class文件常量池的大部分数据会被加载到运行时常量池。

运行时常量池相比于class文件常量池具有动态性,运行时常量池的内容不全部来自于class文件常量池,可以通过代码生成的方式加入到里面。如String.intern。

String.intern()的用法

拿String的内容去Stringtable查找,则返回引用。如果不存在就把该对象的引用存在Stringtable中。

字符串常量池

1.字符串创建的两种方式

  • String rumenz1=”入门”;
  • String rumenz2=new String(“小站”);

入门在编译期间就已经确定,会进入字符串常量池,但是字符串常量池只会保存一个引用,最终还是会在堆上创建一个入门对象。new String这种方式调用了String类的构造函数,new是创建一个对象实例并初始化该实例,因此这个字符串对象是在运行时才能确定的,创建的实例在堆上。

2.字符串常量的本质

字符串常量池是JVM维护的一个字符串实例引用表,在HotSpot VM中它是叫做一个StringTable的全局表。在字符串常量池中维护的是字符串实例的引用,底层C++维护的就是一个Hashtable。这些被维护引用的字符串实例,被称作被驻留字符串interned string进入字符串常量池的字符串

返回笔记列表
入门小站