String内存陷阱简介
String 方法用于文本分析及大量字符串处理时会对内存性能造成一些影响。可能导致内存占用太大甚至OOM。
一、先介绍一下String对象的内存占用
一般而言,Java 对象在虚拟机的结构如下:
•对象头(object header):8 个字节(保存对象的 class 信息、ID、在虚拟机中的状态)
•Java
原始类型数据:如 int, float, char 等类型的数据
•引用(reference):4 个字节
•填充符(padding)
String定义:
JDK6:
private
final char value[];
private final int offset;
private final int
count;
private int hash;
JDK6的空字符串所占的空间为40字节
JDK7:
private
final char value[];
private int hash;
private transient int hash32;
JDK7的空字符串所占的空间也是40字节
JDK6字符串内存占用的计算方式:
首先计算一个空的
char 数组所占空间,在 Java 里数组也是对象,因而数组也有对象头,故一个数组所占的空间为对象头所占的空间加上数组长度,即 8 + 4 = 12 字节 ,
经过填充后为 16 字节。
那么一个空
String 所占空间为:
对象头(8
字节)+ char 数组(16 字节)+ 3 个 int(3 × 4 = 12 字节)+1 个 char 数组的引用 (4 字节 ) = 40 字节。
因此一个实际的
String 所占空间的计算公式如下:
8*(
( 8+12+2*n+4+12)+7 ) / 8 = 8*(int) ( ( ( (n) *2 )+43) /8 )
其中,n
为字符串长度。
二、举个例子:
1、substring
package demo; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; public class TestBigString { private String strsub; private String strempty = new String(); public static void main(String[] args) throws Exception { TestBigString obj = new TestBigString(); obj.strsub = obj.readString().substring(0,1); Thread.sleep(30*60*1000); } private String readString() throws Exception { BufferedReader bis = null; try { bis = new BufferedReader(new InputStreamReader(new FileInputStream(newFile("d:\\teststring.txt")))); StringBuilder sb = new StringBuilder(); String line = null; while((line = bis.readLine()) != null) { sb.append(line); } System.out.println(sb.length()); return sb.toString(); } finally { if (bis != null) { bis.close(); } } } }
其中文件"d:\\teststring.txt"里面有33475740个字符,文件大小有35M。
用JDK6来运行上面的代码,可以看到strsub只是substring(0,1)只取一个,count确实只有1,但其占用的内存却高达接近67M。
然而用JDK7运行同样的上面的代码,strsub对象却只有40字节
什么原因呢?
来看下JDK的源码:
JDK6:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); } // Package private constructor which shares value array for speed. String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
JDK7:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); } public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); }
可以看到原来是因为JDK6的String.substring()所返回的 String 仍然会保存原始 String的引用,所以原始String无法被释放掉,因而导致了出乎意料的大量的内存消耗。
JDK6这样设计的目的其实也是为了节约内存,因为这些 String 都复用了原始 String,只是通过 int 类型的 offerset, count 等值来标识substring后的新String。
然而对于上面的例子,从一个巨大的 String 截取少数 String 为以后所用,这样的设计则造成大量冗余数据。 因此有关通过 String.split()或 String.substring()截取 String 的操作的结论如下:
•对于从大文本中截取少量字符串的应用,String.substring()将会导致内存的过度浪费。
•对于从一般文本中截取一定数量的字符串,截取的字符串长度总和与原始文本长度相差不大,现有的
String.substring()设计恰好可以共享原始文本从而达到节省内存的目的。
既然导致大量内存占用的根源是
String.substring()返回结果中包含大量原始 String,那么一个减少内存浪费的的途径就是去除这些原始 String。如再次调用
newString构造一个的仅包含截取出的字符串的 String,可调用 String.toCharArray()方法:
String
newString = new String(smallString.toCharArray());
2、同样,再看看split方法
public class TestBigString { private String strsub; private String strempty = new String(); private String[] strSplit; public static void main(String[] args) throws Exception { TestBigString obj = new TestBigString(); obj.strsub = obj.readString().substring(0,1); obj.strSplit = obj.readString().split("Address:",5); Thread.sleep(30*60*1000); }
JDK6中分割的字符串数组中,每个String元素占用的内存都是原始字符串的内存大小(67M):
而JDK7中分割的字符串数组中,每个String元素都是实际的内存大小:
原因:
JDK6源代码:
public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this, limit); } public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList<String> matchList = new ArrayList<String>(); Matcher m = matcher(input); // Add segments before each match found while(m.find()) { if (!matchLimited || matchList.size() < limit - 1) { String match = input.subSequence(index, m.start()).toString(); matchList.add(match); public CharSequence subSequence(int beginIndex, int endIndex) { return this.substring(beginIndex, endIndex); }
三、其他方面:
1、String a1 = “Hello”; //常量字符串,JVM默认都已经intern到常量池了。
创建字符串时 JVM 会查看内部的缓存池是否已有相同的字符串存在:如果有,则不再使用构造函数构造一个新的字符串,
直接返回已有的字符串实例;若不存在,则分配新的内存给新创建的字符串。
String a2 = new String(“Hello”); //每次都创建全新的字符串
2、在拼接静态字符串时,尽量用 +,因为通常编译器会对此做优化。
public String constractStr() { return "str1" + "str2" + "str3"; }
对应的字节码:
Code:
0: ldc #24; //String str1str2str3 --将字符串常量压入栈顶
2: areturn
3、在拼接动态字符串时,尽量用 StringBuffer 或 StringBuilder的 append,这样可以减少构造过多的临时 String 对象(javac编译器会对String连接做自动优化):
public String constractStr(String str1, String str2, String str3) { return str1 + str2 + str3; }
对应字节码(JDK1.5之后转换为调用StringBuilder.append方法):
Code:
0: new #24; //class java/lang/StringBuilder 3: dup 4: aload_1 5: invokestatic #26; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 8: invokespecial #32; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 11: aload_2 12: invokevirtual #35; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_3 16: invokevirtual #35; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; ――调用StringBuilder的append方法 19: invokevirtual #39; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: areturn ――返回引用
String内存陷阱简介的更多相关文章
- iOS: ARC & MRC下string内存管理策略探究
ARC & MRC下string内存管理策略探究 前两天跟同事争论一个关于NSString执行copy操作以后是否会发生变化,两个人整了半天,最后写代码验证了一下,发现原来NSString操作 ...
- jvm-垃圾回收gc简介+jvm内存模型简介
gc是jvm自动执行的,自动清除jvm内存垃圾,无须人为干涉,虽然方便了程序员的开发,但同时增加了开发人员对内存的不可控性. 1.jvm内存模型简介 jvm是在计算机系统上又虚拟出来的一个伪计算机系统 ...
- String内存分析,for-each,参数传递
上午总结: 蓝白书P245 (一)Iterator用法 import java.util.*; public class HashSetTest{ public static void main(St ...
- Java String内存释放
Java String内存释放 这是一个坑,Java对于String对象,不进行内存的回收: 处理大数据量的时候,少用String. 与JDK有关系:jdk1.6环境下,内存只占用10M,jdk1.8 ...
- Android-ION内存管理简介
ION内存管理简介 https://www.jianshu.com/p/4f681f6ddc3b http://kernel.meizu.com/memory%20management%20-%20i ...
- C# 由范式编程==运算符引发对string内存分配的思考
今天在看C#编程指南时(类型参数的约束http://msdn.microsoft.com/zh-cn/library/d5x73970.aspx)看到一段描述: 在应用 where T : class ...
- java中string内存的相关知识点
(一):区别java内存中堆和栈: 1.栈:数据可以共享,存放基本数据类型和对象的引用,其中对象存放在堆中,对象的引用存放在栈中: 当在一段代码块定义一个变量时,就在栈中 为这个变量分配内存空间,当该 ...
- [Java] 01 String 内存分析
public class StringTest{ public static void main(String[] args){ String str1 = new String("123& ...
- string内存管理
本人从事.net开发快两年了,一直认为鄙人的C++基础还是很扎实的,并且对Windows操作系统也有一定认识(Linux系就真比较少用),刚毕业的时候,也曾经经常研究游戏破解之类的小外挂,那时候真是折 ...
随机推荐
- 10.cadence.自定义焊盘的创建[原创]
一.自定义图形焊盘 1.设置环境(面板大小,格点) --- ------ 圆形 Shape > Circular ---- 两个DRC错误,证明图形重合了, 将图形复合一下: --- 椭圆类焊盘 ...
- 使用 httpkit 来替代 jetty
Compojure 是一个基于 ring 的上层web开发框架.在 lein new compojure my-app 生成的项目中,默认是启用 jetty 服务器的,最近用到了 http-kit 中 ...
- List<T> please check srcIndex
这种错误的原因,是因为List<T>不是线程安全的.
- UVa 644 Immediate Decodability
吐槽下我的渣渣英语啊,即使叫谷歌翻译也没有看懂,最后还是自己读了好几遍题才读懂. 题目大意:题意很简单,就是给一些互不相同的由'0','1'组成的字符串,看看有没有一个字符串是否会成为另一个的开头的子 ...
- HDU 3448 Bag Problem
这是一道搜索的背包题目 题意: 有n件物品从中最多选m件,使其总重量不超过v,求能获得的最大重量 有一个很重要的剪枝(是数据的问题还是这个剪枝本身很高效?): 如果重量最大m件物品都不超过v,则答案就 ...
- 二、CSS 基本介绍
[ 显示目录 ] [ 隐藏 ] 目录 基本概念 CSS组成部分 CSS的规则 引入CSS样式的方法 颜色的表示 CSS Reset 选择器分类 浮动 盒子模型 box-sizing属性 实例:实现“田 ...
- MIPI DSI 和 D-PHY 初始化序列
MIPI DSI 和 D-PHY 初始化序列 -- 深圳 南山平山村 曾剑锋 参考文档: i.MX 6Dual/6Quad Multimedia Applications Processor Refe ...
- HDU 1496 Equations 等式(二分+暴力,技巧)
题意:给出4个数字a,b,c,d,求出满足算式a*x1^2+b*x2^2+c*x3^2+d*x4^2=0的 (x1,x2,x3,x4) 的组合数.x的范围[-100,100],四个数字的范围 [-50 ...
- 【JavaScript学习笔记】if使用
<html> <body> <script language="JavaScript"> var a=4; var b=2; if(a==3) ...
- scala学习笔记(7):函数(1)
函数是Scala的第一公民! 1 基本定义 scala> def max(x: Int, y: Int): Int = { if (x > y) x else y } 跟着是括号里带有冒 ...