JAVA内存管理+String创建字符串对象+常量池详解(合集)

相信绝大多数的新人在看到这一块的时候都是晕的,而且不少面试中会出现关于内存和常量池的问题,今天我们就简单讲一下JAVA程序运行过程的内存管理、String创建字符串对象和常量池的问题。

Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾回收器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

首先,想要了解JAVA的内存分配,就要先知道有哪些内存模块,也就是说要了解JAVA内存模型。
Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。
方法区:是静态分配的,编译器将变量在绑定在某个存储位置上,而且这些绑定不会在运行时改变。 常数池,源代码中的命名常量、String常量、代码段和数据段(用来存放static定义的静态成员)都保存在方法区。
Java栈:是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。用来保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用。
Java堆:堆分配意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
(补充:
内存泄露:在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:1、这些对象是可达的,即有引用指向;2、这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。)

—–String创建字符串对象与常量池—–

案例:

        int a=10;
        int b=20;
        System.out.print(a==b);//true
        String str="abc";
        String str1="abc";  
        System.out.print(str==str1);//true
        String str2="a"+"b"+"c";        
        System.out.print(str==str2);//true  
        String str3="c";
        String str4="ab"+str3;
        System.out.print(str==str4);//false 
        String str5="ab";
        String str6=str3+str5;
        System.out.print(str==str6);//false
        String str7=new String("abc");  
        String str8=new String("abc");  
        System.out.print(str7==str8);//false
        System.out.print(str==str7);//false

案例分析
如上例所述,变量a,b和它们的值10,20都是存在栈里面,声明的所以String类型的引用也都是存在栈里。而字符串abc是存在字符串常量池中,new出来的String对象则是存在堆里。

String str="abc";
System.out.print(str==str1);//true

上面这行代码被执行的时候,JVM先到字符串池中查找,看是否已经存在值为”abc”的对象,如果存在,则不再创建新的对象,直接返回已存在对象的引用;如果不存在,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。所以这句代码创建了一个对象

String str1="abc";  
System.out.print(str==str2);//true  

通过上面的解释这个就清楚了,在执行第二行代码时,”abc”字符串对象在常量池中已存在,所以直接返回池中已存在的那个字符串对象。

String str2="a"+"b"+"c";

由于常量字符串是在编译的时候就也被确定的,又因”a”,”b”和”c”都是常量,因此变量str2的值在编译时就可以确定。这行代码编译后的与String str=”abc”;是一样的,所以这句代码也是只创建了一个对象,但是这与我们平时好像不太一样啊?一般使用“+”连接两个字符串都会产生另一个新的字符对象。下面我们看一下下面这行代码就明白了:

String str3="c";
String str4="ab"+str3;
System.out.print(str==str4);//false 
String str5="ab";
System.out.print(str==str6);//false

从上面例子我们就可以得出:使用“+”连接的两个字符串本身就是字面常量字符串时,如果池中存在这样连接后的字符串,则是不会重新创建对象,而是直接引用池中的字符串对象;如果“+”连接的两字符串中只要有一个不是字面常量串(即已经定义过的),会产生新的字符串对象(抛去特殊如final定义字符串不提)。
那我们来看一下有new创建字符串时会有什么不同:

String str7=new String("abc");  

首先、这行代码究竟创建了几个String对象呢?答案是2个。由于new String(“abc”)相当于”abc”,一个就是创建出来的放在堆的原实例对象,而另一个就是放在常量池中的 “aaa” 对象,当然这里的str7本身只是一个引用,放在栈里,用来指向堆中创建出来的对象。

String str7=new String("abc");  
String str8=new String("abc");  
System.out.print(str7.equals(str8));//true
System.out.print(str7==str8);//false
System.out.print(str==str7);//false

由于str7和str8是连个存在栈里的引用,他们分别创建了两个对象”abc”,虽然他们内容相同(str7.equals(str8)==true),但是实际存在物理地址不同,所以str7==str8值为false,同理,str指向常量池中的”abc”,所以str==str7也是false。

关于常量池,是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

注意:常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念。
所以当然常量池不只是表示字符串常量池,例如一些包装类都实现了常量池技术

基本类型和基本类型的包装类。基本类型有:byte、short、char、int、long、boolean。基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean(注意区分大小写)。
二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中
上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现
如下:

        Integer i1=10;
        Integer i2=10;
        Integer i3=new Integer(10);
        Integer i4=new Integer(10);
        System.out.print(i1==i2);//true
        System.out.print(i2==i3);//false
        System.out.print(i3==i4);//false
        Double d1=1.0;
        Double d2=1.0;
        System.out.print(d1==d2);//false

案例分析:
1)i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer包装类实现了常量池技术,因此i1和i2的10均是从常量池中获取的,均指向同一个地址,因此i1=12。
2)i3和i4均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i3和i4不相等,因为他们所存指针不同,所指向对象不同。
3)d1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。

—–PS—–
关于这个帖子纠结了好几天还是写了,因为感觉写不出东西,所以就把一个点凑在了一起讲,希望对大家有用~
CSDN :BlackGuard_P
QQ:51452547


猜你喜欢

转载自blog.csdn.net/qq_34598667/article/details/53183680