java 常量池和运行时常量池,java常量池和字符串常量池
如何解决写爬虫IP受阻的问题?立即使用。
Java恒池是一个经久不衰的话题,也是面试官的最爱。话题有很多种。小菜早就听说过恒池。这一次,我要好好总结一下。
推荐:java视频教程
jvm的虚拟内存分配:
计数器是jvm执行程序的管道,里面存储了一些跳转指令。这太深奥了,理解不了。
本地方法栈是jvm用来调用操作系统方法的栈。
虚拟机堆栈是jvm用来执行java代码的堆栈。
方法区存储一些常量、静态变量、类信息等。可以理解为内存中类文件的存储位置。
虚拟机堆是jvm用来执行java代码的堆。
Java中的常量池实际上分为两种形式:静态常量池和运行时常量池。
所谓静态常量池,就是*中的常量池。类文件。类文件中的常量池不仅包含字符串(数字)的字面量,还包含类和方法的信息,这些信息占据了类文件的大部分空间。
运行时常量池是指jvm虚拟机完成类加载操作后,将类文件中的常量池加载到内存中,保存在方法区。我们常说的常量池是指方法区的运行时常量池。
接下来,我们引用一些网上比较流行的常量池的例子,然后进行说明。
String s1= Hello
String s2= Hello
String s3= Hel lo
String s4=Hel 新字符串( lo );
String s5=新字符串( Hello );
string S6=S5 . intern();
字符串s7= H
String s8= ello
字符串s9=s7 s8
system . out . println(S1==S2);//真
system . out . println(S1==S3);//真
system . out . println(S1==S4);//假
system . out . println(S1==S9);//假
system . out . println(S4==S5);//假
system . out . println(S1==S6);//true首先需要说明的是,在java中,直接使用==运算符来比较两个字符串的引用地址,而不是比较内容。请将String.equals()用于比较内容。
S1==s2,这很容易理解。当s1和s2被赋值时,它们都使用字符串文字。说白了,他们就是把弦写死。编译时,这个字面值会直接放入类文件的常量池中,从而实现重用。加载到运行时常量池后,s1和s2指向同一个内存地址,所以它们是相等的。
S1==s3这个地方有个洞。虽然s3是一个动态拼接的字符串,但拼接中涉及的所有部分都是已知的文字。编译时,这个拼接会被优化,编译器会直接帮你拼出来,所以String s3= Hel lo在类文件中被优化为字符串S3=“Hello”;所以s1==s3成立。
S1==s4当然不相等。虽然s4也是拼接的,但是新的字符串( lo )部分不是已知的文字,而是一个意外的部分。编译器不会优化它,所以它必须等到运行时才能确定结果。结合字符串不变性定理,谁知道s4赋在哪里,所以地址肯定不一样。用图表来理清你的思路:
S1==s9不相等,道理相似。虽然s7和s8在赋值时使用了文字字符串,但是当它们拼接成s9时,s7和s8作为两个变量是不可预测的。编译器毕竟是编译器,不能当解释器用,所以没有优化。运行时,s7和s8拼接的新字符串在堆中的地址是不确定的,不能和方法区常量池中的s1地址相同。
S4==s5不需要解释,两者绝对不相等,都在堆里,只是地址不同。
S1==s6这两个等式完全归功于内点法。s5在堆里,内容是Hello。intern方法将尝试将Hello字符串添加到常量池中,并返回它在常量池中的地址。因为常量池中已经有一个Hello字符串,所以intern方法直接返回地址;s1在编译时已经指向常量池,所以S1和s6指向同一个地址并且相等。
至此,我们可以得出三个非常重要的结论:
为了更好地理解常量池,我们必须注意编译时的行为。
运行时常量池中的常量基本上来自每个类文件中的常量池。
程序运行时,jvm不会自动向常量池中添加常量,除非常量是手动添加的(比如调用intern方法)。
以上只涉及字符串常量池,其实还有整数常量池,浮点常量池等。但都差不多,只是常量不能手动添加到数值型的常量池中,常量池中的常量在程序启动时就已经确定了,比如整型常量池的常量范围:-128~127,常量池中只能使用这个范围内的数字。
实践
说了这么多,还是来摸摸真正的恒池吧。
如前所述,类文件中有一个静态常量池。这个常量池是由编译器生成的,用于在java源文件中存储字面量(本文只关注字面量)。假设我们有以下java代码:
String s= hi为了方便,就这么简单,可以!将代码编译成类文件后,用winhex打开二进制格式的类文件。如图所示:
简要说明类文件的结构。前四个字节是类文件的幻数,用来标识它是一个类文件。说白了就是文件头,即:CA FE BA BE。
接下来的四个字节是java的版本号。这里的版本号是34,因为作者是用jdk8编译的。版本号的级别对应jdk版本的级别。高配版可以兼容低配版,低配版不能执行高配版。所以,如果有一天读者想知道别人的类文件是用什么jdk版本编译的,可以看看这4个字节。
接下来是常量池入口,其中2个字节用于标识常量池中常量的数量。在这个例子中,值是00 1A,翻译成十进制的26,即有25个常数,其中第0个常数是一个特殊值,所以只有25个常数。
常量池中存储着各种类型的常量,它们都有自己的类型和存储规范。本文只关注字符串常量,从01 (1个字节)开始,然后用2个字节记录字符串的长度,然后是字符串的实际内容。在本例中:01 00 02 68 69。
接下来,我们来谈谈运行时常量池。由于运行时常量池在方法区,我们可以通过jvm参数设置方法区大小:-XX:PermSize,-XX:MaxPermSize,从而间接限制常量池大小。
假设jvm启动参数是:-xx:permsize=2m-xx:maxpermsize=2m,然后运行下面的代码:
//保留引用以防止自动垃圾回收
ListString list=new ArrayList string();
int I=0;
while(true){
//通过intern方法手动将常量添加到常量池中
list.add(String.valueOf(i))。实习生());
}程序会立即抛出:线程‘main’Java . lang中的异常,内存不足错误:perm genspace异常。PermGen空间是方法区,足以说明常量池在方法区。
在jdk8中,method区域被移除,取而代之的是metaspace区域,所以我们需要使用新的jvm参数:-XX:MaxMetaspaceSize=2M,仍然运行上面的代码,抛出:java.lang.out of memory错误:Metaspace异常。类似地,运行时常量池被划分为元空间区域。关于Metaspace区域的具体知识,请自行搜索。
更多java知识,请关注java基础课程专栏。以上是java常量池图形化讲解的详细内容。更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。