JVM(七)字符串详解
常量池:
我们前面也一直说常量池有三种:
1:class文件中的常量池,前面我们解析class文件的时候解析的就是,这是静态常量池。在硬盘上。
2:运行时常量池。可以通过HSDB查看,是InstanceKlass的一个属性:ConstantPool *_constants。在方法区或者说在元空间中(JDK1.8+)
可以通过HSDB查看,HSDB的使用可以看JVM第一篇中的介绍。
3:字符串常量池。底层是String Pool--StringTable--HashTable。在堆区。
注意:并不是所有的字符串都会在字符串常量池里。
String是怎么存储的?
在java中我们的String对象存储的字符串都是在其内部的一个char数组上的。
我们看到两个不同的变量,以不同的创建方式创建,字符串一样,但是字符串变量里的value数组属性地址竟然是一样的? 是不是很神奇。这就牵涉到JVM里面
是怎么存储字符串的问题了。还有就是两个变量的hashcode值也是一样的,这是因为String重写了hashcode方法,hash值只和字符串的内容也就是value有关,所以是一样的。
JVM中的String是怎么存储的呢?
在JVM中,使用StringTable来存储String的当然也有些不是通过StringTable存储的,这个后面说明。StringTable继承HashTable,也就是字符串在JVM中是key-value形式存储的。数据结构也就是数组+链表。
在openJDK中的symbolTable.cpp中如下方法:
key:
是通过1中的方式生成的。1)根据字符串以及字符串的长度计算出hashvalue.2)根据hashvalue计算出index,这个index就是key。也就是数组的下标,在这里称为bucket(桶)默认桶的数量为60013个。
可以通过-XX:StringTableSize=2000参数来调整桶的大小。
value:
key计算出了bucket的位置,value的值就是2中生成的 HashtableEntry<oop, mtSymbol>* entry = new_entry(hashValue, string());
它是将Java中String类的实例instanceOopDesc封装成了HashtableEntry,再存储起来的。
这里补充下,在第一篇JVM中已经提到了oop-klass体系。这里再说明下:
Oop: java中对象在JVM中的存在形式。klass是java中的类在JVM中存在的形式。
通过idea我们可以看到在创建String过程中都创建了些什么内容
实例
我们从idea中接着看上面的例子。看下创建字符串过程中到底创建了那些内容。
public static void main(String[] args) {
test1();
}
public static void test1(){
String s1="1";
String s3="1";
String s2=new String("1");
System.out.println(s1==s3);
System.out.println(s1==s2); }
以Debug的方式调试,在控制台最右上角有个Memory View,可以实时看到每一步创建了那些对象,创建了几个。
第一行执行完:我们看到char[] ,String各新增了一个。
第二行执行完:char[],String一个都没新增,很神奇吧,别慌,执行完。
第三行执行完:只新增了一个String。
两个比较结果:s1和s3的地址是一样的。s2是不同的地址。
为什么会出现上面看到的结果呢?关键看下图:
1)如果是一个char[]数组类型数据 ,它的对象在JVM中是typeArrayOopDesc形式的。
2:直接双引号创建一个字符串:按照上面说的,字面字符串会在堆里有一个String对象,String对象里有一个char[]数组对象,把String对象对应的instanceOopDesc封装成HashTableEntry然后把HashTableEntry放入常量池中。s1只是引用这个String对象。
3:两个双引号:当s2创建“11”字面字符串时,会首先判断常量池是否有这个字符串如果有的话会直接返回这个字符串的instanceOopDesc。所以s1,s2指向的是同一个块地址。
如果没有的话会创建一个像2中的那样。
4:通过 new String 创建字符串:new 操作会在堆里创建一个String对象,这个String对象的char数组还是指向typeArrayOopDesc,如果字符串常量池中已经存在了当前字符串,
还是会指向已经存在的地址。
因此可以看到上面举得例子,s1,s2,s3变量中的char数组的内存地址都是一样的!!
5:创建两个new String方式的字符串,常量池中还是只有一个,但是两个s1,s2地址是不一样的,但是其下的char数组还是会指向同一个typeArrayOopDesc。
字符串拼接
public static void test1(){
String s1="1";
String s2="2";
String s3=new String("3");
String s6="12";
String s7="13";
String s4=s1+s2;
String s5=s1+s3;
}
我们首先看下字符串拼接底层是怎样实现的。通过 javap -c TestString.class 可以查看字节码指令。或者直接通过idea查看.class文件
我们看到String s4=s1+s2; String s5=s1+s3; 底层都是通过StringBuilder#append来拼接之后再toString得到的。但是不仅仅只有这一点区别!
我们继续看StringBuilder#toString方法。发现是调用了 new String(value, 0, count); 的构造方法。
我们通过Debug看下,通过拼接得到的字符串有什么不一样的地方?
首先看下 String s6="12"; String s4=s1+s2; 的区别,s1+s2得到的字符串也是"12", 这里char数组地址竟然不一样了!!!
我们上面知道常量池中如果已经有了这个字符串,下面创建同样的字符串的时候都是从常量池中获取,char数组的地址都是一样的。这里竟然不一样了!
这就是拼接字符串的不同之处,拼接出来的字符串并没有从常量池中获取,创建出来的字符串也不会放入字符串常量池中,s6是常量池中的字符串,s4里面的char数组就是普通的堆里面的数组。s5拼接的字符串也是这样的。
我们这里把这个这个构造函数和常量字符串构建单独拉出来看下。
第一行执行完:String,char[] 各新增一个。
第二行执行完:新增了两个String,一个char[]数组 ,而且看到s1,s3字符串虽然一样的,但是char[] 却不再一样了。这也就是s1字符串并不在常量池中,s3会把字符串放入常量池中。
虽然String s=s1+s2这种拼接的字符串并不会放到字符串常量池中,但是我们可以调用String#intern方法把当前的字符串主动放入字符串常量池中。
我们还是以上面这个例子,加一行代码:
第一行执行完结果:
第二行执行完结果:没有什么明显的结果
第三行执行完: 只新增了一个String对象,而且char[] 数组地址是一样的。这是因为s1.intern()方法,把s1的字符串放入常量池中了,s3创建的时候,只是在堆里再创建一个新的String对象就可以了,这个在上面的图解中也说明了。
还有一种特殊情况我们来看下,有final修饰符修饰的字符串之间的拼接情况:
public static void test3(){
final String s1="1";
final String s2="2";
String s3=s1+s2;
String s4="12";
System.out.println(s3==s4);
}
因为s1,s2是final修饰的,在编译阶段就放入了字节码的常量池中,s3其实在编译阶段已经指向了常量池中的"12"了。
我们通过编译后的class也可以看到:所以比较肯定是true。
// 只会创建一个String 一个char[],编译的时候就优化成"帅帅"
String s="帅"+"帅";
// 三个String,三个char数组对象,
String s2 = "帅" + new String("真帅");
JVM(七)字符串详解的更多相关文章
- SQL Server日期时间格式转换字符串详解
本文我们主要介绍了SQL Server日期时间格式转换字符串的相关知识,并给出了大量实例对其各个参数进行对比说明,希望能够对您有所帮助. 在SQL Server数据库中,SQL Server日期时间格 ...
- Swift_字符串详解(String)
Swift_字符串详解(String) 类型别名 //类型别名 fileprivate func testTypeAliases() { let index = String.Index.self p ...
- MS SQL Server 数据库连接字符串详解
MS SQL Server 数据库连接字符串详解 原地址:http://blog.csdn.net/jhhja/article/details/6096565 问题 : 超时时间已到.在从池中获取连接 ...
- JVM类加载机制详解(二)类加载器与双亲委派模型
在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有: 1.通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件).而获取的方式,可 ...
- JVM类加载机制详解
引言 如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 在加载阶段,虚拟机需要完成以下三件事情: 1)通过一个类的全限定名来获取定义此 ...
- Python变量和字符串详解
Python变量和字符串详解 几个月前,我开始学习个人形象管理,从发型.妆容.服饰到仪表仪态,都开始做全新改造,在塑造个人风格时,最基础的是先了解自己属于哪种风格,然后找到参考对象去模仿,可以是自己欣 ...
- C语言中字符串详解
C语言中字符串详解 字符串时是C语言中非常重要的部分,我们从字符串的性质和字符串的创建.程序中字符串的输入输出和字符串的操作来对字符串进行详细的解析. 什么是字符串? C语言本身没有内置的字符串类型, ...
- JVM运行原理详解
1.JVM简析: 作为一名Java使用者,掌握JVM的体系结构也是很有必要的. 说起Java,我们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Ja ...
- JVM 内存溢出详解(栈溢出,堆溢出,持久代溢出、无法创建本地线程)
出处: http://www.jianshu.com/p/cd705f88cf2a 1.内存溢出和内存泄漏的区别 内存溢出 (Out Of Memory):是指程序在申请内存时,没有足够的内存空间供 ...
随机推荐
- 5分钟完全掌握Python协程
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 1. 协程相关的概念 1.1 进程和线程 进程(Process)是应用程序启动的实例,拥有代码.数据 ...
- HCIP -- OSPF 总结
OSPF:Open Shortest path First :开方式最短路径优先 一.基础知识: 1.使用范围:IGP 2.协议算法特点:链路状态型路由协议,SPF算法 3.协议是否传递网络掩码:是 ...
- MySQL:判断逗号分隔的字符串中是否包含某个字符串 && 如何在一个以逗号分隔的列表中的一个字段中连接MySQL中的多对多关系中的数据
需求: sql语句中,判断以逗号分隔的字符串中是否包含某个特定字符串,类似于判断一个数组中是否包含某一个元素, 例如:判断 'a,b,c,d,e,f,g' 中是否包含 'a',sql语句如何 ...
- 十大经典排序算法最强总结(含Java、Python码实现)
引言 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法.排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面 ...
- HashMap的循环姿势你真的掌握了吗?
hashMap 应该是java程序员工作中用的比较多的一个键值对处理的数据的类型了.这种数据类型一般都会有增删查的方法,今天我们就来看看它的循环方法以前写过一篇关于ArrayList的循环效率问题&l ...
- BOM主数据-用ECN实现可变BOM
用ECN变更号实现可变BOM:通过ECN变更号的参数类型来实现BOM的可变配置. 物料编号:2104 (1)首先BOM的父项物料主数据<基本数据1>必须设置栏位"参数有效值&qu ...
- Android驱动学习-APP操作新硬件的两种方法(支持添加的驱动)
在给Android添加新的驱动后,app要如何使用呢? 正常的使用一个设备,需要getService.但是像LED等我们自己添加的硬件驱动,Android源代码根本没有我们自己添加的服务. 第一种: ...
- git基础-远程仓库的使用
远程仓库的使用 为了能在任意 Git 项目上协作,你需要知道如何管理自己的远程仓库. 远程仓库是指托管在因特网或其他网络中的你的项目的版本库. 你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以 ...
- 项目实战--Stream流实现字符串拼接
需求说明 概述:前端页面查询列表中有个"二级类目"的多选下拉框,用户选择二级类目后,需要从后台数据库查询条件内的数据. 目标:将前端页面传入后端的字符串例如"女性护理, ...
- 《Spring Boot 实战纪实》之前言
目录 前言 (思维篇)人人都是产品经理 1.需求文档 1.1 需求管理 1.2 如何攥写需求文档 1.3 需求关键点文档 2 原型设计 2.1 缺失的逻辑 2.2 让想法跃然纸上 3 开发设计文档 3 ...