Java堆、栈、内存分析

来源:互联网 发布:传奇引擎源码 编辑:程序博客网 时间:2024/06/05 23:58

原文:http://www.cnblogs.com/luckyliu/archive/2012/04/05/2433657.html

在 JAVA 中,有六个不同的地方可以存储数据:

1. 寄存器( register )。这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
2. 栈( stack )。位于通用 RAM 中,但通过它的“栈指针”可以从处理器获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,存取仅次于寄存器。创建程序时候, JAVA 编译器必须知道存储在栈内所有数据的确切大小和生命周期,因为它必须生成相应的内存空间,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些 JAVA 数据存储在栈中——特别是对象引用,但是 JAVA 对象不存储其中。
3. 堆( heap )。一种通用性的内存池(也存在于 RAM 中),用于存放所有的 JAVA 对象。堆不同于栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要 new 写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代价:用堆进行存储分配比用堆栈进行存储存储需要更多的时间。

4. 静态存储( static storage )。这里的“静态”是指“在固定的位置”(尽管也位于RAM)。静态存储里存放程序运行时一直存在的数据。你可用关键字 static 来标识一个对象的特定元素是静态的,但 JAVA 对象本身从来不会存放在静态存储空间里。
5. 常量存储( constant storage )。常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割开,所以在这种情况下,可以选择将其放在 ROM 中。(这点的一个例子便是字符串常量池,所有字面字符串和字符串常量表达式都被自动intered从而被放到一个特殊的静态存储空间里。)
6. 非 RAM 存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

上面这段话摘取自《 Thinking in Java 》』

---------------------------------------------------------------------
堆 是一个运行时数据区 , 类的对象从中分配空间。这些对象通过 new 建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的, Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 java 中的对象和数组都存放在堆中。
栈 的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量( int, short, long, byte, float, double, boolean, char )和对象引用。

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。 假设我们同时定义:
int a = 3;
int b = 3 ;
编译器先处理 int a = 3 ;首先它会在栈中创建一个变量为 a 的引用,然后查找栈中是否有 3 这个值,如果没找到,就将 3 存放进来,然后将 a 指向 3 。接着处理 int b = 3 ;在创建完 b 的引用变量后,因为在栈中已经有 3 这个值,便将 b 直接指向 3 。这样,就出现了 a 与 b 同时均指向 3 的情况。这时,如果再令 a=4 ;那么编译器会重新搜索栈中是否有 4 值,如果没有,则将 4 存放进来,并令 a 指向 4 ;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

以上内容也是摘抄自网上。

 

疑惑:《Thinking in java 4》中不是这样讲的,书中说对于基元数据的存储是在stack中用变量直接存值。对于这点,暂可采取上边的理解,有待日后明确。

---------------------------------------------------------------------
下面我自己来举几个例子:

public class TestStr { public static void main(String[] args) { // 以下两条语句创建了1个对象。"凤山"存储在字符串常量池中 String str1 = "凤山" ; String str2 = "凤山" ; System.out.println(str1==str2);//true   // 以下两条语句创建了2个对象,存储在堆内存中 String str3 = new String("天峨" ); String str4 = new String("天峨" ); System.out.println(str3==str4);//false   // 以下两条语句创建了1个对象。9是存储在栈内存中 int i = 9; int j = 9; System.out.println(i==j);//true   // 以下两条语句创建了1个对象。1对象存储在栈内存中 Integer l = 1;//装箱 Integer k = 1;// 装箱 System.out.println(l==k);//true   //由于没有了装箱,以下两条语句创建了2个对象。两个1对象存储在堆内存中 Integer l1 = new Integer(1); Integer k1 = new Integer(1); System.out.println(l1==k1);//false 内容来自17jquery   // 以下两条语句创建了1个对象。i1,i2变量存储在栈内存中,两个256对象存储在堆内存中 Integer i1 = 256; Integer i2 = 256; System.out.println(i1==i2);//false }} 

对于以上最后两个关于 Integer 对象的例子,在自动装箱时对于值从 –128 到 127 之间的值,使用一个实例。
下面是对字符串常量池()的一个例子:
String s1 = "aaa" + "bbb"; // 产生了 1 个对象。
由于常量的值在编译的时候就被确定了。在这里, "aaa" 和 "bbb" 都是常量,因此变量 s1 的值在编译时就可以确定。这行代码编译后的效果等同于:
String s1 ="aaabbb";
因此这里只创建了一个对象 "aaabbb" ,并且它被保存在字符串池里了。

另:“ == “ 在判断对象时,其实是根据对象在堆栈中的地址判断对象是不是一样,而不是根据 hashcode 值。


在网上看见这段对 Java String 中的 HashCode 和 equal 的总结比较好,记录如下:
1. hashSet 中比较是否重复的依据是 a.hasCode () =b.hasCode () && a.equals ( b ) 内容来自17jquery
2. String 的 hashCode 依据: 以依赖于 char[i] 的 int 值以和 char[i] 的排列序的算法计算出的(可以去看看源码)。不依赖 String 的 ref.
3. String 的 equals 依据: a==b || ( a.length=b.length && { a[i]=b[i] } )
4. 只有用 a==b 时比校的才是比校的 ref ,也就是说这时才是比校是 a 与 b 是不是同一个对象
5. 结论: 两个不同 ref 的 String 可能会被认为是集合中的同一个元素。

---------------------------------------------------------------------


下面主要介绍 JAVA 中的堆、 栈 和 常量池 :

1. 寄存器

最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制。

2. 栈

存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)

3. 堆

存放所有new出来的对象。

4. 静态域

存放静态成员(static定义的)

5. 常量池

存放字符串常量和基本类型常量(public static final)。

6. 非RAM存储

硬盘等永久存储空间

这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。

对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

如以下代码:

String s1 =  "china" ; String s2 =  "china" ; String s3 =  "china" ; String ss1 =  new  String ( "china" ); String ss2 =  new  String ( "china" ); String ss3 =  new  String ( "china" );

对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。

成员变量存储在堆中的对象里面,由垃圾回收器负责回收。

如以下代码:

class  BirthDate { private   int  day; private   int  month; private   int  year; public  BirthDate ( int  d,  int  m,  int  y )  {     day = d;     month = m;     year = y; }  

省略 get,set 方法 …… } 
public   class  Test{     public   static   void  main ( String args[] ) {            int  date =  9 ;            Test test =  new  Test ();             test.change ( date );             BirthDate d1=  new  BirthDate ( 7 , 7 , 1970 );             }            public   void  change1 ( int  i ) { i =  1234 ;            } }

对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析一下代码执行时候的变化:

1. main 方法开始执行:


              int  date =  9 ; 

date 局部变量,基础类型,引用和值都存在栈中。

2. test 为对象引用,存在栈中,对象(new Test())存在堆中。

              Test test =  new  Test ();  

3.

              test.change ( date );  

i 为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。

4.

              BirthDate d1=  new  BirthDate ( 7 , 7 , 1970 );  

d1 为对象引用,存在栈中,对象(new BirthDate())存在堆中,其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中。day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。

5.main 方法执行完之后,date变量,test,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收。

希望通过以上内容的介绍,能够给你带来帮助

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩吃坏东西呕吐发烧怎么办 睿芽密码忘了怎么办 做绿豆糕太稀了怎么办 自热火锅吃完后怎么办 自煮火锅吃完了怎么办 自热火锅没熟怎么办 厕所被米饭堵了怎么办 减完肥肉特别松怎么办 新开的熟食店没人光顾怎么办 吃剩的软炸里脊怎么办 小火锅加热时胀盒该怎么办 天丝面料容易皱怎么办 快递员拒不送件怎么办 鞋店里面买到假鞋子了怎么办 孕吐伤了胃疼怎么办 孕期吐的胃疼怎么办 买的巧克力化了怎么办 跑步后脸上出盐怎么办 头发被剪的很短怎么办 孩子做事情拖拉不专注怎么办 新热水壶有味道怎么办 新买电热壶有味怎么办 两个月狗耳朵臭怎么办 狗狗牙齿变黄怎么办 人用了狗沐浴露怎么办 狗狗吞食牙膏吐怎么办? 狗狗吞食了牙膏怎么办 大猪拉稀不吃食怎么办 猪不发烧不吃食怎么办 天天吃自热米饭怎么办 喝了加热包水怎么办啊 蛋挞没有盒子装怎么办 塑料饭盒盖子被吸住了怎么办 火腿淹的有臭味怎么办 微波炉热饭盖子打不开怎么办 夏天带饭容易馊怎么办? 保温饭盒里有气打不开怎么办 保温饭盒摔了一下打不开怎么办 饭盒跟盖子盖一起打不开怎么办 玻璃杯子盖被水吸住打不开怎么办 电饭煲热剩饭没加水怎么办