
java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出,小菜早就对常量池有所耳闻,此次好好总结一下。
引荐:java视频教程
jvm虚拟内存散布:
顺序计数器是jvm实行顺序的流水线,寄存一些跳转指令,这个太深邃,小菜不懂。
当地要领栈是jvm挪用操纵系统要领所运用的栈。
虚拟机栈是jvm实行java代码所运用的栈。
要领区寄存了一些常量、静态变量、类信息等,能够明白成class文件在内存中的寄存位置。
虚拟机堆是jvm实行java代码所运用的堆。
Java中的常量池,现实上分为两种形状:静态常量池和运转时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不单单议包括字符串(数字)字面量,还包括类、要领的信息,占用class文件绝大部份空间。
而运转时常量池,则是jvm虚拟机在完成类装载操纵后,将class文件中的常量池载入到内存中,并保存在要领区中,我们常说的常量池,就是指要领区中的运转时常量池。
接下来我们援用一些收集上盛行的常量池例子,然后借以解说。
String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel" + "lo"; String s4 = "Hel" + new String("lo"); String s5 = new String("Hello"); String s6 = s5.intern(); String s7 = "H"; String s8 = "ello"; String s9 = s7 + s8; System.out.println(s1 == s2); // true System.out.println(s1 == s3); // true System.out.println(s1 == s4); // false System.out.println(s1 == s9); // false System.out.println(s4 == s5); // false System.out.println(s1 == s6); // true
起首申明一点,在java 中,直接运用==操纵符,比较的是两个字符串的援用地点,并非比较内容,比较内容请用String.equals()。
s1 == s2这个非常好明白,s1、s2在赋值时,均运用的字符串字面量,说白话点,就是直接把字符串写死,在编译时期,这类字面量会直接放入class文件的常量池中,从而完成复用,载入运转时常量池后,s1、s2指向的是统一个内存地点,所以相称。
s1 == s3这个处一切个坑,s3虽然是动态拼接出来的字符串,然则一切介入拼接的部份都是已知的字面量,在编译时期,这类拼接会被优化,编译器直接帮你拼好,因而String s3 = "Hel" + "lo";在class文件中被优化成String s3 = "Hello";,所以s1 == s3建立。
s1 == s4固然不相称,s4虽然也是拼接出来的,但new String("lo")这部份不是已知字面量,是一个不可预感的部份,编译器不会优化,必需比及运转时才够一定效果,连系字符串稳定定理,鬼晓得s4被分派到哪去了,所以地点一定差别。配上一张简图理清思绪:
s1 == s9也不相称,原理差不多,虽然s7、s8在赋值的时刻运用的字符串字面量,然则拼接成s9的时刻,s7、s8作为两个变量,都是不可预感的,编译器毕竟是编译器,不可能当诠释器用,所以不做优化,比及运转时,s7、s8拼接成的新字符串,在堆中地点不一定,不可能与要领区常量池中的s1地点雷同。
s4 == s5已不必诠释了,相对不相称,两者都在堆中,但地点差别。
s1 == s6这两个相称完整归功于intern要领,s5在堆中,内容为Hello ,intern要领会尝试将Hello字符串增加到常量池中,并返回其在常量池中的地点,因为常量池中已有了Hello字符串,所以intern要领直接返回地点;而s1在编译期就已指向常量池了,因而s1和s6指向统一地点,相称。
至此,我们能够得出三个非常重要的结论:
必须要关注编译期的行动,才更好的明白常量池。
运转时常量池中的常量,基础来源于各个class文件中的常量池。
顺序运转时,除非手意向常量池中增加常量(比方挪用intern要领),不然jvm不会自动增加常量到常量池。
以上所讲仅触及字符串常量池,现实上另有整型常量池、浮点型常量池等等,但都迥然差别,只不过数值范例的常量池不能够手动增加常量,顺序启动时常量池中的常量就已一定了,比方整型常量池中的常量局限:-128~127,只要这个局限的数字能够用到常量池。
实践
说了这么多理论,接下来让我们触摸一下真正的常量池。
前文提到过,class文件中存在一个静态常量池,这个常量池是由编译器生成的,用来存储java源文件中的字面量(本文仅仅关注字面量),假定我们有以下java代码:
String s = "hi";
为了轻易起见,就这么简朴,没错!将代码编译成class文件后,用winhex翻开二进制花样的class文件。如图:
简朴解说一下class文件的构造,开头的4个字节是class文件魔数,用来标识这是一个class文件,说白话点就是文件头,既:CA FE BA BE。
紧接着4个字节是java的版本号,这里的版本号是34,因为笔者是用jdk8编译的,版本号的上下和jdk版本的上下相对应,高版本能够兼容低版本,但低版本没法实行高版本。所以,假如哪天读者想晓得他人的class文件是用什么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,然后运转以下代码:
//坚持援用,防备自动垃圾接纳 List<String> list = new ArrayList<String>(); int i = 0; while(true){ //经由过程intern要领向常量池中手动增加常量 list.add(String.valueOf(i++).intern()); }
顺序马上会抛出:Exception in thread "main" java.lang.outOfMemoryError: PermGen space非常。PermGen space恰是要领区,足以申明常量池在要领区中。
在jdk8中,移除了要领区,转而用Metaspace地区替换,所以我们须要运用新的jvm参数:-XX:MaxMetaspaceSize=2M,依旧运转如上代码,抛出:java.lang.OutOfMemoryError: Metaspace非常。同理申明运转时常量池是划分在Metaspace地区中。细致关于Metaspace地区的学问,请读者自行搜刮。
更多java学问请关注java基础教程栏目。
以上就是java常量池图文详解的细致内容,更多请关注ki4网别的相干文章!