你真的了解java.lang.String吗?
一直觉得自己的Java基础技术还算可以,自从看了一些大牛的博客后,发现自己对Java的理解还只是皮毛。之前看过JVM的一些知识以及java.lang.String的源码,以为自己对它很了解,但真的是这样吗?在这之前我对字符串的认识一直停留在很基础的认识上:
1、String对象是不可变的,一旦创建内容将不可能被修改。
2、JVM中存在一个字符串常量池,String.intern()方法可以把运行时产生的字符串放入常量池中。
3、对于以下代码有:s1和s2指向同一个对象,s3和s1指向不同的对象但值相等。
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = new String("Hello World");
Java字符串真的是不可变的吗?
今天在stackoverflow上看到这样的一个问题,Java字符串真的是不可变的吗?并贴出了一段修改字符串的代码。
String s1 = "Hello World";
String s2 = "Hello World";
String s3 = s1.substring(6);
System.out.println(s1); // Hello World
System.out.println(s2); // Hello World
System.out.println(s3); // World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] = 'J';
value[7] = 'a';
value[8] = 'v';
value[9] = 'a';
value[10] = '!';
System.out.println(s1); // Hello Java!
System.out.println(s2); // Hello Java!
System.out.println(s3); // World
看到代码后,我恍然大悟。虽然看过String的源码,知道String的内部是一个final的char数组,也知道final数组的值可以修改,可以通过反射机制去修改实例的一些变量值,但从来没有想到过字符串的值可以通过这种方式修改。
接着看了下面的一些回答,了解到最后一行代码System.out.println(s3); // World在java 7u6版本之前会输出Java!对比了一下java 6u45和java 7u45的String的源码,发现在java 6u45版本中substring产生的新字符串和原字符串共用的一个char数组,只是构造字符串时修改了offset和count的值。而在java 7u45的版本的源码中并不存在offset和count这两个常量,在substring的时候是从原char数组中拷贝出来了this.value = Arrays.copyOfRange(value, offset, offset+count)。这样的话在java 7u6之前,调用字符串的substring的时候,产生的字符串和原字符串实际是共用了同一个char数组,可以节约内存空间。
下图是java 7u6之前版本中s1、s2、s3字符串在JVM的存储结构。所以通过反射直接修改char数组的值是可以修改字符串的。
对问题的回答:String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。这个问题也揭示了为什么反射技术在某些场景下非常实用,但在大多数情况下,你应该避免使用它。
那么在java 7u6版本为什么要做这样的修改呢?
虽然共用一个char数组可以节约内存空间,但这样会带来内存泄漏的问题。假如你有一个很长的母字符串,使用substring想从其中得到一个有用的子字符串,然后你只使用这个子字符串。这是你的母字符串就会成为垃圾被JVM回收掉,但是由于母字符串的char数组一直被字符串使用无法回收,整个母字符串的内容一致存在内存中,导致内存泄漏。
关于字符串常量池
在这之前,我一直在想Java的字符串常量池是存在什么地方,Heap?PermGen?并且写了一些测试代码证实了字符串常量池是存储在PermGen。但看到大牛的文章后,发现我的认识还是那么的狭小,下面是大牛对字符串常量池和String.intern()的总结:
- 不要在Java 6及以前的版本使用String.intern()将字符串放入常量池,因为这是的字符串常量池是存储在固定大小的内存区(PermGen)
- Java 7和8中将字符串常量池存储在堆内存中。
- Java 7和8中可以通过JVM参数-XX:StringTableSize来控制字符串常量池的大小。
- -XX:StringTableSize的默认值在Java 7中是1009,在Java 8中大概是25~50K
- 在多线程情况下可以随意使用Stirng.intern()方法。8个写线程只比1个写线程的负载多了17%,1个写7个读线程只比一个线程的负载多了9%
- 字符串常量池不是线程隔离的
- 虽然Java 7+对String.intern()做了很多优化,但是它还是花费CPU资源。在简单的例子中没有调用String.intern()方法的程序比调用了的程序要快3.5倍。所以不需要对所有的字符串调用String.intern()方法,只需要对经常被使用的字符串(例如:省、市等)调用String.intern()方法加入常量池。
参考文献:
http://java-performance.info/changes-to-string-java-1-7-0_06/
http://stackoverflow.com/questions/20945049/is-a-java-string-really-immutable
http://java-performance.info/string-intern-in-java-6-7-8/
http://java-performance.info/string-intern-java-6-7-8-multithreaded-access/
http://java-performance.info/string-intern-java-7-8-part-3/
- 大小: 11.9 KB
分享到:
相关推荐
解决 java.lang.NoSuchFieldError: STRING at org.jbpm.identity.hibernate.PermissionUserType. 不用jbpm的jbpm-identity.jar 用这个就好
在Java编程中,`java.lang.NumberFormatException`是一个常见的运行时异常,它通常发生在尝试将一个字符串转换为数值类型(如int、long、float或double)时,但该字符串无法被解析为有效的数值。"For input string: ...
标题“Springmvc : Failed to convert property value of type 'java.lang.String' to int”涉及的是一个在使用Spring MVC框架时常见的错误。这个错误通常出现在尝试将一个字符串类型(String)的属性值转换为整型...
标题 "java.lang.Exception: java.lang.IllegalArgumentException: firstMovedIndex, lastMove" 描述了一个Java编程中的异常情况。这个异常通常发生在尝试执行一个不合法的操作时,例如数组或集合操作超出了其边界。...
在Java编程语言中,`java.lang.String`是最重要的类之一,它是所有字符串操作的基础。这个类位于核心类库中,因此无需显式导入即可使用。本文将深入探讨`String`类的一些关键知识点,包括它的特性、构造方法、常用...
Java编程语言的基础构建块之一是`java.lang`包,它被自动导入到每个Java程序中,无需显式导入。这个包包含了许多核心类和接口,是编写任何Java应用程序不可或缺的部分。`java.lang`包中最基本的类是`Object`,它是...
总的来说,解决 "java.lang.ClassNotFoundException" 的关键在于正确管理和配置项目的依赖,确保所有必要的类都能在运行时被正确加载。通过仔细检查和调整上述步骤,你应该能成功地解决这个问题并继续进行JSON转换...
在Java编程中,`java.lang.NoSuchMethodError`异常是一个运行时异常,通常发生在程序试图执行一个不存在的方法时。这个异常的出现通常意味着编译时和运行时的类版本不匹配,或者是在编译期间存在而运行时不存在的...
Type 异常报告 消息 Failed to convert ... nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date': no matching editors or co
commons-lang3.3.1.jar、Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。为JRE5.0+的更好的版本所提供 Jar文件包含的类: META-INF/MANIFEST.MFMETA-INF/LICENSE....
commons-lang3.3.1.jar、Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。为JRE5.0+的更好的版本所提供 Jar文件包含的类: META-INF/MANIFEST.MFMETA-INF/LICENSE....
在Java编程语言中,`java.lang.reflect`包是核心库的一部分,它提供了运行时访问类、接口、字段和方法的能力。这个包对于理解和操作对象的动态特性至关重要,尤其是在实现反射机制时。反射允许我们在程序运行期间...
在Java编程中,遇到“Exception in thread 'main' java.lang.NoClassDefFoundError”是一种常见的异常情况,这通常意味着JVM在运行时未能找到指定的类定义。此错误不同于ClassNotFoundException,后者发生在尝试加载...
了解 java.lang 包 掌握包装类 掌握String 和 StringBuffer 类 运用以下类的方法: Math Class Object
Java语言的核心库之一就是`java.lang`包,它是最基础且最重要的包,包含了所有Java程序都会用到的一些基本类。这个包中包含了如异常处理、基本数据类型的包装类、数学运算、类和对象操作等核心功能。 异常处理是...
解决 java.lang.NoSuchMethodError 的错误 Java.lang.NoSuchMethodError 错误是一种常见的 Java 异常,它发生在 Java 虚拟机 (JVM) 无法找到某个类的特定方法时。这种错误可能是由于项目依赖比较复杂、Java 运行...
java.lang.ExceptionInInitializerError异常的解决方法 java.lang.ExceptionInInitializerError异常是一种常见的Java异常,它发生在静态变量的初始化过程中。了解这个异常的解决方法对于Java开发者来说非常重要。 ...
boolean delete(java.lang.String sql, java.util.Map<java.lang.Integer,java.lang.Object> elements) 根据传入的参数删除单条记录的方法 boolean delete(java.lang.String sql, java.lang.Object[] elements)...