三分钟理解Java中字符串(String)的存储和赋值原理

String str1="ABC"; 和String str2 = new String("ABC"); 有什么区别。

String str1="ABC" 可能创建一个对象或者不创建对象,如果"ABC"这个字符串在java String池里不存在,会在java String池创建这个一个String对象("ABC").如果已经存在,str1直接reference to 这个String池里的对象。

String str2 = new String("ABC") 至少创建一个对象,也可能两个。因为用到new 关键字,会在heap创建一个 str2 的String 对象,它的value 是 "ABC".同时,如果"ABC"这个字符串在java String池里不存在,会在java String池创建这个一个String对象("ABC").

String 有一个intern() 方法,native,用来检测在String pool是否已经有这个String存在。

考虑下面的问题:

String str1 = new String("ABC");
String str2 = new String("ABC");

str1 == str2 的值是True 还是False呢? False.

String str3 = "ABC";
String str4 = "ABC";

String str5 = "A" + "BC";

str3 == str4 的值是True 还是False呢? True.

str3 == str5 的值是True 还是False呢? True.

在写代码的时候,一般不要 String str2 = new String("ABC");

String a = "ABC";
String b="AB";
String c=b+"C";
System.out.println(a==c); false
a和b都是字符串常量所以在编译期就被确定了!

而c中有个b是引用不是字符串常量所以不会在编译期确定。
而String是final的!所以在b+"c"的时候实际上是新创建了一个对象,然后在把新创建对象的引用传给c.



可能很多java的初学者对String的存储和赋值有迷惑,以下是一个很简单的测试用例,你只需要花几分钟时间便可理解。

1.在看例子之前,确保你理解以下几个术语:

:由JVM分配区域,用于保存线程执行的动作和数据引用。栈是一个运行的单位,Java中一个线程就会相应有一个线程栈与之对应。

:由JVM分配的,用于存储对象等数据的区域。

常量池:在编译的阶段,在堆中分配出来的一块存储区域,用于存储显式的String,float或者integer.例如String str="abc"; abc这个字符串是显式声明,所以存储在常量池。

2.看这个例子,用JDK5+junit4.5写的例子,完全通过测试

import   static  org.junit.Assert.assertNotSame;
import   static  org.junit.Assert.assertSame;

import  org.junit.Test;

/**
 * 
@author  Heis
 *
 
*/
public   class  StringTest{

    @Test
    
public   void  testTheSameReference1(){
        String str1
= " abc " ;
        String str2
= " abc " ;
        String str3
= " ab " + " c " ;
        String str4
= new  String(str2);
        
        
// str1和str2引用自常量池里的同一个string对象
        assertSame(str1,str2);
        
// str3通过编译优化,与str1引用自同一个对象
        assertSame(str1,str3);
        
// str4因为是在堆中重新分配的另一个对象,所以它的引用与str1不同
        assertNotSame(str1,str4);
    }
    

}

  • 第一个断言很好理解,因为在编译的时候,"abc"被存储在常量池中,str1和str2的引用都是指向常量池中的"abc"。所以str1和str2引用是相同的。
  • 第二个断言是由于编译器做了优化,编译器会先把字符串拼接,再在常量池中查找这个字符串是否存在,如果存在,则让变量直接引用该字符串。所以str1和str3引用也是相同的。
  • str4的对象不是显式赋值的,编译器会在堆中重新分配一个区域来存储它的对象数据。所以str1和str4的引用是不一样的。



                                                                

另一种说法,求大神指点

JVM内存分四种:

1、栈区(stacksegment)—由编译器自动分配释放,存放函数的参数值,<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">局部变量</a>的值等,具体方法执行结束之后,系统自动释放JVM内存资源

2、堆区(heapsegment)—一般由程序员分配释放,存放由new创建的对象和数组,jvm不定时查看这个对象,如果没有引用指向这个对象就回收

3、静态区(datasegment)—存放<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">全局变量</a>,<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">静态变量</a>和<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%B8%B8%E9%87%8F&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">字符串常量</a>,不释放

4、代码区(codesegment)—存放程序中方法的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BB%A3%E7%A0%81&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">二进制代码</a>,而且是多个对象共享一个代码空间区域

在方法(代码块)中定义一个变量时,java就在栈中为这个变量分配JVM内存空间,当超过变量的<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E4%BD%9C%E7%94%A8%E5%9F%9F&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">作用域</a>后,java会自动释放掉为该变量所分配的JVM内存空间;在堆中分配的JVM内存由<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=java%E8%99%9A%E6%8B%9F%E6%9C%BA&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">java虚拟机</a>的自动<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">垃圾回收</a>器来管理,堆的优势是可以动态分配JVM内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配JVM内存的。缺点就是要在运行时动态分配JVM内存,存取速度较慢;栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的无灵活性。

◆java堆由Perm区和Heap区组成,Heap区则由Old区和New区组成,而New区又分为Eden区,From区,To区,Heap={Old+NEW={Eden,From,To}},见图1所示。

Heap区分两大块,一块是NEWGeneration,另一块是OldGeneration.在NewGeneration中,有一个叫Eden的空间,主要是用来存放新生的对象,还有两个SurvivorSpaces(from,to),它们用来存放每次<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">垃圾回收</a>后存活下来的对象。在OldGeneration中,主要存放应用程序中生命周期长的JVM内存对象,还有个PermanentGeneration,主要用来放JVM自己的反射对象,比如<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E7%B1%BB%E5%AF%B9%E8%B1%A1&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">类对象</a>和方法对象等。

在NewGeneration块中,垃圾回收一般用Copying的算法,速度快。每次GC的时候,存活下来的对象首先由Eden拷贝到某个SurvivorSpace,当SurvivorSpace空间满了后,剩下的live对象就被直接拷贝到OldGeneration中去。因此,每次GC后,EdenJVM内存块会被清空。在OldGeneration块中,垃圾回收一般用mark-compact的算法,速度慢些,但减少JVM内存要求.

垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收NEW中的垃圾,JVM<a target=_blank target="_blank" class="inner-link decor-none" href="http://zhidao.baidu.com/search?word=%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA&fr=qb_search_exp&ie=utf8" rel="nofollow" style="color: rgb(202, 0, 0); text-decoration: none;">内存溢出</a>通常发生于OLD段或Perm段垃圾回收后,仍然无JVM内存空间容纳新的Java对象的情况。

JVM调用GC的频度还是很高的,主要两种情况下进行垃圾回收:当应用程序线程空闲;另一个是JVM内存堆不足时,会不断调用GC,若连续回收都解决不了JVM内存堆不足的问题时,就会报outofmemory错误。因为这个异常根据系统运行环境决定,所以无法预期它何时出现。

根据GC的机制,程序的运行会引起系统运行环境的变化,增加GC的触发机会。为了避免这些问题,程序的设计和编写就应避免垃圾对象的JVM内存占用和GC的开销。显示调用System.GC()只能建议JVM需要在JVM内存中对垃圾对象进行回收,但不是必须马上回收,一个是并不能解决JVM内存资源耗空的局面,另外也会增加GC的消耗。

◆当一个URL被访问时,JVM内存区域申请过程如下:

A.JVM会试图为相关Java对象在Eden中初始化一块JVM内存区域

B.当Eden空间足够时,JVM内存申请结束。否则到下一步

C.JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收),释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区

D.Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区

E.当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)

F.完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建JVM内存区域,则出现"outofmemory错误"

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值