大家好,又见面了,我是你们的朋友全栈君。
要是没有实践过别人书本上的理论的话,就还是会说常量池在方法区里面,要是知道方法区已经随jdk升级,被逐步干掉的话,额,也不能说被干掉,只是被优化了,这又体现了看书的程度深浅了,就会看到有的文章说常量池移动到heap堆里面了,还有极少的说移动到Metaspace里面了,产生了分歧。这个时候就需要实践出真知了。
代码语言:javascript复制/**
* 测试 常量池在分区的位置
*
* @author LiXuekai on 2020/6/9
*/
public class StringConstantPoolTest {
public static void main(String[] args) {
List<String> list = Lists.newArrayList();
while (true) {
list.add(String.valueOf(System.currentTimeMillis()).intern());
}
}
}
代码很简单,那本书上也大致是这个样子。String.intern()会将字符串丢到字符串常量池里面。以此来不断增加常量池的使用部分。
还有你得一直使用着这个字符串,不然就被gc了,你就看不到oom现象了。
jdk1.6的测试
他当时测试的时候,出的异常是方法区OOM.
当时的jdk还是1.6,我这就不测试1.6的了。
执行结果说明jdk1.6的时候常量池在方法区。
jdk1.7的测试
下面是jdk1.7的测试情况,还是这个代码,就是启动参数设置的不一样。
使用的jvm参数设置:
代码语言:javascript复制-XX: PrintGCDetails -Xms100M -Xmx100M -Xmn10M -XX:SurvivorRatio=8 -XX:PermSize=10m -XX:MaxPermSize=10m
然后这个程序的异常截图如下:
堆空间溢出。
使用jvm看内存分区的使用情况的截图:
可以看到堆里面old区总共90M,已经89M,这个是在报oom之前的截图。方法区总共就分了10m,在oom的时候,也就使用了7m多,说明这个常量池,在jdk1.7的时候,确实被安排到了堆Java heap里面了。
上面的说明加起来,使得下面这个理论得到了验证。
永久代主要存放以下数据:
JVM internal representation of classes and their metadata //类及其元数据的JVM内部表示 Class statics //类的静态 Interned strings //实际字符串,说的就是常量池吧 从 JDK7 开始,JDK 开发者们就有消灭永久代的打算了。有部分数据移到永久代之外了:
Symbols => native memory // 符号引用 >本机内存 Interned strings => Java Heap // Interned string => Java堆 Class statics => Java Heap //类statics => Java堆
结论:jdk1.7的时候,常量池已经被安排在堆里面了。
jdk1.8的测试
使用的jvm参数设置:
-XX: PrintGCDetails -Xms200M -Xmx200M -Xmn10M -XX:SurvivorRatio=8 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX: UseCompressedClassPointers -XX: UseCompressedOops -XX:CompressedClassSpaceSize=5M
代码执行时候报的异常截图
使用jvm看内存分区的使用情况的截图:
设置堆的大小是200m,元空间就只有10m,测试了几次了,每次堆的old区里面也就残留81m,看元空间的最大是10m,使用了8.4m之后,差不多就oom了。堆还差得远呢。
说明这个常量池就是在元空间里面了吧,不能在Java heap里面了吧。
在看元空间的时候,有个这个图
说是开发jvm的人画的。元空间被分为2部分,类空间 class space 和非类空间 non class space
深入 Class Space:
最大的一部分是 Klass 结构,它是固定大小的。
然后紧跟着两个可变大小的 vtable 和 itable,前者由类中方法的数量决定,后者由这个类所实现接口的方法数量决定。
随后是一个 map,记录了类中引用的 Java 对象的地址,尽管该结构一般都很小,不过也是可变的。
vtable 和 itable 通常也很小,但是对于一些巨大的类,它们也可以很大,一个有 30000 个方法的类,vtable 的大小会达到 240k,如果类派生自一个拥有 30000 个方法的接口,也是同理。但是这些都是测试案例,除了自动生成代码,你从来不会看到这样的类。
深入 Non-Class Space
这个区域有很多的东西,下面这些占用了最多的空间:
- 常量池,可变大小;
- 每个成员方法的 metadata:ConstMethod 结构,包含了好几个可变大小的内部结构,如方法字节码、局部变量表、异常表、参数信息、方法签名等;
最后,那就按照老外的说法来吧,我的测试也稍微辅助说明一下吧,虽然代码里面报错是Java heap 溢出。这点奇怪了。
姑且最终结论,常量池就是在Metaspace 元空间里面。
参考:
老外原文地址
怎么想怎么都觉得不对,这个常量池怎么能在元空间里面。
后面又调整参数,-xms 和-xmx设置2g,代码运行时间就更久了,说明啥,数据对象分配了那么多,要是这个方法区,早就被撑爆炸了,怎么可能会在元空间呢。这地方就分给他10,这个图上最大也就13m,肯定不可能装下那么多的字符串的。那老外画的图又作何解释呢。老外画错了,或者说老外没画明白。
所以,最终的测试结论:
结论:这个常量池(特指字符串常量池而不是所有的常量池),应该还是在Java heap里面,
上面的测试只能证明:jdk1.8中 字符串常量池是在堆里面。
JAVA的三种常量池
此外,Java有三种常量池,即字符串常量池(又叫全局字符串池)、class文件常量池、运行时常量池。
1. 字符串常量池(也叫全局字符串池、string pool、string literal pool)
字符串常量池 Interned Strings
2. 运行时常量池(runtime constant pool)
当程序运行到某个类时,class文件中的信息就会被解析到内存的方法区里的运行时常量池中。每个类都有一个运行时常量池
3. class文件常量池(class constant pool)
class常量池是在编译后每个class文件都有的,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是 常量池*(constant pool table)*,用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。*字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。
按照这个分析的话,
运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中。
这个理论还说的通,但是另外2个常量池就不好测试了。
Java Language and Virtual Machine Specifications
上面这个是jdk的官网链接,里面不管是jdk几,里面都有Method Area方法区
都是这么几个分区,说明这几个名字都是概念。
顺便再上传一下他原文和网页简单翻译的图
Each run-time constant pool is allocated from the Java Virtual Machine’s method area (§2.5.4). The run-time constant pool for a class or interface is constructed when the class or interface is created (§5.3) by the Java Virtual Machine.
方法区确实是一个概念,概念,概念。
干掉的是永久代,而不是方法区。
卧槽,接口还在,只是实现类变了。
最开始的实现叫PermGen,后来是PermGen java heap 一起实现,现在叫Metaspace Java heap 一起协调工作。
再补充帮助理解。
元空间并不是方法区!!!
方法区包括类信息、常量、静态变量等,是JVM规范。 方法区是jvm规范里面的概念。
1.7之前方法区的实现就是永久代。 1.7 把常量池和静态变量放入了堆中,也就是方法区由永久代和堆实现。
1.8 把永久代删除使用元空间,也就是方法区由元空间(类信息)和堆实现(常量池、静态变量)。
堆中包含正常对象和常量池,new String()放入堆中,String::inter会将堆中的String变量放入堆中的常量池中。
这个解释就比较完美了。
Each run-time constant pool is allocated from the Java Virtual Machine’s method area (§2.5.4). The run-time constant pool for a class or interface is constructed when the class or interface is created (§5.3) by the Java Virtual Machine.
这句话,可不就是说,所有的运行时常量池都是从jvm的 method area 方法区分配来的。
所以,说常量池在方法区,是对的。因为方法区是个概念的东西。
在jvm里面具体实现还的根据不同的jdk,实现的区的名称也不一样,前有PermGen,后有permGen Java heap ,后再有metaspace Java heap。
最终结论:
字符串常量在堆内存,类的元数据在本地内存。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/164776.html原文链接:https://javaforall.cn