Java字符串常量池_字符串常量池溢出

Java字符串常量池_字符串常量池溢出包装类对象池是不是JVM常量池的一种?Java创建newString会产生多少个对象

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

在Java的内存分配中,总共3种常量池:

Java 常量池详解(二)class文件常量池 和 Java 常量池详解(三)class运行时常量池

1.字符串常量池(String Constant Pool)

在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

在这里插入图片描述

1.1:字符串常量池在Java内存区域的哪个位置?

  • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
  • 在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。
  • (堆内是可以进行回收的,然后方法区也是能回收的,但是本身区域内存比较少,如果用的字符串常量太多了,也会抛java.lang.OutOfMemoryError:PermGenspace 异常)

1.2:字符串常量池是什么?

  • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
  • 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
  • 在JDK7.0中,StringTable的长度可以通过参数指定:
-XX:StringTableSize=66666`

1.3 字符串常量池生成的时机?

String a = "a";

在这里插入图片描述

  • 全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)
    在这里插入图片描述

如何将String对象放入到常量池

  • “abc” 双引号String 对象会自动放入常量池
  • 调用String的intern 方法也会将对象放入到常量池中

String 对象代码案例解析

public static void main(String[] args) { 
   
    String a = "a";
    String b = "b";
    String c = "a" + "b";
    //生成两个对象 一个"ab" ,一个新的String 对象value 值是ab
    //public String(String original) { 
   
    // this.value = original.value;
    // this.hash = original.hash;
    //}
    String d = new String("ab"); 
    
    String e = a + "b";
    String f = a + b;
    String g = "ab";
    
   System.out.println(e == c);
   System.out.println(c == d);
   System.out.println(f == c);
   System.out.println(g == c);
   
   String e1 = e.intern();
   String c2 = c.intern();
   System.out.println(e1 == c2);
   System.out.println(e1 == c);
}

//运行结果
false
false
false
true
true
true
  1. String c =“a” + “b” 和String c = “a” + b (String b= “b”)的区别
String b = "b";
String c = "a" + "b"; 等价于 String c ="ab"
String c1 = "a" + b; 
 
// java 反编译的结果 
 0 ldc #3 <b> //load constant 加载常量 "b"
 2 astore_1   // 存入变量1中
 3 ldc #4 <ab> //自动识别了 
 5 astore_2
 6 new #7 <java/lang/StringBuilder>
 9 dup
10 invokespecial #8 <java/lang/StringBuilder.<init>>
13 ldc #2 <a>
15 invokevirtual #9 <java/lang/StringBuilder.append>
18 aload_1
19 invokevirtual #9 <java/lang/StringBuilder.append>
22 invokevirtual #10 <java/lang/StringBuilder.toString>
25 astore_3
26 return

(1) “a”+“b” 编译器自动识别了变成了 “ab” => 3 ldc #4
(2) “a” + b(变量)

  1. 先new 了StringBuilder 对象,并初始化init
  2. 然后bulider.append(“a”)
  3. 从变量1(b)中取出值”b”
  4. 然后执行了bulider.append(“b”)
  5. 最后执行了builder.toString() 方法 给变量3( c1)进行赋值

new string(“abc”)创建了几个对象

答案:是两个 ,new string(xxxx)方法,xxxx传入的是String对象。说明xxxx也是String对象。

	 public String(String original) { 
   
        this.value = original.value;
        this.hash = original.hash;
    }

String 是一个final 类型对象是不会变化的,如果发生变化,说明其实是新的对象。

public final class String

解析public native String intern() 方法

如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回

native实现代码:

  • \openjdk7\jdk\src\share\native\java\lang\String.c
Java_java_lang_String_intern(JNIEnv *env, jobject this) 
{ 
    
    return JVM_InternString(env, this); 
}
  • \openjdk7\hotspot\src\share\vm\prims\jvm.h
JNIEXPORT jstring JNICALL 
JVM_InternString(JNIEnv *env, jstring str);
  • \openjdk7\hotspot\src\share\vm\prims\jvm.cpp
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str)) 
  JVMWrapper("JVM_InternString"); 
  JvmtiVMObjectAllocEventCollector oam; 
  if (str == NULL) return NULL; 
  oop string = JNIHandles::resolve_non_null(str); 
  //调用StringTable::intern 方法
  oop result = StringTable::intern(string, CHECK_NULL);
  return (jstring) JNIHandles::make_local(env, result); 
JVM_END
  • \openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
oop StringTable::intern(Handle string_or_null, jchar* name, int len, TRAPS) { 
    
  //根据名字找到对应hash下标
  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); 
}
  • \openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
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; 
}
1.它的大体实现结构就是:JAVA 使用 jni 调用c++实现的StringTable的intern方法。

2.要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

Interger 包装类的池化技术

public final class Integer extends Number implements Comparable<Integer> { 
   
   
    @Native public static final int   MIN_VALUE = 0x80000000;

  
    @Native public static final int   MAX_VALUE = 0x7fffffff;

    //缓存-128到127的值在IntegerCache里面,可以进行共享
    private static class IntegerCache { 
   
        static final int low = -128;
        static final int high;
        static final Integer cache[];
    
        static { 
   
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) { 
   
                try { 
   
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) { 
   
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
    
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
    
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
    
        private IntegerCache() { 
   }
    }
    
    public static Integer valueOf(int i) { 
   
        //是不是在-128到127里面,不是的话就生成新的Integer
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
}        

Integer 对象代码案例解析

public void test(){ 
   
    Integer i1 = 10;
    Integer i2 = 10;
    Integer i3 = new Integer(10);//新对象
    Integer i4 = new Integer(10);//新对象
    Integer i5 = Integer.valueOf(10);//从缓存池里面获取。
    Integer i6 = Integer.valueOf(128);
    Integer i7 = 128;

    System.out.println(i1 == i2); // true
    System.out.println(i2 == i3); // false
    System.out.println(i3 == i4); // false
    System.out.println(i1 == i5); // true
    System.out.println(i6 == i7); // false
}

//运行结果:
true
false
false
true
false

为啥Integer i1 =10 跟Integer.valueOf(10) 是相等的?

  • 因为Integer i1 = 10 底层原理是 Integer i1 = Integer.valueof(10)
  //Integer i1 =10 反编译的结果
  0 bipush 10
  2 invokestatic #14 <java/lang/Integer.valueOf> //调用了Integer.valueof方法
  5 astore_1

为啥Integer i1 =128 跟Integer.valueOf(128) 是不相等的?

  • 因为超过-128~127 这个范围,就不在缓存池里面,不能共享都是新new 出来的
public static Integer valueOf(int i) { 
   
        //是不是在-128到127里面,不是的话就生成新的Integer
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

问题:包装类对象池是不是 JVM 常量池的一种?

  • 包装类的对象池是池化技术的应用,并非是虚拟机层面的东西,而是 Java 在类封装里实现的,IntegerCache 是 Integer在内部维护的一个静态内部类,用于对象缓存。
  • Integer 对象池在底层实际上就是一个变量名为 cache 的数组,里面包含了 -128 ~ 127 的 Integer 对象实例。使用对象池的方法就是通过 Integer.valueOf() 返回 cache 中的对象,像 Integer i = 10这种自动装箱实际上也是调用 Integer.valueOf() 完成的
  • 这和常量池中字面量的保存有很大区别,Integer 不需要显示地出现在代码中才添加到池中,初始化时它已经包含了所有需要缓存的对象
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/164783.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • C# CultureInfo列表详细说明

    C# CultureInfo列表详细说明””(空字符串)固定区域性 af 南非荷兰语 af-ZA 南非荷兰语(南非) sq 阿尔巴尼亚语 sq-AL 阿尔巴尼亚语(阿尔巴尼亚) ar 阿拉伯语 ar-DZ 阿拉伯语(阿尔及利亚) ar-BH 阿拉伯语(巴林) ar-EG 阿拉伯语(埃及) ar-IQ 阿拉伯语(伊拉克) 

    2022年6月19日
    25
  • spss k-means聚类分析_K均值聚类及其应用

    spss k-means聚类分析_K均值聚类及其应用SPSS聚类分析:K均值聚类分析一、概念:(分析-分类-K均值聚类)1、此过程使用可以处理大量个案的算法,根据选定的特征尝试对相对均一的个案组进行标识。不过,该算法要求您指定聚类的个数。如果知道,您可以指定初始聚类中心。您可以选择对个案分类的两种方法之一,要么迭代地更新聚类中心,要么只进行分类。可以保存聚类成员、距离信息和最终聚类中心。还可以选择指定一个变量,使用该变量的值…

    2022年10月17日
    2
  • HTTPS和HTTP的区别是什么?

    HTTPS和HTTP的区别是什么?

    2021年10月14日
    52
  • mac tensorboard

    mac tensorboard

    2021年5月27日
    108
  • 打开GTA 5竟要跑19.8亿次if语句!黑客嘲讽R星代码烂,修改后直接省70%加载时间

    打开GTA 5竟要跑19.8亿次if语句!黑客嘲讽R星代码烂,修改后直接省70%加载时间本文转载自量子位一支烟的功夫,GTA5联机版终于打开了。「7年了!GTA5联机版加载还是这么慢??」△PleasewaitforevertoplayReddit、Steam、HackerNews上,无数玩家吐槽抱怨……进游戏少则等5、6分钟,多则20分钟。终于,一个黑客大哥实在忍不了,用逆编译器逐条查看运行情况,终于找到原因。原来,R星(游戏开发商RockStar)写的代码太低效,加载时,一个if语句竟然循环了19.8亿次….幕后黑手:谁占用大量时间?加

    2022年6月12日
    32
  • ps图层面板详解_ps图层样式怎么使用

    ps图层面板详解_ps图层样式怎么使用原文:http://coolketang.com/staticPhotoshop/5a98d4acfe88c20038c5716f.html1.本节课程将为您演示,[样式面板]的使用。首先选择工具箱中的[横排文字工具],创建一个文字图层。 2.然后在图像的适当位置上点击,确定文字的起始位置。 3.接着输入一行文字。 4.点击选项栏右侧的[提交当前所有编辑]按钮,完成文字的输入。 5.在字…

    2025年9月12日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号