java内存管理-堆栈内存

来源:互联网 发布:javascript网站 编辑:程序博客网 时间:2024/05/02 01:13

转自:http://blog.csdn.net/yechaodechuntian/article/details/22696285

http://my.oschina.net/u/565575/blog/80285

内存泄漏的定义: 对象不再被应用程序使用,但是垃圾回收器却不能移除它们,因为它们正在被引用。

综述:在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存 中分配 。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间, 该内存空间可以立即被另作他用。 堆内存用来存放由 new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 栈有一个很重要的特殊性,就是存在栈中的数据可以共享。比较类里面的数值是否相等时,用equals()方法;当 测试两个包装类的引用是否指向同一个对象时,用==, 


Java把内存划分成两种:一种是栈内存,一种是堆内存

函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存 中分配 。

当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间, 该内存空间可以立即被另作他用。 
   堆内存用来存放由 new创建的对象和数组。 
   在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 
   在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对 象的引用变量。 

引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。 ,Java自动管理栈和堆,程序员不能直接地设置栈或堆。 

 Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等 指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时 动态分配内存的,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, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。 

String是一 个特殊的包装类数据。可以用: 
String str = new String("abc"); 
String str = "abc"; 
两种的形式来创建,第一种是用new()来新 建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 
而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈 中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。

较类里面的数值是否相等时,用equals()方法;当 测试两个包装类的引用是否指向同一个对象时,用== 下面用例子说明上面的理论。 
String str1 = "abc"; 
String str2 = "abc"; 
System.out.println(str1==str2); //true 
可以看出str1和 str2是指向同一个对象的。 

String str1 =new String ("abc"); 
String str2 =new String ("abc"); 
System.out.println(str1==str2); // false 
用new的方式是生成不同的对象。每一次生成一个 。 
   因此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。 

另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的immutable性质,当String变量需要经常变换其值时,应 该考虑使用StringBuffer类,以提高程序效率。(因为String 被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s 原先指向一个String 对象,内容是"Hello",然后我们对s 进行了+操作,那么s 所指向的那个对象是否发生了改变呢?答案是没有。这时,s 不指向原来那个对象了,而指向了另一个String 对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s 这个引用变量不再指向它了。

java中内存分配策略及堆和栈的比较 
2.1 内存分配策略

静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意 顺序分配和释放. 

2.3 JVM中的堆和栈

JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通 过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。 

JVM运行时,将内存分为堆和栈,堆中存放的是创建的对象,JAVA字符串对象内存实现时,在堆中开辟了一快很小的内存,叫字符串常量池,用来存放特定的字符串对象。 

关于String对象的创建,两种方式是不同的,第一种不用new的简单语法,即 
String s1="JAVA"; 
创建步骤是先看常量池中有没有与"JAVA"相同的的字符串对象,如果有,将s1指向该对象,若没有,则创建一个新对象, 并让s1指向它。 
第二种是new语法 
String s2="JAVA"; 
这种语法是在堆而不是在常量池中创建对象,并将 s2指向它,然后去字符串常量池中看看,是否有与之相同的内容的对象,如果有,则将new出来的字符串对象与字符串常量池中的对象联系起来,如果没有,则 在字符串常量池中再创建一个包含该内容的字符串对象,并将堆内存中的对象与字符串常量池中新建出来的对象联系起来。 
这就是字符串的一次投入,终 生回报的内存机制,对字符串的比较带来好处。

通俗版

1. 容易被搞晕的--堆和栈 
由于"堆"和"栈"这两个概念是看不见摸不着的东西,让很多程序员都整不明白是怎么回
事,其实这两个概念也没有什么好研究的,因为堆和栈程序员根本没有办法控制其具体内容。
我们只需要了解一点,栈与堆都是Java 用来在内存中存放数据的地方就行了。然后再
弄清楚这两个概念分别对应这程序开发的什么操作,以及堆和栈的区别即可。
1.1 堆--用new 建立,垃圾自动回收负责回收 
1、堆是一个"运行时"数据区,类实例化的对象就是从堆上去分配空间的;
2、在堆上分配空间是通过"new"等指令建立的;
3、Java 针对堆的操作和C++的区别就是,Java 不需要在空间不用的时候来显式的释放;
4、Java 的堆是由Java 的垃圾回收机制来负责处理的,堆是动态分配内存大小,垃圾
收集器可以自动回收不再使用的内存空间。
5、但缺点是,因为在运行时动态分配内存,所以内存的存取速度较慢。
例如:
String s1 = "asdf";
String s2 = "asdf";
System.out.println(s1==s2);
String s1 =new String ("asdf");
String s2 =new String ("asdf");
System.out.println(s1==s2);
就是在堆上开辟的空间来存放String 的对象。
1.2 栈--存放基本数据类型,速度快 
1、栈中主要存放一些基本类型的变量(int, short, long, byte, float, double,
boolean, char)和对象句柄;
2、栈的存取速度比堆要快;
3、栈数据可以共享;
4、栈的数据大小与生存期必须是确定的,缺乏灵活性。
例如:
就是在堆上开辟的空间来存放String 的对象。
1.3 何谓栈的"数据共享" 
栈其中一个特性就是"数据共享",那么什么是"数据共享"呢?
我们这里面所说的数据共享,并不是由程序员来控制的,而是JVM 来控制的,指的是是
系统自动处理的方式。
比如定义两个变量:
这两个变量所指向的栈上的空间地址是同一个,这就是所谓的"数据共享"。
它的工作方式是这样的:
JVM 处理int a = 5,首先在栈上创建一个变量为a 的引用,然后去查找栈上是否还有5
这个值,如果没有找到,那么就将5存放进来,然后将a 指向5。
接着处理int b = 5,在创建完b 的引用后,因为在栈中已经有5这个值,便将b 直接
指向5。
String str = new String("abc");
int a = 3;
int a = 5;
int b = 5;
于是,就出现了a 与b 同时指向5的内存地址的情况。
1.4 实例化对象的两种方法 
对于String 这个类来说它可以用两种方法进行建立:

用这两个形式创建的对象是不同的,第一种是用new()来创建对象的,它是在堆上开辟
空间,每调用一次都会在堆上创建一个新的对象。
而第二种的创建方法则是先在栈上创建一个String 类的对象引用,然后再去查找栈中
有没有存放"asdf",如果没有,则将"asdf"存放进栈,并让str 指向"asdf",如果已经有
"asdf" 则直接把str 指向"abc"。
我们在比较两个String 是否相等时,一定是用"equals()"方法,而当测试两个包装类
的引用是否指向同一个对象时,我们应该用"= ="。
因此,我们可以通过"= ="判断是否相等来验证栈上面的数据共享的问题。
例1:
该程序的运行结果是,"true",那么这说明"s1"和"s2"都是指向同一个对象的。
例2:
该程序的运行结果是,"false",这说明用new 的方式是生成的对象,每个对象都指向不同
的地方。


1.堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。要点:堆,顺序随意。栈,后进先出(Last-In/First-Out)。

2.堆和栈的区别可以用如下的比喻来看出:使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

3.在java中方法里定义的变量,存储在栈内存,当方法结束时,自动销毁。而new的对象是存储在堆内存里的,不会随方法结束而销毁,除非没有被另外的引用变量引用,会在被JVM在适当的时候回收。

在 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从而被放到一个特殊的静态存储空间里。)


(还需要了解堆结构 堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全树。)

附上String知识:

1.String是最基本的数据类型吗?
基本数据类型包括byte、int、char、long、float、double、boolean 和short。
java.lang.String 类是final 类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer 类

2.String s = "Hello";s = s + " world!";这两行代码执行后,原始的 String
对象中的内容到底变了没有?没有。因为String 被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在
这段代码中,s 原先指向一个String 对象,内容是"Hello",然后我们对s 进行了+操作,那么s 所指向的那个对象是否发生了改变呢?答案是没有。这时,s 不指向原来那个对象了,而指向了另一个String 对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s 这个引用变量不再指向它了。通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String 来代表字符串的话会引起很大的内存开销。因为String 对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String 对象来表示。这时,应该考虑使用StringBuffer 类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。

3.数组有没有 length()这个方法? String有没有 length()这个方法?
数组没有length()这个方法,有length 的属性。String 有有length()这个方法。



0 0