澳门新葡萄京官网注册 8

澳门新葡萄京官网注册《深入理解Java虚拟机》读书笔记之——运行时数据区域

世家理解,Java中string.intern(卡塔尔方法调用会先去字符串常量池中寻觅相应的字符串,假诺字符串不设有,就能在字符串常量池中开创该字符串然后再重返。

String是Java幼功的要紧考试之处。可问的点多,並且不菲点能够横向切到任何考场,或纵向深切JVM。

澳门新葡萄京官网注册 1

字符串常量池是三个一定大小的HashMap,桶的数额暗许是1009,
从Java7u40开端,该暗中认可值增大到60013。在Java6中级,字符串常量池是身处Perm空间的,从Java7以前,字符串常量池被移到Heap空间。上边,大家由此测验程序来眼线字符串常量池在Java6,Java7七个区别版本底下的内部存款和储蓄器分配景况。

正文略过了String的基本内容,入眼在于String#intern()。

Java虚构机械运输作时数据区

测验程序

public class StringPoolTest {

    public void testStringPoolWithLongString(){
        long i=0;
        while(true){
            String longString = "This is a very long string, very very long string to test the gc behavior of the string constant pool"+i;
            longString.intern();
            i++;
        }
    }

    public static void main(String[] args){
        StringPoolTest stringPoolTest = new StringPoolTest();
        stringPoolTest.testStringPoolWithLongString();
    }
}

测试程序非常粗略,一个死循环,循环之中通过依次增加变量i创制独一的字符串,然后用main函数运行程序。

String常量池

String常量可能会在三种机缘进入常量池:

  1. 编译期:通过双引号表明的常量(包括展现注脚静态编写翻译优化后的常量,如”1”+”2”优化为常量”12”),在前端编写翻译期将被静态的写入class文件中的“常量池”。该“常量池”会在类加载后被载入“内部存款和储蓄器中的常量池”,约等于大家向来所说的常量池。同期,JIT优化也也许发生肖似的常量。
  • 运行期:调用String#intern()方法,可能将该String对象动态的写入上述“内部存款和储蓄器中常量池”。

机遇1的一坐一起是不言自明标。原理可阅览class文件布局、类加载、编译期即运营期优化等剧情。

机缘2在jdk6和jdk7中的行为差别,下边研讨。

1.1 程序计数器

作用:
它能够看成是时下线程所进行的字节码的行号提示器;为了线程切换后能上涨到科学的实践职位,每条线程都急需有二个独自的顺序计数器,各条线程之间流量计互不影响,独立存款和储蓄(线程私有)。
只要线程正在推行的是三个Java方法,流速计记录的是正值实行的捏造机字节码指令的地点;若是线程正在举行的是Native方法,计数器值为空。

不行现象:
此内部存款和储蓄器区域是绝无唯有三个在Java虚构机规范中一向不规定任何OutOfMemoryError意况的区域。

Java 6

咱俩选用版本Jdk1.6.0_29来跑该程序,展开Java
VisualVM监察和控制,能够见见,Perm区不断发生GC,因此的出结论,即使字符串常量池放在Perm空间,但当Perm空间周围满的时候,JVM会将字符串常量池中的无用字符串回笼掉。

澳门新葡萄京官网注册 2

String#intern()

读者可一贯阅读参考资料。下述总括仅为了猴子自个儿复习方便。

1.2 Java设想机栈

作用:
Java虚构机栈的生命周期与线程相像。虚构机栈描述的是Java方法推行的内部存储器模型:种种方法在试行的同时都会成立贰个栈帧(八个艺术叁个栈帧)用于存储局地变量表、操作数栈、动态链接、方法说话等新闻。每七个办法从调用直至实行到位的经过,就对应着二个栈帧在编造机栈中入栈到出栈的历程。
四个线程中的方法或许还有大概会调用其余方法,那样就能够组成艺术调用链,并且以此链恐怕会非常长,而且每一个线程都有点子处于执涨势况。对于实施引擎来讲,唯有移动线程栈顶的栈帧才是有效的,称为当前栈帧(Current
Stack Frame),这些栈帧关联的章程称为当前艺术(Current Method)。

澳门新葡萄京官网注册 3

image.png

澳门新葡萄京官网注册 4

image.png

有些变量表:存放编译器可以见到的各样基本数据类型、对象引用和returnAddress类型。当中60位长度的long和double类型的数额占用2个部分变量空间,其他的数据类型只占用1个。局地变量表所需的内部存款和储蓄器空间在编写翻译时期实现分红,当步向三个方式时,那一个办法必要在帧中分红多大的局地变量空间是全然明显的,在措施运转时期不会转移一些变量表的大小。

那么些现象:
StackOverflowError分外:线程要求的栈深度超越设想机所允许的吃水。
OutOfMemoryError十分:虚构机栈增添时无法报名到丰裕的内部存款和储蓄器。

Java 7

下边,大家切换成Jdk1.7.0_67重跑该程序,能够观望Perm区内部存款和储蓄器分配曲线很平整,没有现身内部存款和储蓄器分配之处。

澳门新葡萄京官网注册 5

但在Heap空间,新的靶子不断发出,然后不断触发GC

澳门新葡萄京官网注册 6

声明

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java&trade; Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */  
public native String intern();

String#intern(State of Qatar是多少个native方法。依据Javadoc,假若常量池中留存当前字符串,
就能直接再次来到当前字符串. 假使常量池中平昔不此字符串,
会将此字符串归入常量池中后, 再再次来到。

1.3 本地点法栈

作用:
本地点法栈与虚构机栈所发挥的效果与利益特别雷同,它们中间的差距是杜撰机栈为虚构机实践Java方法(字节码)服务,而地面方法栈则为虚构机使用到的Native方法服务。

不行现象:
StackOverflowError分外:线程央浼的栈深度超过设想机所允许的吃水。
OutOfMemoryError格外:虚构机栈扩大时不恐怕报名到丰盛的内部存款和储蓄器。

结论

由于Perm区大小是简单的,经常唯有几十MB,所以不推荐在Java6下布满应用String.intern(卡塔尔(قطر‎,那篇文章string-intern-in-java-6-7-8的性情测验声明,在Java6底下大量选用intern(卡塔尔(قطر‎会促成应用品质的明显下跌,还应该有十分的大恐怕发生OOM错误。但从Java7发端,字符串常量池被移到了Heap空间,Heap空间的尺寸只受制于机器的实际内部存款和储蓄器大小,由此,在Java7下行使String.intern(卡塔尔国能更低价地回退重复String对象对内存的占领。

贯彻原理

JNI最终调用了c++实现的StringTable::intern(卡塔尔国方法:

oop StringTable::intern(Handle string_or_null, jchar* name,  
                        int len, TRAPS) {  
  unsigned int hashValue = java_lang_String::hash_string(name, len);  
  int index = the_table()->hash_to_index(hashValue);  
  oop string = the_table()->lookup(index, name, len, hashValue);  
  // Found  
  if (string != NULL) return string;  
  // Otherwise, add to symbol to table  
  return the_table()->basic_add(index, string_or_null, name, len,  
                                hashValue, CHECK_NULL);  
}
oop StringTable::lookup(int index, jchar* name,  
                        int len, unsigned int hash) {  
  for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) {  
    if (l->hash() == hash) {  
      if (java_lang_String::equals(l->literal(), name, len)) {  
        return l->literal();  
      }  
    }  
  }  
  return NULL;  
}

在the_table(卡塔尔国重临的hash表中寻找字符串,如若存在就重回,不然加入表。

StringTable是一个固化大小Hashtable,暗许大小是1009。基本逻辑与Java中HashMap雷同,也应用拉链法化解碰撞难点。

既然如此是拉链法,那么一旦放进的String相当多,就能够加深碰撞,引致链表十分长。最坏意况下,String#intern(卡塔尔(قطر‎的习性由O(1)退化到O(nState of Qatar。

  • jdk6中StringTable的长短固定为1009。
  • jdk7中,StringTable的长短可以经过二个参数-XX:StringTableSize指定,默认1009。

1.4 Java堆(GC堆)

作用:
Java堆是虚构机所管理的内部存款和储蓄器中最大的一块,在设想机运维时创立。此内存区域的必定要经过之处指标便是存放对象实例和数组。
Java堆是污源搜聚器处理的基本点区域,因而也被称得上“GC堆”。
从内部存款和储蓄器回笼的角度看,由于前几日收罗器基本都施用分代搜集算法,所以Java堆被分为:新生代和耄耋之时代;再留意一点:Eden空间、From
SurOne plusr空间、To
SurHTCr空间等。值得注意的是,从JKD1.7早先,永恒代Perm慢慢被移除,最新的JDK1.第88中学一度选拔元空间(MetaSpace)代替长久代。
从内部存款和储蓄器分配的角度看,线程分享的Java堆中或许划分出多个线程私有的分红缓冲区。

澳门新葡萄京官网注册 7

image.png

澳门新葡萄京官网注册 8

image.png

不行情状:
OutOfMemoryError格外:在堆中从未内部存款和储蓄器实现实例分配,何况堆也无从扩张。

jdk6和jdk7下String#intern()的区别

1.5 方法区

作用:
用以存储已被虚构机加载的类新闻、常量、静态变量、即时编写翻译器编写翻译后的代码等数码。
这些区域的内部存款和储蓄器回笼指标重若是本着常量池的回笼和对项指标卸载。

非常现象:
OutOfMemoryError万分:方法区不能知足内存分配要求

引言

相信广大Java程序猿都做相符String s = new String("abc");其一讲话成立了多少个对象的问题。这种主题材料首借使为了考查技师对字符串对象常量池的左右。上述的语句中开创了2个目的:

  • 首先个对象,内容”abc”,存款和储蓄在常量池中。
  • 其次个指标,内容”abc”,存款和储蓄在堆中。

1.6 运营时常量池

常量池(Constant Pool
Table),用于贮存编写翻译期生成的各类字面量、符号引用,文字字符串、final变量值、类名和章程名常量,那有个别剧情将在类加载后寄放到方法区的运作时常量池中。它们以数组形式探访,是调用方法、与类联系及类的对象化的桥梁。

运行时常量池除了寄存编写翻译期发生的Class文件的常量外,还可存放在程序运转时期改换的新常量,相比何奇之有扩大新常量方法有String类的internd(卡塔尔国方法。String.intern(卡塔尔是三个Native方法,它的效果是:假如运转时常量池中已经蕴涵一个也正是此String对象内容的字符串,则赶回常量池中该字符串的引用;若无,则在常量池中成立与此String内容一模二样的字符串,并重临常量池中成立的字符串的引用。可是JDK7的intern(State of Qatar方法的完结成所区别,当常量池中尚无该字符串时,不再是在常量池中创设与此String内容一致的字符串,而改为在常量池中著录堆中第叁回面世的该字符串的征引,并重返该援引。

可是,JDK1.7事情未发生前运转时常量池是方法区的一局地,JDK1.7及今后版本现已将运转时常量池从方法区中移了出来,在堆(Heap)中开垦了一块区域存放运转时常量池。

问题

来看一段代码:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

打字与印刷结果:

# jdk6下
false false
# jdk7下
false true

切切实实怎么稍后再解释,然后将s3.intern();语句下调一行,放到String s4 = "11";后面。将s.intern();放到String s2 = "1";后面:

public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);
}

打印结果:

# jdk6下
false false
# jdk7下
false false

1.7 直接内部存款和储蓄器

直白内部存储器(Direct
memory)并非JVM运营时数据区的一部分,亦不是Java设想机标准中定义的内存区域。但这一部分内部存款和储蓄器也被每每使用,何况它也可以有可能导致OutOfMemoryError非常现身。

本机直接内部存储器的抽成不会碰到Java堆大小的界定,可是,照旧会惨被本机总内存(包蕴RAM及SWAP区可能分页文件)的大小及Computer寻址空间的限量,进而引致动态扩大时现身OutOfMemoryError至极。

jdk6的解释

image.png

注:图乳白色线条代表String对象的原委针对;月光蓝线条代表地址指向。

jdk6中,上述的具备打字与印刷都是false。

因为jdk6的常量池放在Perm区中,和正规的Heap(指Eden、Surviver、Old区)完全抽离。具体来讲:使用引号证明的字符串都以因而编写翻译和类加载直接载入常量池,坐落于Perm区;new出来的String对象坐落于Heap(E、S、O)中。拿三个Perm区的靶子地址和Heap中的对象地址进行相比较,肯定是分裂的。

Perm区首要囤积一些加载类的音讯、静态变量、方法某些、常量池等。

jdk7的解释

在jdk6及早先的本子中,字符串常量池都以放在Perm区的。Perm区的暗中认可大小独有4M,若是多放一些大字符串,超级轻易抛出OutOfMemoryError: PermGen space

由此,jdk7已经将字符串常量池从Perm区移到平常的Heap(E、S、O)中了。

Perm区即长久代。本人用恒久代达成方法区就便于碰着内部存款和储蓄器溢出;何况方法区贮存的剧情也很难预计大小,没须要放在堆中管理。jdk8已经裁撤了长久代,在堆外新建了二个Metaspace达成方法区。

还好因为字符串常量池移到了Heap中,才发出了上述变化。

第一段代码

image.png

先看s3和s4:

  • 首先,String s3 = new String("1") + new String("1");,生成了几个对象,s3最后指向堆中的”11″。注意,那时常量池中是还未有字符串”11″的。
  • 然后,s3.intern();,将s3中的字符串”11″归入了常量池中,因为这个时候常量池中空头支票字符串”11″,由此平常做法与跟jdk6千人一面,在常量池中生成叁个String对象”11″——不过,jdk7中常量池不在Perm区中了,相应做了调度:常量池中不要求再囤积一份对象了,而是直接存款和储蓄堆中的援引,也便是s3的引用地址。
  • 接下来,String s4 = "11";,”11″通过双引号显示注明,由此会直接去常量池中搜寻,若无再成立。发掘已经有这么些字符串了,也等于刚刚由此s3.intern();仓储在常量池中的s3的援引地址。于是,间接回到s3的引用地址,s4赋值为s3的援引,s4指向堆中的”11″
  • 终极,s3、s4指向的堆中的”11″,常量池中存款和储蓄s3的援引,满足s3 == s4

再看s和s2:

  • 首先,String s = new String("1");,生成了2个对象,常量池中的”1″和堆中的”1″,s指向堆中的”1″
  • 然后,s.intern();,上一句已经在常量池中开创了”1″,所以这里什么都不做。
  • 接下来,,String s2 = "1";,常量池中有”1″,由此,s2直接针对常量池中的”1″
  • 最后,s指向的堆中的”1″,s2指向常量池中的”1″,常量池中蕴藏字符串”1″,不满意s == s2

其次段代码

image.png

先看s3和s4,将s3.intern();放在了String s4 = "11";后:

  • 先执行String s4 = "11";,当时,常量池中不真实”11″,由此,将”11″归入常量池,然后s4指向常量池中的”11″
  • 再执行s3.intern();,上一句已经在常量池中创设了”11″,所以那边什么都不做。
  • 末尾,s3仍指向的堆中的”11″,s4指向常量池中的”11″,常量池中存放字符串”11″,不再满足s3 == s4

再看s和s2,将s.intern();放到String s2 = "1";后:

  • 先执行String s2 = "1";,以前已通过String s = new String("1");在常量池中成立了”1″,由此,s2直接针对常量池中的”1″
  • 再执行s.intern();,常量池中有”1″,所以那边什么都不做。
  • 最终,s指向的堆中的”1″,s2指向常量池中的”1″,常量池中存款和储蓄字符串”1″,仍不满意s == s2

差异小结

jdk7与jdk6对照,对String常量池的职位、String#intern(卡塔尔(قطر‎的语义都做了改造:

  • 将String常量池从Perm区移到了Heap区。
  • 调用String#intern(卡塔尔方法时,堆中有该字符串而常量池中绝非,则平素在常量池中保存堆中目的的引用,而不会在常量池中再度创设对象。

利用姿势

建议直接阅读参考资料。

额外的难点

String#intern(卡塔尔国的主导用法如下:

String s1 = xxx1.toString().intern();
String s2 = xxx2.toString().intern();
assert s1 == s2;

然而,xxx1.toString()xxx2.toString()现已创立了四个无名String对象,这件事后再调用String#intern()。那么,那多少个无名对象去哪了

推断猴子对创造对象的长河明白卓殊,大概xxx1.toString()回来时还还没有将指标保存到堆上?大概String#intern(State of Qatar上做了哪些语法糖?

末端一时间再解决吧。。。


参考:

  • 深刻剖判String#intern

本文链接:

本文链接:String常量池和String#intern()
作者:猴子007
出处:https://monkeysayhi.github.io
正文基于文化分享签字-雷同方法分享4.0国际许可公约公布,接待转发,演绎或用于商业目标,可是必需保留本文的具名及链接。

发表评论

电子邮件地址不会被公开。 必填项已用*标注