作者:不清不慎,Java大数据开发工程师一枚,热爱研究开源技术! 架构师社区合伙人!


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

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

1.对象的创建以及内存结构

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

类加载检查结束之后,虚拟机将为新生对象分配内存,java中为对象分配内存有两种方式,一种是指针碰撞,该方法适用于内存规整的情况,在中间放一个指针作为分界点的指示器,使用过的内存和空闲的内存各放在一边,当需要分配内存的时候只需要将指针移动即可。另一种是空闲列表,如果java堆中的内存不是规整的,虚拟机会维护一张列表,记录哪块内存可用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。采用哪种分配方式是根据java堆是否规整决定的。而java堆是否规整由JVM是否使用带有压缩整理功能的垃圾收集器决定。

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

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

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

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

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

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

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

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

2.String揭秘

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

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

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

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

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

最常见的String操作莫过于拼接字符串了,在拼接字符串时,我们尽量用+,因为通常编译器会做出优化,如String test="hello "+"world",编译器会将其视为String test="hello world"。所以在拼接国泰字符串时,我们需要尽量使用StringBuffer或者StringBuilder的append方法,这样可以减少构造过多的临时String对象。下面我们来看一个简单的实例来证实:在String对象中有一个特殊的方法intern(),它是一个本地方法,当调用该方法时,如果池中已经包含了一个等于此String对象的字符串,则返回池中的字符串,否则,将此对象添加到池中,并且返回String对象的引用。

在上面的一个例子中,str1和str4并不是同一个对象引用,因此不相等,那么我们使用intern方法,添加一句,观察运行结果:也许很多人想到我们可以使用intern方法来创建对象,避免使用new创建大量的对象,但是这也有一个隐含的问题。

使用String的intern()方法返回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内部提供了substring方法供我们使用,其源码如下(1.8版本):从上面的源码可以看出,substring方法截取字符串的时候,会将String的原生内容复制到新的子字符串中,从整个方法的调用链来看,它会保存原始String。因此这也引发了下面的问题。

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

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

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

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

看一个用例:

public class TestSubString {    public static void main(String[] args) {        List<String> list=new ArrayList<String>();        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揭秘!相关推荐

  1. Java基础知识(JAVA中String、StringBuffer、StringBuilder类的区别)

    java中String.StringBuffer.StringBuilder是编程中经常使用的字符串类,他们之间的区别也是经常在面试中会问到的问题.现在总结一下,看看他们的不同与相同. 1.可变与不可 ...

  2. java里面string什么意思_「Java基础知识」Java中的字符串是什么

    原标题:「Java基础知识」Java中的字符串是什么 字符串顾名思义就是一些字符组合在一起组成的一串数据,称作字符串,在Java中字符串用双引号包围起来,格式为String string = &quo ...

  3. 面试必考之Java中String是基础类型?是包装类型?

    我们都知道,Java中String不属于基础数据类型.基础类型只有8中基本数据类型:byte.short.int.long.float.double.char.boolean,而String是最常用到 ...

  4. java中String new和直接赋值的区别

        Java中String new和直接赋值的区别     对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才 ...

  5. java object数组转实体类_详解Java中String JSONObject JSONArray List实体类转换

    JSON使用阿里的fastJson为依赖包 gradle依赖管理如下: compile group: "com.alibaba", name: "fastjson&quo ...

  6. Java中String字符串截取几种方法(substring,split)

    Java中String字符串截取几种方法 substring,split 这是一个Java中的String的基础用法的演示. 下面通过代码对大家进行讲解 substring 这里用来ndexOf,la ...

  7. java string设置编码_详解Java中String类型与默认字符编码

    为什么写这个 至于为什么要写这个,主要是一句mmp一定要讲,绕了一上午,晕死 Java程序中的中文乱码问题一直是一个困扰程序员的难题,自己也不例外,早在做项目时就遇到过很多编码方式的坑,当时想填来着, ...

  8. 【翻译】Java中String, StringBuffer, StringBuilder的区别

    2019独角兽企业重金招聘Python工程师标准>>> String 是  Java 中最重要的类之一,并且任何刚开始做Java编程的人,都会 用String定义一些内容,然后通过著 ...

  9. java中String的常用方法

    java中String的常用方法 1.length() 字符串的长度 例:char chars[]={'a','b'.'c'}; String s=new String(chars); int len ...

最新文章

  1. rm 空间不释放_面试官跟我说rm删除文件之后,空间就被释放了吗?我该怎么答...
  2. nginx 及 php 配置
  3. python有限循环_Python循环
  4. SDNU 1416.一元三次方程求解(数学)
  5. android 调出键盘表情_Android 软键盘和emoji表情切换方案,和微信几乎一样的体验...
  6. 51nod1766-树上的最远点对【结论,线段树】
  7. 到底什么是分布式系统
  8. 用于SaaS和NoSQL的Jdbi
  9. 统计建模与r软件_【统计建模与R软件笔记】008 描述统计量(1)
  10. linux usb驱动u盘启动不了,Linux环境下USB的原理、驱动和配置(4)
  11. 品质和创新成为 GDC 2017 “State of Unreal” 的焦点
  12. 高甜预警|甜齁你的情人节促销海报设计模板
  13. 使用Python代码处理Excel
  14. Csdn账号如何注销?
  15. 安卓webview开发简介
  16. 供应链三道防线(读书笔记)4(共4)
  17. 数仓建模—ID Mapping(下)
  18. 堆的进化之旅5-Relaxed Heap松弛堆
  19. 多宽带联网(五) 仅使用Windows进行多宽带叠加
  20. 手机测试中的ICCID是什么?

热门文章

  1. 关于命令行窗口输入pip list出错的一些解决办法
  2. 图论 ---- F. The Shortest Statement (最短路的性质 + 任意两点间最短路 + 图转树)
  3. Discovering Gold LightOJ - 1030[概率dp或者记忆化搜索]
  4. jsp与java_JSP与JavaBeans
  5. C++ queue 详细介绍
  6. 【构造】CF12E Start of the season(神奇的构造)难度⭐⭐⭐
  7. hbuildx打包成apk_HBuilder打包webapp为apk的方法
  8. Linux下如何同时注释多行/同时取消多行注释
  9. 找出1个小时前更新的文件并进行拷贝
  10. 从一道题浅说 JavaScript 的事件循环