Java内存分配(堆、栈、常量池)

时间:2022-12-27 07:53:20

Java内存分配中本人暂时主要关心三个点:堆、栈以及常量池。

关于内存模型可以参考《http://blog.csdn.net/sinat_34596644/article/details/51761714》,他是参考《深入JVM》一书,具体还没看,也不知道他总结的是否正确。

一、粗略介绍

1.堆:存放new出来对象和数组。eg. String str = new String('xxx'); 这个new String('xxx') 对象存放在堆中。

2.栈:存放八大基本数据类型(btye/char/short/int/long/float/double/boolean)以及对象的引用

3.常量池:普通常量(值本身eg.整型常量:1024)不可变的变量(eg.final int i = 1024)

    常量池又分为静态常量池和运行时常量池未完待续(http://blog.csdn.net/gcw1024/article/details/51026840)

    静态常量池:在编译的时候确定,存在于.class文件中。

    运行时常量池:存在于.class文件中的常量池,在运行时被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法。当一个String实例str调用intern()方法时,Java查找常量池中是否有相同的Unicode字符串常量。若有,则返回其引用,若无,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。(在JDK1.7中,将常量池也放到了堆中,所以若大量使用str.intern()方法,会导致堆内存溢出,参见:http://blog.csdn.net/u014039577/article/details/50377805)

二、栈和堆之间的联系-->引用变量:

引用变量的产生:在堆中生成了对象和数组后,可以在栈中定义一个普通的变量,这个变量的值为数组或对象在堆内存中的首地址,因此栈中这个变量就成了数组或对象的引用变量。

引用变量的销毁:当程序运行到其作用域之外后引用变量被销毁,释放占用的内存。

堆内存的释放:曾经被引用变量引用的堆内存(对象或数组)将不会因为引用变量的销毁而释放,而是变成了仍然占据着内存的垃圾。在随后一个不确定的时间被垃圾回收期回收。这也是Java比较占内存的原因。

三、栈与堆的优缺点:

栈的优点:存取速度比堆块,栈数据可以共享

        缺点:数据大小与生命周期是确定的

堆的优点:是运行时动态地分配内存,生命周期也不必事先告诉编译器。

        缺点:运行时动态分配内存,存取速度较慢。

四、常量池:

常量池指的是在编译期间被确定,并被保存在已编译的.class文件(关于.class文件的说明:http://blog.csdn.net/gcw1024/article/details/51026840)中的一些数据。除了代码中所定义的基本数据类型对象型(如String及数组,通过非new的方式?)常量值(final),还包含一些以文本形式出现的符号引用

1.类的接口的全限定名;2.字段的名称和描述符;3.方法的名称和描述符。

虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合。

Java为了提高性能提供了和String类一样的对象池机制,当然Java的八种基本类型的包装类(Packaging Type)也有对象池机制(摘自http://blog.csdn.net/qq_27093465/article/details/52033409, 里面有以下例子,可以参考。

public class IntegerTest {     public static void main(String[] args) {     
objPoolTest();
}

public static void objPoolTest() {
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);

System.out.println("i1=i2\t" + (i1 == i2)); //true
System.out.println("i1=i2+i3\t" + (i1 == i2 + i3)); // ture --> Java的数学计算是在内存栈里操作的
System.out.println("i4=i5\t" + (i4 == i5)); //false
System.out.println("i4=i5+i6\t" + (i4 == i5 + i6)); //true

System.out.println();
}
}

在程序运行时,常量池会存储在Method Area,而不是堆中

存储的类型:

eg.String str = "China"; 其中的China存储在常量池中。

五、字符串内存分配

字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).

有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。

1. String s = "china"跟String ss = new String("china")的内存分配是如何的:

String s = "china"中的引用变量s会被创建在栈中,"china"会被创建在常量池(如果不存在的话,若存在则会重用)。

String ss = new String("china")中的引用变量ss被创建在栈中,new String("china")被创建在堆中,而对于其中的“china”,会先去常量池中查找是否已经有了"china"对象。如果没有则在常量池中创建一个"china",而后在堆中再创建一个此"china"对象的copy对象。

2.String ss = new String("china")会生成几个对象?

一个或两个,取决于在常量池中是否存在"china" 字符创对象,有,则一个。无,则两个。

3.字符串内存分配结构图:

Java内存分配(堆、栈、常量池)

4. 请确定如下代码的output 

        String s0= "kvill";           String s1=new String("kvill");           String s2=new String("kvill");           System.out.println( s0==s1 );        //false -->两次new在堆中生成了不同的对象,每个对象有不同的地址。==比较的是地址值,所以false        s1.intern();           s2=s2.intern(); //把常量池中"kvill"的引用赋给s2           System.out.println( s0==s1);          //false --> s0在常量池,而s1在堆中        System.out.println( s0==s1.intern() ); //true  --> s0在常量池,执行s1.intern()的时候,Java查看常量池中是否有“kvill”的Unicode字符串常量,若有则返回该字符串常量的引用        System.out.println( s0==s2 );          //true  -->同上
5. String常量池的几个例子
【1】String a = "ab";   String bb = "b";   String b = "a" + bb;   System.out.println((a == b)); //result = false 
【1】中,JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。
【2】String a = "ab";   final String bb = "b";   String b = "a" + bb;   //这样的字符串拼接是在编译期间完成?System.out.println((a == b)); //result = true
【2】和【1】中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。
 【3】String a = "ab";   final String bb = getBB();   String b = "a" + bb;   System.out.println((a == b)); //result = false   private static String getBB() {  return "b";   }
【3】JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false。

六、基础类型的变量和常量在内存中的分配。

Java内存分配(堆、栈、常量池)

编译器先处理int i1 = 9;首先它会在栈中创建一个变量为i1的引用,然后查找栈中是否有9这个值,如果没找到,就将9存放进来,然后将i1指向9。接着处理int i2 = 9;在创建完i2的引用变量后,因为在栈中已经有9这个值,便将i2直接指向9。这样,就出现了i1与i2同时均指向9的情况。最后i3也指向这个9。

七、成员变量和局部变量在内存中的分配。

我的理解是成员变量一般会在构造函数中用到,有了构造函数才会被new,new出来的对象是在堆中,因此成员变量就是在堆中的。而局部变量(形式参数),是在方法中的,方法的调用时在栈中,所以局部变量也是在栈中的。

https://www.cnblogs.com/SaraMoring/p/5687466.html中有具体的例子。