String对象是我们日常使用的对象类型,字符串对象或者其等价对象(如char数组),在内存中总是占据了最大的空间块,因此如何高效地处理字符串,是提高系统整体性能的关键。

在此之前,String作为一个对象类型,我们必须清楚Java对象的创建以为对象的内存结构。

创建一个对象通常需要使用new关键字,当虚拟机遇到一条new指令的时候,首先会检查这个指令的参数是在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果是则执行相应的类加载过程。

类加载检查结束之后,虚拟机将为新生对象分配内存,java中为对象分配内存有两种方式,一种是

,该方法适用于内存规整的情况,在中间放一个指针作为分界点的指示器,使用过的内存和空闲的内存各放在一边,当需要分配内存的时候只需要将指针移动即可。另一种是

,如果java堆中的内存不是规整的,虚拟机会维护一张列表,记录哪块内存可用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。采用哪种分配方式是根据java堆是否规整决定的。而java堆是否规整由JVM是否使用带有压缩整理功能的垃圾收集器决定。

另外需要考虑的是内存分配过程中线程安全的情况。有如下两种解决方案;

堆内存分配的动作做同步处理。

另一种是把内存分配的动作按照线程划分为不同的空间之中执行,即每一个线程在java堆中预先分配一小块内存(TLAB),哪个线程需要分配内存首先在TLAB上分配,如果TLAB分配完了之后,才会同步分配新的TLAB。JVM是否使用TLAB由参数

来决定。

内存分配完毕之后想,虚拟机需要分配到的内存空间初始化为零值。这一步操作保证了对象的实例字段在java代码中可以不赋初始值就可以使用,接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息等,这些信息存放在对象的对象头中。这些工作完成之后,从JVM的角度来看一个对象已经创建成功了,从java的角度来看还需要执行init方法,将对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

在HotSpot虚拟机中,对象在内存中存储的布局可分为三个部分,即对象头,实例数据和对齐填充。

对象头包括两个部分,第一部分用来存储对象自身运行时的数据,如哈希码,GC分代年龄、线程所持有的锁等,官方称为“Mark Word”。第二个部分为类型 指针,即对象指向它的类元数据的指针,虚拟机通过这个 指针来确定这个对象属于哪个类的实例。

实例数据是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。

对齐填充并不是必须的,仅仅起到占位符的作用,HotSpot虚拟机需要对象起始地址必须是8字节的整数倍,对象部分正好是8字节的整数倍,所以当实例数据部分没有对齐时,需要通过对齐填充来对齐。

对于String类型,我们首先来看看其JDK内部的成员变量的声明代码:

sss.png

我们会看到它内部维护着一个char数组,而且它是由final关键词修饰的,说明它一旦创建之后不可变。对于String的创建,比较特殊一些,我们来看一下它的具体创建原理:

不管使用任何方式来创建一个字符串S的时候,Java运行时会拿着这个S字符串在String池中查找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串S,否则不会创建对象,也不会在池中添加。

前面提到使用new关键创建对象,那么肯定会在堆栈创建一个新的对象,String也是一样的。

使用直接指定或者使用纯字符串拼接来创建String对象,则仅仅会检查String池中的字符串,池中没有就创建一个,如果存在,就不需要创建新的,但是绝对不会在堆栈区再去创建对象。

使用包含变量的表达式来创建String对象时,则不仅会检查并维护Sting池,而且还会在堆栈区创建一个新的String对象。

最常见的String操作莫过于拼接字符串了,在拼接字符串时,我们尽量用+,因为通常编译器会做出优化,如String test="hello "+"world",编译器会将其视为String test="hello world"。所以在拼接国泰字符串时,我们需要尽量使用StringBuffer或者StringBuilder的append方法,这样可以减少构造过多的临时String对象。下面我们来看一个简单的实例来证实:

ddd.png

在String对象中有一个特殊的方法,它是一个本地方法,当调用该方法时,如果池中已经包含了一个等于此String对象的字符串,则返回池中的字符串,否则,将此对象添加到池中,并且返回String对象的引用。

在上面的一个例子中,str1和str4并不是同一个对象引用,因此不相等,那么我们使用intern方法,添加一句,观察运行结果:

sdsds.png

也许很多人想到我们可以使用intern方法来创建对象,避免使用new创建大量的对象,但是这也有一个隐含的问题。

使用String的

方法返回JVM对字符串缓存池里已经存在的字符串引用,从而解决内存性能问题,但是intern方法使用的池是JVM全局的池,很多情况下我们的程序并不需要如此大作用域的缓存,而且,它所使用的是JVM heap中PermGen对应的区域,PermGen通常是用来存放装载类和创建类实例时用到的元数据,因此,使用过多的intern方法会导致PermGen过度增长而最后返回OOM,因此垃圾收集器不会对缓存的String做垃圾回收,因此不建议使用。

实际中,如果需要创建大量的字符串,我们可以自己构建缓存,比如使用HashMap,将需缓存的String作为key和value放在HashMap中,例如下面代码:

public String getCacheString(String key){

String temp=cacheMap.get(key);

if(temp!=null){

return temp;

}else{

cacheMap.put(key,key);

return key;

}

}

在字符串的使用中,另一个常见的操作是截取字符串,在String内部提供了

方法供我们使用,其源码如下(1.8版本):

41.png

42.png

43.png

从上面的源码可以看出,substring方法截取字符串的时候,会将String的原生内容复制到新的子字符串中,从整个方法的调用链来看,它会保存原始String。因此这也引发了下面的问题。

在一个大字符串中我们需要截取的字符串远远小于其原始字符串的长度时,不建议直接使用substring方法截取后直接返回,这样会造成内存泄漏,我们可以使用new String的方式来创建一个个字符串对象,将垃圾回收交给JVM GC,避免内存泄漏问题。

当在一个大字符串中我们需要截取的字符串几乎和原始字符串长度相等的时候,我们可以放心的使用substring方法来截取返回。

所幸的是,在JDK1.7之后的版本中,将substring的内部实现修改为使用Arrays进行拷贝,不再复用之前的原字符串,因此使其得以回收,所以String内存泄漏的问题也得到了修复。

如果使用了1.7之前的API,也可以使用下面的方法来解决内存泄漏问题。

看一个用例:

public class TestSubString {

public static void main(String[] args) {

List list=new ArrayList();

for(int i=0;i<1000;i++){

SubString1 str1=new SubString1();

SubString2 str2=new SubString2();

list.add(str1.getSubString(1,6));

list.add(str2.getSubString(1,6));

}

}

public static class SubString1{

public String str=new String(new char[10000000]);

public String getSubString(int begin,int end){

return new String(str.substring(begin, end)); //使用new重新创建字符串

}

}

public static class SubString2{

public String str=new String(new char[10000000]);

public String getSubString(int begin,int end){

return str.substring(begin, end); //直接截取返回

}

}

}

在这个用例中,原始字符串很大,但是需要截取的却是很小的一段,因此在这种场景下推荐使用SubString1重新new一个字符串来释放原始字符串的方式来截取字符串,这样避免了原始字符串不能被回收,存在内存泄漏的问题

java 中的string_Java中String揭秘相关推荐

  1. java中private string_java 中private static final String string的介绍final的意思

    java 中private static final String string的介绍final的意思: final的意思是这个string类型的东西不能再被改变.也可以说是常量 static说明这个 ...

  2. 【基础巩固篇】Java中String揭秘!

    作者:不清不慎,Java大数据开发工程师一枚,热爱研究开源技术! 架构师社区合伙人! String对象是我们日常使用的对象类型,字符串对象或者其等价对象(如char数组),在内存中总是占据了最大的空间 ...

  3. java 使用string_java中string怎么使用

    展开全部 以下是关于string的七种用法,注意哦,记得要时常去查看java的API文档,那个里面也有很详细32313133353236313431303231363533e78988e69d8331 ...

  4. java string 占位符_驳《阿里「Java开发手册」中的1个bug》?

    前两天写了一篇关于<阿里Java开发手册中的 1 个bug>的文章,评论区有点炸锅了,基本分为两派,支持老王的和质疑老王的. 首先来说,无论是那一方,我都真诚的感谢你们.特别是「二师兄」, ...

  5. Java中是否可以继承String类,为什么

    详细解析: 1. 关于final修饰符 参考文章:  浅析Java中的final关键字 根据程序上下文环境,Java关键字final有"这是无法改变的"或者"终态的&qu ...

  6. Java提升篇:理解String 及 String.intern() 在实际中的应用

    String的深入解析 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些 ...

  7. Java 8中的StringJoiner与String.join的示例

    将多个String文字或对象合并为一个是常见的编程要求,并且经常会发现需要为应用程序将String列表或String集合转换为CSV String的情况. 长期以来,JDK API无法将多个Strin ...

  8. java字符串剪切函数,java用substring函数截取string中一段字符串,substringstring

    java用substring函数截取string中一段字符串,substringstring from  http://www.cnblogs.com/laiweili/archive/2012/11 ...

  9. java stringbuffer数组_Java中字符数组、String类、StringBuffer三者之间相互转换

    一.StringBuffer与String的相互转换 1.将StringBuffer转换成String StringBuffer类成员toString函数可将其转换成String类型. StringB ...

最新文章

  1. Android 自定义 —— View lineTo 与 rLineTo 的区别
  2. JAVA中“==”与equals()方法区别
  3. seg显示时间——51程序
  4. html to docx
  5. Cisco IOS Cookbook 中文精简版(续)
  6. 【区块链】以太坊truffle+web3+ganache简单实践
  7. 软件评测师教程书本对软考的作用有多大?
  8. 2019最新盘点:适合中小型企业的财务系统软件
  9. android 将鼠标右键点击事件改为点击后返回功能
  10. 二、SQL Server 2008 登录
  11. 人人网首页登录页面html码,人人网的登录页面 - BryanYang的个人空间 - OSCHINA - 中文开源技术交流社区...
  12. 梁宁:增长思维30讲脑图笔记
  13. python3.6从入门到精通mobi_Python 3.6零基础入门与实战epub
  14. 苹果IOS14版本自建服务器无法下载解决方法
  15. 写给男人的十句悄悄话,想知道女人到底想要什么样的男人吗,那么请详读下文——
  16. 深圳六月有哪些公园能赏花 赏花打卡点推荐
  17. 前端面试官常问的问题有哪些?(前端面试题总结)
  18. 下一代智能扫码报修系统介绍
  19. 全球及中国呼叫中心服务行业运营模式及发展战略分析报告2022-2028年
  20. 千锋教育python2104期总结day14

热门文章

  1. QList、QVector、QMap容器类
  2. 莫代尔是什么?它有哪些优缺点?
  3. QString转HTuple
  4. excel文本方式区学习笔记
  5. 线段树(Segment Tree)
  6. iframe 父级元素查找
  7. linux 软raid配置-系统安装
  8. 8.3实例程序:平面阴影
  9. 改善C#程序的建议2:C#中dynamic的正确用法
  10. c语言的返回类型是指针变量吗,C语言-指针类型