分析Java的String对象

  对于Java中的String对象,个人觉得每个程序员都会思考过、学习过、研究过这个对象,因为他是面试官们的最爱。如:String s = new String("abc");,创建了几个对象。这种问题反复出现在程序员面试的过程中。下面我们对应着一些代码片段以及其的执行结果,来深入分析Java的String对象。

  首先我们要注意的是String对象的处理在JDK6和JDK7中的处理是不同的。下面通过代码来分析String的三个主要方面知识。

1.常量池的存在

先看看下面代码以及执行结果:

public static void main(String[] args) {
    new StringInternTest().testStringPool();
}

/**
 * 测试常量池的存在
 */
public void testStringPool() {
    String a = new String("abc");
    String b = "abc";
    System.out.println(a == b);
}

执行结果:false

其实这点相信很多Java学习者都已经了解了。Java对于一些基本类型的值维护着一个常量池,如String,int等。同时int还有着自己的缓存。

正是由于字符串常量池的存在才使得上面的代码执行结果为false。

首先分析变量b的处理流程,由于b是通过直接指定字符串值的方式创建的,所以先在栈中创建一个变量b,然后再去常量池中查找有没有"abc"这个字符串存在,不存在则创建一个值为“abc”的对象存放到常量池,然后变量b指向该对象,存在的话则直接指向即可。

下面来分析变量a,a是通过new关键字声明的字符串变量,java中只要遇到new那么jvm就会进行内存分配。所以变量a指向一个地址引用,该引用指向一个堆内存中分配的内存块,该内存块存放着一个String对象,到这里相信大家都是没有任何疑问的。再后面问题就来了。

当分配一块内存存放一个String对象,那么这个对象的值“abc”怎么存放的呢。从网上搜集很多文章都解释说“abc”的值同变量b的创建方式一样,也是在常量池保存着一个值为“abc”的对象,然后将一个变量指向该对象,即b-->堆中的一个对象-->常量池中的一个对象,而a-->常量池中的一个对象。所以a==b为false。个人感觉不是太对,但是也找不出正确的结论,或者可以很有力的证据证明该解释是错误的,不过我可以有一种方法从侧面证明该解释的不合理性,该方法会在下面一段介绍到。在此我希望有同样疑问的人可以一起探讨,或者有人能够给出权威解答。

2.常量池的位置

对于JVM中常量池的位置,JDK6和JDK7是不同的。对于JVM的内存异常错误--OOM其实是分类型的。如java.lang.OutOfMemoryError: Java heap space,java.lang.OutOfMemoryError: PermGen space,还有其他的,但是这里只需要用到这两个即可。下面我们来看一段代码以及在JDK6和7下的执行结果。

代码一:/**
 * 测试常量池的位置
 */
public void testPoolLocation() {
    List<String> lists = new ArrayList<String>();
    for (int i = 0; i < 1000000; i++) {
        System.out.println(i);
        lists.add(new String("" + i).intern());
    }
}

注:intern()方法下面会介绍

代码二:/**
 * 测试常量池的位置
 */
public void testPoolLocation2() {
    List<String> lists = new ArrayList<String>();
    for (int i = 0; i < 1000000; i++) {
        System.out.println(i);
        lists.add(new String("" + i));
    }
}

在执行代码一二时我们需要调整下JVM的内存参数:-Xms16M -Xmx16M -XX:PermSize=6M -XX:MaxPermSize=6M

执行堆内存为16M,非堆内存为6M,这样的目的是让程序更快的抛出内存异常。

先在JDK6在执行代码一,看结果:

123094
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at org.vicky.study.StringInternTest.testPoolLocation(StringInternTest.java:33)
    at org.vicky.study.StringInternTest.main(StringInternTest.java:14)

从结果可以看出,程序创建了123094个字符串然后抛出了内存异常,注意下这里的内存异常是PermGen space

再来看看代码二的执行结果:

297868
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at org.vicky.study.StringInternTest.testPoolLocation2(StringInternTest.java:44)
    at org.vicky.study.StringInternTest.main(StringInternTest.java:14)

从结果可以看出,代码二创建了297868个字符串然后抛出了内存异常,注意这里的异常是发生在heap space,不同于代码一是发生在PermGen space。而两段代码的不同之处仅仅是代码一在创建字符串时调用了intern方法。

对于intern方法做个简单解释,具体可以参考API。intern主要作用是将判断字符串常量池中是否存在一个等于该字符串对象的对象(通过equals()方法判断),存在则返回常量池中的对象,不存在则将该对象放入常量池。

另外代码一创建仅12W个字符串,而不进行intern()操作的代码二却能够创建30W个字符串,所以对于代码一来说引起对内异常的操作是intern()而且创建过多的字符串对象。而intern是将字符串对象放入常量池,所以最终的内存异常是由于常量池所在的内存块发生了OOM,由此我们可以断定JDK6的常量池是在PermGen中。

下面再来看看JDK7的执行结果:

代码一:

300836
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.lang.StringBuilder.toString(StringBuilder.java:405)
    at org.vicky.study.StringInternTest.testPoolLocation(StringInternTest.java:33)
    at org.vicky.study.StringInternTest.main(StringInternTest.java:14)

代码二:

300742
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.lang.StringBuilder.toString(StringBuilder.java:405)
    at org.vicky.study.StringInternTest.testPoolLocation2(StringInternTest.java:44)
    at org.vicky.study.StringInternTest.main(StringInternTest.java:14)

从两个结果来看,结果是一样的,内存异常都发生在heap space,说明JDK7将字符串常量池搬到了堆内存。

这里还有另外一个结果:代码一和二创建的字符串数量相差很小,说明执行intern()方法和不执行intern()方法每次消耗的内存大致相同,为什么会这样呢,根据intern()方法的API文档,intern会确保常量池存放一个相同的字符串,那据此说明代码一每次都会存在两个对象,所以代码一可创建的字符串数量应该是代码二的一般才对,但是结果并非如此。原因是JDK7的intern方法会将变量直接指向常量池中的对象,而不指向原来的对象,所以GC会将原来的对象回收掉。

上面提到过如何侧面证明new String("abc")不会将"abc"存到常量池。现在我们来分析JDK6下的代码一和代码二的执行结果,如果说new String("abc")会将"abc"存入常量池,那么代码一中的intern()方法岂不是没有意义,因为每次new一个字符串时这个字符串都会自动放入常量池,但是一和二的执行结果完全是不同的,所以我认为new String("abc")不会将"abc"存入常量池。不知道这样理解对还是不对,但是也找不到更好地文章。

3.intern方法的使用

Stirng的intern()方法是将字符串对象显示的放入常量池的方法。

Stirng的intern()方法的使用需要小心,因为常量池是一个hashtable,即在常量池查找字符串时是根据hashcode去查找的,但是JDK的常量池大小是固定的,所以当常量池里的字符串过多就会发生hash碰撞,导致查找效率降低。

最后其实还是没搞清楚new String()的逻辑以及intern()方法的意义。该篇文章可作为一个思路,不可作为一个结论。我也会继续对其进行研究,希望能够深入的了解String常量池。欢迎一起探讨。

参考文章:

深入解析String#intern

java中特殊的String类型

从内存溢出看Java 环境中的内存结构

时间: 2025-01-06 07:56:01

分析Java的String对象的相关文章

Java中String对象的不可变性

首先看一个程序 package reverse; public class Reverse { public static void main(String[] args) { String c1=new String("abc"); String c2=new String("abc"); String c3=c1; System.out.println("c1==c2:"+ (c1==c2)); System.out.println(&quo

java中String对象和String变量

java中String对象和String变量 (2011-12-27 20:40:27) 转载▼ 标签: it 最近在论坛上看到关于String s = new String("XYZ") + new String("XYZ");到底创建几个对象的讨论,觉得比较有意思,在此总结一下. 在JAVA中除了8种基本类型之外,其他的都是类对象及其引用.所以 "XYZ"在JAVA中是一个String对象,对于String类对象来说它的对象值是不能修改的,也

Java系列2 --- 你真的知道Java的String对象么?

?在上一篇中说道这篇文章会说java的动态绑定机制,由于这个知识点放在继承中讲会比较合适,说以在这篇文章中先来详细的说说String对象吧. ?只要学过Java的同学,我们都知道Java一共有8中基本类型,但是在Java中最常用的String类型却不属于这8中基本类型中.他是Java.lang包中的一个类.但是String对象在引用传递中JVM的处理却与其他对象不同. ?在正式开始来讲这个String对象的时候我们首先来简单的说明下Java中的值传递和引用传递.正如很多Java说熟知的那样,Ja

从源码分析java.lang.String.isEmpty()

今天在写代码的时候用到了java.lang.String.isEmpty()的这个方法,之前也用过,今天突发奇想,就看了看源码,了解了解它的实现方法,总结出来,大家可以交流交流. 通常情况下,我们使用这个方法的时候是这样的: "hello wudb".isEmpty(); 上面的代码返回的是false,然后我们打开源码分析,isEmpty()这个方法在很多类里面都有,我们今天分析的是String里面的,所以找到java.lang.String这个类,然后去找idEmpty()这个方法

Java中String对象的传递解析

话不多说了,直接上代码. public class Demo { // static String str = "hello world!"; // static String str = new String("hello world!"); // static char[] ch = new char[]{'A','B','C'}; public static void test(String str,char[] ch,int a) { System.out.

java 创建string对象机制 字符串缓冲池 字符串拼接机制 字符串中intern()方法

字符串常量池:字符串常量池在方法区中 为了优化空间,为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池.如果字符串已经存在池中,就返回池中的实例引用.如果字符串不在池中,就会实例化一个字符串并放到池中.Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享.所以,在常量池中的这些字符串不会被垃圾收集器回收 1.String str = new String("hello");此时创建的2个对象

java 创建string对象机制 字符串缓冲池 字符串拼接机制

1.String str = new String("hello"); 创建了2个对象,1.检查常量池中有没有hello,没有的话,创建对象放到常量池中,再创建对象放到堆中.如果常量池有hello对象,则只创建一个对象并放到堆中. 2.字符串常量池在方法区 3.String str = "hello";检查常量池有无hello,如果有,则把指向该对象,如果没有,创建对象放在常量池里. 4.intern()方法.把字符串变成常量池里的字符串:如果常量池中已经包含了等于

java中String对象的长度

java中String的长度 使用 Integer.MAX_VALUE Integer.MAX_VALUE = 2147483647 :   String   和   StringBuffer 获得长度的方法: public   int   length() Returns   the   length   of   this   string.   The   length   is   equal   to   the   number   of   16-bit   Unicode   c

Java面向对象----String对象的声明和创建

String a="abcd"  相等  String b="abcd" String a=new String("abcd")   不等于  String  b=new String("abcd")     字符串池内存地址不同 对象不可变  常量 "abcd"+"a"   拼接 等于新创建了对象  abcda 面向对象的优点 便于程序模拟现实世界中的实体 隐藏细节 可重用 java对