Java 常量池详解之字符串常量池实现代码
作者:new?hilbert() 发布时间:2022-09-09 22:12:03
在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
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(变量)
先new 了StringBuilder 对象,并初始化init
然后bulider.append(“a”)
从变量1(b)中取出值"b"
然后执行了bulider.append(“b”)
最后执行了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://blog.csdn.net/Prior_SX/article/details/123463430
猜你喜欢
- 面试题:1同步方法和同步块,哪种更好?2.如果同步块内的线程抛出异常会发生什么?1. 同步方法和同步块,哪种更好?同步块更好,这意味着同步块
- 本文实例为大家分享了Android画画板展示的具体代码,供大家参考,具体内容如下main.xml布局<RelativeLayout x
- 文件资源操作Spring 定义了一个 org.springframework.core.io.Resource 接口,Resource 接口
- Android 中RecyclerView顶部刷新实现详解1. RecyclerView顶部刷新的原理RecyclerView顶部刷新的实现
- 本文实例为大家分享了Unity3D仿写Button面板事件绑定功能的具体代码,供大家参考,具体内容如下最近在做一个情节引导得项目。其中一个需
- WebSocket介绍WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中
- 在使用JDBC的时候,数据库据连接是非常宝贵的资源。为了复用这些资源,可以将连接保存在一个队列中。当需要的时候可以从队列中取出未使用的连接。
- IDEA自定义pom依赖抽离公共代码,代码解耦,减少重复第一步: 抽离公共部分的代码第二步: 点击右侧工具栏的maven,刷新,点击skip
- 下面的方法返回false表示网络不通// 检测网络 public static boolean checkNetworkAvailable(
- 一、概述:在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何自定义一个类似热
- 如果发现maven项目里面src/main/resources下的配置文件或src/main/java下的xml配置文件未能同步至targe
- pom.xml与settings.xmlpom.xml与setting.xml,可以说是Maven中最重要的两个配置文件,决定了Maven的
- 目前在公司做一个小东西,里面用到了 FFmpeg 简单处理音视频,感觉功能特别强大,在做之前我写了一个小例子,现在记录一下分享给大家,希望大
- 第三方类库源码将一网友的XMPP代码从ADT转到AS时,发现其使用了第三方类库,源码放在了lib下,直接在AS中Import project
- 一、简介1.为了防止一个应用程序控制CPU而导致其他应用程序和操作系统本身永远被挂起这一可能情况,操作系统不得不使用某种方式将物理计算分割为
- 一、什么是外观模式定义:为子系统中的一组接口提供一个一致的界面,用来访问子系统中的一群接口。外观模式组成:Facade:负责子系统的的封装调
- WPF 窗体设置亚克力效果框架使用大于等于.NET40。Visual Studio 2022。项目使用 MIT 开源许可
- asp.net core 中已经自带了一个官方的依赖注入框架,现在想把它应用到控制台程序中,控制台程序是最简洁的代码结构,摒除了其他一堆嵌入
- Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spri
- 本文实例讲述了C#显示文件夹下所有图片文件的方法。分享给大家供大家参考。具体实现方法如下:<%@ Page Language=&quo