旗下导航:搜·么
当前位置:网站首页 > JAVA教程 > 正文

java常量池图文详解【JAVA教程】,java

作者:搜教程发布时间:2019-12-02分类:JAVA教程浏览:43评论:0


导读:java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出,小菜早就对常量池有所耳闻,此次好好总结一下。引荐:java视频教程jvm虚拟内存散布:顺...

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网别的相干文章!

标签:java


欢迎 发表评论: