国外中文网站域名注册商,网站地图导出怎么做,网站构建培训,简单的html网页设计一、常量池概述 JVM的常量池主要有以下几种#xff1a; class文件常量池运行时常量池字符串常量池基本类型包装类常量池 它们相互之间关系大致如下图所示#xff1a; 每个 class 的字节码文件中都有一个常量池#xff0c;里面是编译后即知的该 class 会用到的字面量与符号引…一、常量池概述
JVM的常量池主要有以下几种 class文件常量池运行时常量池字符串常量池基本类型包装类常量池 它们相互之间关系大致如下图所示 每个 class 的字节码文件中都有一个常量池里面是编译后即知的该 class 会用到的字面量与符号引用这就是 class 文件常量池。JVM加载 class 会将其类信息包括 class 文件常量池置于方法区中。class 类信息及其 class 文件常量池是字节码的二进制流它代表的是一个类的静态存储结构JVM加载类时需要将其转换为方法区中的 java.lang.Class 类的对象实例同时会将 class 文件常量池中的内容导入运行时常量池。运行时常量池中的常量对应的内容只是字面量比如一个字符串它还不是 String 对象当 Java 程序在运行时执行到这个字符串字面量时会去字符串常量池里找该字面量的对象引用是否存在存在则直接返回该引用不存在则在Java堆里创建该字面量对应的 String 对象并将其引用置于字符串常量池中然后返回该引用。Java的基本数据类型中除了两个浮点数类型其他的基本数据类型都在各自内部实现了常量池但都在[-128~127]这个范围内。 二、class文件常量池
java的源代码 .java 文件在编译之后会生成 .class 文件class 文件需要严格遵循 JVM 规范才能被 JVM 正常加载它是一个二进制字节流文件里面包含了class文件常量池的内容。
class文件常量池诞生于编译时存在于class文件中存放符号引用和字面量。 2.1 查看一个class文件内容
jdk 提供了 javap 命令用于对class文件进行反汇编输出类相关信息。该命令用法如下 用法: javap options classes
其中, 可能的选项包括:-help --help -? 输出此用法消息-version 版本信息-v -verbose 输出附加信息-l 输出行号和本地变量表-public 仅显示公共类和成员-protected 显示受保护的/公共类和成员-package 显示程序包/受保护的/公共类和成员 (默认)-p -private 显示所有类和成员-c 对代码进行反汇编-s 输出内部类型签名-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)-constants 显示最终常量-classpath path 指定查找用户类文件的位置-cp path 指定查找用户类文件的位置-bootclasspath path 覆盖引导类文件的位置 例如我们可以编写一个简单的类如下 public class Student {private final String name 张三;private final int entranceAge 18;private String evaluate 优秀;private int scores 95;private Integer level 5;public String getEvaluate() {return evaluate;}public void setEvaluate(String evaluate) {String tmp ;this.evaluate evaluate tmp;}public int getScores() {return scores;}public void setScores(int scores) {final int base 10;System.out.println(base: base);this.scores scores base;}public Integer getLevel() {return level;}public void setLevel(Integer level) {this.level level;}
} 对其进行编译和反汇编 javac Student.java
javap -v Student.class
# over 得到以下反汇编结果: Classfile /home/work/sources/open_projects/lib-zc-crypto/src/test/java/Student.classLast modified 2021-1-4; size 1299 bytesMD5 checksum 06dfdad9da59e2a64d62061637380969Compiled from Student.java
public class Studentminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 Methodref #19.#48 // java/lang/Object.init:()V#2 String #49 // 张三#3 Fieldref #18.#50 // Student.name:Ljava/lang/String;#4 Fieldref #18.#51 // Student.entranceAge:I#5 String #52 // 优秀#6 Fieldref #18.#53 // Student.evaluate:Ljava/lang/String;#7 Fieldref #18.#54 // Student.scores:I#8 Methodref #55.#56 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;#9 Fieldref #18.#57 // Student.level:Ljava/lang/Integer;#10 String #58 // #11 Class #59 // java/lang/StringBuilder#12 Methodref #11.#48 // java/lang/StringBuilder.init:()V#13 Methodref #11.#60 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#14 Methodref #11.#61 // java/lang/StringBuilder.toString:()Ljava/lang/String;#15 Fieldref #62.#63 // java/lang/System.out:Ljava/io/PrintStream;#16 String #64 // base:10#17 Methodref #65.#66 // java/io/PrintStream.println:(Ljava/lang/String;)V#18 Class #67 // Student#19 Class #68 // java/lang/Object#20 Utf8 name#21 Utf8 Ljava/lang/String;#22 Utf8 ConstantValue#23 Utf8 entranceAge#24 Utf8 I#25 Integer 18#26 Utf8 evaluate#27 Utf8 scores#28 Utf8 level#29 Utf8 Ljava/lang/Integer;#30 Utf8 init#31 Utf8 ()V#32 Utf8 Code#33 Utf8 LineNumberTable#34 Utf8 getEvaluate#35 Utf8 ()Ljava/lang/String;#36 Utf8 setEvaluate#37 Utf8 (Ljava/lang/String;)V#38 Utf8 getScores#39 Utf8 ()I#40 Utf8 setScores#41 Utf8 (I)V#42 Utf8 getLevel#43 Utf8 ()Ljava/lang/Integer;#44 Utf8 setLevel#45 Utf8 (Ljava/lang/Integer;)V#46 Utf8 SourceFile#47 Utf8 Student.java#48 NameAndType #30:#31 // init:()V#49 Utf8 张三#50 NameAndType #20:#21 // name:Ljava/lang/String;#51 NameAndType #23:#24 // entranceAge:I#52 Utf8 优秀#53 NameAndType #26:#21 // evaluate:Ljava/lang/String;#54 NameAndType #27:#24 // scores:I#55 Class #69 // java/lang/Integer#56 NameAndType #70:#71 // valueOf:(I)Ljava/lang/Integer;#57 NameAndType #28:#29 // level:Ljava/lang/Integer;#58 Utf8 #59 Utf8 java/lang/StringBuilder#60 NameAndType #72:#73 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#61 NameAndType #74:#35 // toString:()Ljava/lang/String;#62 Class #75 // java/lang/System#63 NameAndType #76:#77 // out:Ljava/io/PrintStream;#64 Utf8 base:10#65 Class #78 // java/io/PrintStream#66 NameAndType #79:#37 // println:(Ljava/lang/String;)V#67 Utf8 Student#68 Utf8 java/lang/Object#69 Utf8 java/lang/Integer#70 Utf8 valueOf#71 Utf8 (I)Ljava/lang/Integer;#72 Utf8 append#73 Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;#74 Utf8 toString#75 Utf8 java/lang/System#76 Utf8 out#77 Utf8 Ljava/io/PrintStream;#78 Utf8 java/io/PrintStream#79 Utf8 println
{方法信息此处省略...
}
SourceFile: Student.java 其中的 Constant pool 就是 class 文件常量池使用 # 加数字标记每个“常量”。 2.2 class文件常量池的内容
class 文件常量池存放的是该 class 编译后即知的在运行时将会用到的各个“常量”。注意这个常量不是编程中所说的 final 修饰的变量而是字面量和符号引用如下图所示 2.2.1 字面量
字面量大约相当于Java代码中的双引号字符串和常量的实际的值包括 1文本字符串
即代码中用双引号包裹的字符串部分的值。
例如刚刚的例子中有三个字符串张三优秀它们在class文件常量池中分别对应
#49 Utf8 张三
#52 Utf8 优秀
#58 Utf8
2用final修饰的成员变量的值
例如private static final int entranceAge 18;
这条语句定义了一个final常量entranceAge它的值是18对应在class文件常量池中就有 #25 Integer 18 注意只有final修饰的成员变量如entranceAge才会在常量池中存在对应的字面量。而非final的成员变量scores以及局部变量base(即使使用final修饰了)它们的字面量都不会在常量池中定义。 2.2.2 符号引用
符号引用包括 1类和接口的全限定名
例如
#11 Class #59 // java/lang/StringBuilder
#59 Utf8 java/lang/StringBuilder 2方法的名称和描述符
例如
#38 Utf8 getScores
#39 Utf8 ()I#40 Utf8 setScores
#41 Utf8 (I)V
以及这种对其他类的方法的引用
#8 Methodref #55.#56 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;#55 Class #69 // java/lang/Integer
#69 Utf8 java/lang/Integer#56 NameAndType #70:#71 // valueOf:(I)Ljava/lang/Integer;
#70 Utf8 valueOf
#71 Utf8 (I)Ljava/lang/Integer; 3字段的名称和描述符
例如
#3 Fieldref #18.#50 // Student.name:Ljava/lang/String;#18 Class #67 // Student
#67 Utf8 Student#50 NameAndType #20:#21 // name:Ljava/lang/String;
#20 Utf8 name
#21 Utf8 Ljava/lang/String;
以及这种局部变量
#64 Utf8 base:10 三、运行时常量池
JVM在加载某个 class 的时候需要完成以下任务
通过该class的全限定名来获取它的二进制字节流即读取其字节码文件。其内容包括在上文class文件常量池中介绍的class文件常量池。将读入的字节流从静态存储结构转换为方法区中的运行时的数据结构。在Java堆中生成该 class 对应的类对象代表该 class 原信息。这个类对象的类型是 java.lang.Class它与普通对象不同的地方在于普通对象一般都是在 new 之后创建的而类对象是在类加载的时候创建的且是单例。
上述过程的第二步就包含了将 class 文件常量池内容导入运行时常量池。class 文件常量池是一个 class 文件对应一个常量池而运行时常量池只有一个多个 class 文件常量池中的相同字符串只会对应运行时常量池中的一个字符串。
运行时常量池除了导入 class 文件常量池的内容还会保存符号引用对应的直接引用(实际内存地址)。这些直接引用是JVM在类加载之后的链接(验证、准备、解析)阶段从符号引用翻译过来的。
此外运行时常量池具有动态性的特征它的内容并不是全部来源与编译后的 class 文件在运行时也可以通过代码生成常量并放入运行时常量池。比如 String.intern() 方法(String.intern()方法的分析见下文)。
要注意的是运行时常量池中保存的“常量”依然是字面量和符号引用。比如字符串这里放的仍然是单纯的文本字符串而不是 String 对象。 四、字符串常量池
字符串常量池是 JVM 用来维护字符串实例的一个引用表。在 HotSpot 虚拟机中它被实现为一个全局的 StringTable 底层是一个 c 的 hashtable 。它将字符串的字面量作为 key 实际堆中创建的 String 对象的引用作为 value 。
字符串常量池在逻辑上属于方法区但 JDK1.7 开始就被挪到了堆区。
String 的字面量被导入 JVM 的运行时常量池时并不会马上试图在字符串常量池加入对应 String 的引用而是等到程序实际运行时要用到这个字面量对应的 String 对象时才会去字符串常量池试图获取或者加入 String 对象的引用。因此它是懒加载的。
如前所述class 文件常量池和运行时常量池中都没有直接存储字面量对应的实际对象比如 String 对象。那么 String 对象到底是什么时候在哪里创建的呢 4.1 创建字符串对象的方式
4.1.1 字面量赋值
直接用双引号字面值的方式创建字符串。
String str1 test;
String str2 test;
//str1 str2 ? true
System.out.println(str1 str2 ? (str1 str2));
当Java虚拟机启动成功后上面的字符串test的字面量已经进入运行时常量池
然后主线程开始运行执行到 String str1 “test; 这条语句时JVM会根据运行时常量池中的这个字面量去字符串常量池寻找其中是否有该字面量对应的 String 对象的引用注意是引用。 如果没找到就会去 Java 堆创建一个值为 “test 的 String 对象并将该对象的引用保存到字符串常量池然后返回该引用如果找到了说明之前已经有其他语句通过相同的字面量赋值创建了该 String 对象直接返回引用即可。
接着执行到 String str2 “test; 这条语句JVM 成功找到上一步生成的 String 对象的引用。所以 str1 str2 返回 true。 4.1.2 new String 创建字符串
使用new String 创建字符串会先在常量池中创建再在堆中创建。
String str3 new String(tony);
String str4 tony;
//str3 str4 ? false
System.out.println(str3 str4 ? (str3 str4)); 第一句中JVM拿字面量 “tony 去字符串常量池试图获取其对应 String 对象的引用因为是首次执行所以没找到于是在堆中创建了一个 “tony 的 String 对象并将其引用保存到字符串常量池中然后返回返回之后因为new的存在JVM又在堆中创建了与 “tony 等值的另一个 String 对象。因此这条语句创建了两个 String 对象它们值相等都是 “tony 但是引用(内存地址)不同所以 str3 str4 结果是false。 4.1.3 String.intern() 方法
String.intern() 是一个 native (本地) 方法用来处理字符串常量池中的字符串对象引用。它的工作流程可以概括为以下两种情况 常量池中已有相同内容的字符串对象如果字符串常量池中已经有一个与调用 intern() 方法的字符串内容相同的 String 对象intern() 方法会直接返回常量池中该对象的引用。常量池中没有相同内容的字符串对象如果字符串常量池中还没有一个与调用 intern() 方法的字符串内容相同的对象intern() 方法会将当前字符串对象的引用添加到字符串常量池中并返回该引用。 总结 intern() 方法的主要作用是确保字符串引用在常量池中的唯一性。当调用 intern() 时如果常量池中已经存在相同内容的字符串则返回常量池中已有对象的引用否则将该字符串添加到常量池并返回其引用。 // 创建一个新的 Java 对象并把该Java 对象的引用添加到字符串常量池s1 指向该引用
String s1 Java;
// s2 也指向字符串常量池中 Java 对象的引用和 s1 是同一个对象
String s2 s1.intern();
// 在堆中创建一个新的 Java 对象s3 指向它
String s3 new String(Java);
// s4 指向字符串常量池中的 Java 对象的引用和 s1 是同一个对象
String s4 s3.intern();
// s1 和 s2 指向的是同一个常量池中的对象
System.out.println(s1 s2); // true
// s3 指向堆中的对象s4 指向字符串常量池中的对象引用所以不同
System.out.println(s3 s4); // false
// s1 和 s4 都指向常量池中的同一个对象
System.out.println(s1 s4); // true 对于第14行 s1 s4 要注意JDK1.6和JDK1.7开始String.intern()的执行逻辑是不一样的。 JDK1.6 以前 intern() 可以查找调用该 intern 的对象是否存在字符串常量池如果存在 返回其引用如果 不存在则添加进去并返回引用JDK1.7及以后 intern() 查找是否存在 如果存在 则返回其引用但是如果字符串常量池不存在堆区一定存在不存在就无法调用该方法)那么就将该堆区对象的引用加入字符串常量池。 因此对于第14行 s1 s4 如果是JDK1.6及以前的版本结果就是false而如果是JDK1.7开始的版本结果就是true。 4.1.4 new String 相加
String str5 new String(good) new String(morning);
str5.intern();
String str6 goodmorning;
//str5 str6 ? true str5 str5.intern() ? true
System.out.println(str5 str6 ? (str5 str6) str5 str5.intern() ? (str5 str5.intern()));
第一句会分别把不在常量池的good和morning两个常量对象加入常量池拼接起来goodmorning不在常量池而goodmorning并不会在常量池中创建常量或引用。str5.intern()检查常量池是否有字符串goodmorning没有把引用添加到常量池中str6检查常量池有字符串goodmorning返回它的引用所以两个判断的结果都是true。
String str5 new String(“good”) new String(“morning”) 被JVM编译优化后后如下
String str5 (new StringBuilder()).append(new String(good)).append(new String(morning)).toString();
底层是一个StringBuilder在进行把两个对象拼接在一起最后栈中str5指向堆中的goodmorning其实是StringBuilder对象 4.2 字符串常量池是否会被GC
字符串常量池本身不会被GC但其中保存的引用所指向的 String 对象们是可以被回收的。否则字符串常量池总是只进不出那么很可能会导致内存泄露。
在 HotSpot 的字符串常量池实现 StringTable 中提供了相应的接口用于支持GC不同的GC策略会在适当的时候调用它们。一般实在Full GC的时候额外调用StringTable的对应接口做可达性分析将不可达的String对象的引用从StringTable中移除掉并销毁其指向的String对象。 五、封装类常量池
除了字符串常量池Java 的基本类型的封装类大部分也都实现了常量池。包括 Byte、Short、Integer、Long、Character、Boolean注意浮点数据类型Float、Double是没有常量池的。
封装类的常量池是在各自内部类中实现的比如 IntegerCache(Integer的内部类)自然也位于堆区。
要注意的是这些常量池是有范围的 Byte , Short , Integer , Long : [-128~127]Character : [0~127]Boolean : [True, False] 例如下面的代码注意其结果
Integer i1 127;
Integer i2 127;
System.out.println(i1 i2); //trueInteger i3 128;
Integer i4 128;
System.out.println(i3 i4); //falseInteger i5 -128;
Integer i6 -128;
System.out.println(i5 i6); //trueInteger i7 -129;
Integer i8 -129;
System.out.println(i7 i8); //false