「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」
一、静态常量池和运行时常量池
Class常量池可以理解为是Class文件的资料库。常量池用于存放编译期生成的各种 “字面量” 和 “符号引用”
类文件中除了包含类的版本、字段、方法、接口等描述信息,还包含的就是常量池。如下图所示:
我们可以通过javap命令生成更可读的JVM字节码指令文 件:
1. 字面量
字面量指由字母、数字等构成的字符串或者数字常量。
字面量只可以右值出现,所谓的右值就是指等号右边的值,比如:int a=1 这里的a为左值,1为右值,在这个例子中1就是字面量
int a = 1;
int b = 2;
String c = "abcdefg"
package com.lxl.jvm;
public class Math {
public static int initData = 666;
public static User user = new User();
public User user1;
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
System.out.println("aaa");
Math math = new Math();
while(true){
math.compute();
}
}
}
2、符号引用
符号引用是编译原理中的概念,是相对于直接引用来说的,主要包括以下三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
上面的例子中,
-
a、b是字段名称,是一种符号引用
-
在Math类中,
- com/lxl/jvm/Math是类的全限定名,也是符号引用
- main和compute是方法名,也是符号引用
- ()是UTF8格式的描述符,这些都是符号引用。
最开始这些字面量和符号引用都是保存在常量池中,他们都是静态信息
当程序运行时被加载到内存后,这些符号才有对应的内存地址信息。这些常量一旦被转入内存就会变成运行时常量池。运行时常量池在方法区中。
再说的明白一些,到底什么是常量池,什么是运行时常量池?
Math类,生成的对应的class文件,class文件中定义了一个常量池集合,这个集合用来存储一系列的常量。这时候的常量池是静态常量池。
当程序运行起来,会将类信息加载到方法区中,并为这些常量分配内存地址,这时原来的静态常量池就转变成了运行时常量池。
符号引用在程序运行以后被加载到内存中,原来的代码就会被分配内存地址,引用这个对象的地方就会变成直接引用,也就是我们说的动态链接了。
例如,上面Math类中的compute()方法,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。
这句话的意思是:compute()方法被加载到内存以后,就有了自己的地址,原来调用computer()方法的符号引用,现在就变成对compute()地址的直接引用,这个直接引用是存在对象头里的,通过指针来指向直接饮用
二、字符串常量池
String c = "abcdefg"
字符串常量就保存在字符串常量池中
1、 字符串常量池的设计思想
1)字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
2)JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先查询字符串常量池是否存在该字符串
- 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
比如,下面的案例
String c = "abcdefg"
String d = "abcdefg"
- 首先在字符串常量池中开辟一块空间,用来保存字符串“abcdefg”,然后在让符号引用c指向“abcdefg”的地址。
- 执行到第二句代码时,会发现内存中已经有“abcdefg”了,那么就不会再创建了,而是直接让符号引用d指向“abcdefg”的地址。
2、字符串常量池保存的位置
Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池
Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里
Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里
用一段代码来验证字符串常量到底是存在哪里的?
package com.lxl.jvm;
import java.util.ArrayList;
/**
* jdk6:‐Xms6M ‐Xmx6M ‐XX:PermSize=6M ‐XX:MaxPermSize=6M
* jdk8:-Xms6M -Xmx6M -XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 10000000; i++) {
//String str = String.valueOf(i).intern();
//String.valueOf(i);
list.add("123");
}
}
}
Jdk7及以上:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
jdk6及以下
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
三、运行时常量池,字符串常量池,堆,方法区的位置关系
关于运行时常量池和字符串常量池以及和方法区,堆得关系,看下图:
jdk1.6:有永久代,运行时常量池保存在永久代中,字符串常量池是运行时常量池的一部分
jdk1.8及以后:有元空间,运行时常量池保存在元空间中,字符串常量池保存在堆中
\
今天的文章深刻理解运行时常量池、字符串常量池分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19344.html