Java内存之"栈"与"堆"

标签: java 内存 | 发表时间:2013-01-29 01:47 | 作者:
出处:http://www.iteye.com

        昨天中午,发了一篇 equals和==区别的博文,晚上再看时有几位大牛指出了其中的一些错误,很感谢他们的留言,一句简简单单的留言给了我对这些错误知识点改正的机会。或许这就是从事互联网行业所提倡的互帮互助的精神吧,因为有分享,有交流,互联网才会发展的如此迅猛。大牛提的一个观点很好,好的东西可以拿出来分享,错的东西却可能带给别人错误的理解,这一点我确实得向看了我写了一些bug博客的人道个歉。

        针对大牛所指出的错误,晚上翻出了资料,重新温习了一遍。继续总结一下:

       

        一、在JVM中,内存是如何被划分的?

        java把内存分两种:一种是栈内存,另一种是堆内存

        1. 在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;(所以int的东西放在栈中)

        2. 堆内存用来存放由new创建的对象和数组以及对象的实例变量。

        在函数(代码块)中声明(这里并没有实例化)一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理

 

        二、堆和栈的优缺点比较
        1. 堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的;缺点就是要在运行时动态分配内存,存取速度较慢。

        2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器,栈数据可以共享;但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

 

        三、new出来的对象存在内存的什么地方?

        在Java中,创建一个对象包括对象的声明和对象的实例化两部分。程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(heap)中分配空间。

public class NewTest {
	double aaa;
	double bbb;
	public NewTest(double aa,double bb){
		aaa = aa;
		bbb = bb;
	}
}

        上面的这段代码,当我们使用NewTest  test = new NewTest()时,我们做如下分析:

        NewTest  test : 声明一个对象test时,将在栈内存为对象的引用变量test分配内存空间,但NewTest的值为空,称test是一个空对象。空对象不能使用,因为它还没有引用任何"实体"。

        test = new NewTest()时,在堆内存中为类的成员变量aaa,bbb分配内存,并将其初始化为各数据类型的默认值;接着进行显式初始化(类定义时的初始化值);最后调用构造方法,为成员变量赋值,返回堆内存中对象的引用(相当于首地址)给引用变量test,以后就可以通过test来引用堆内存中的对象了。

 

NewTest  test1 = new NewTest();
NewTest  test2 = new NewTest();

        在使用同一个类创建两个不同的对象的时候,这些对象实例将在堆中被分配到不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。

 

        四、Java基本类型在内存中的存储

        这里重点讨论一下八种基本类型(int, short, long, byte, float, double, boolean, char)在内存中时如何存放的。我们所指的八种基本类型定义的变量,是指形式如 int a=3、char aa ='a'类型的,即不通过new的。这里的aa时指向char类型的一个引用,指向'a'这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

        另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。

        比如:我们同时定义:

                long a =3;

                long b =3;

        编译器处理的过程是:

        1. 编译器先处理long a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。

        2. 接着处理long b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

        定义完a与b的值后,如果再令a = 4,这时,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

 

        五、基本类型对应的包装类

        基本类型都有对应的包装类:如int对应Integer类,double对应Double类等,基本类型的定义都是直接在栈中,如果用包装类来创建对象,就和普通对象一样了。例如:

        int a=0;a直接存储在栈中。

        Integer b= new Integer(5);b对象数据(这里指的是5)存储在堆中,b的引用存储在栈中,通过栈中的引用来操作对象。

   

        六、String在内存中的存放

        String是一个特殊的包装类数据,可以用用以下两种方式创建:

        String str = new String("abc");第一种创建方式,和普通对象的的创建过程一样;

        String str = "abc";  第二种创建方式,类似于基本数据类型的创建,变量名和数据存放在栈空间中。

 

        七、数组在内存中的存放

         int x[] 或者int []x 时,在内存栈空间中创建一个数组引用,通过该数组名来引用数组。

         x = new int[5] 将在堆内存中分配5个保存int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0.

 

         八、static变量在内存中的存放

         用static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的"固定位置"-static storage,可以理解为所有实例对象共有的内存空间。static变量有点类似于C中的全局变量的概念;静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址。把static拿来,就是告诉JVM它是静态的,它的引用(含间接引用)都是指向同一个位置,在那个地方,你把它改了,它就不会变成原样,你把它清理了,它就不会回来了。

         那静态变量与方法是在什么时候初始化的呢?

         对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再进行初始化。

 

——2013年1月29日凌晨写于刘洋寝室



已有 1 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [java 内存] 推荐:

JAVA内存释放

- - Java - 编程语言 - ITeye博客
(问题一:什么叫垃圾回收机制. ) 垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能. 当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用,以免造成内存泄露. (问题二:java的垃圾回收有什么特点. ) JAVA语言不允许程序员直接控制内存空间的使用.

Java 堆内存(Heap)

- - ITeye博客
        堆(Heap)又被称为:优先队列(Priority Queue),是计算机科学中一类特殊的数据结构的统称. 堆通常是一个可以被看做一棵树的数组对象. 在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权.

java内存泄漏

- - 编程语言 - ITeye博客
不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址. Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的. GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.

深入Java内存模型

- - ImportNew
你可以在网上找到一大堆资料让你了解JMM是什么东西,但大多在你看完后仍然会有很多疑问. happen-before是怎么工作的呢. 用volatile会导致缓存的丢弃吗. 为什么我们从一开始就需要内存模型. 通过这篇文章,读者可以学习到足以回答以上所有问题的知识. 它包含两大部分:第一部分是硬件层次的大体架构,第二部分是深入OpenJdk源代码和实现.

Java内存之"栈"与"堆"

- - ITeye博客
        昨天中午,发了一篇 equals和==区别的博文,晚上再看时有几位大牛指出了其中的一些错误,很感谢他们的留言,一句简简单单的留言给了我对这些错误知识点改正的机会. 或许这就是从事互联网行业所提倡的互帮互助的精神吧,因为有分享,有交流,互联网才会发展的如此迅猛. 大牛提的一个观点很好,好的东西可以拿出来分享,错的东西却可能带给别人错误的理解,这一点我确实得向看了我写了一些bug博客的人道个歉.

浅谈Java--内存泄漏

- - ITeye博客
      JAVA的垃圾回收机制,让许多程序员觉得内存管理不是很重要,但是内存内存泄露的事情恰恰这样的疏忽而发生,特别是对于Android开发,内存管理更为重要,养成良好的习惯,有利于避免内存的泄漏..     这里可以把许多对象和引用看成是有向图,顶点可以是对象也可以是引用,引用关系就是有向边.

Java 内存模型 JMM

- - 码蜂笔记
JMM,Java Memory Model,Java 内存模型. 什么是内存模型,要他何用. 假定一个线程为变量var赋值: var = 3;,内存模型要回答的问题是:在什么条件下,读取变量var的线程可以看到 3这个值. 如果缺少了同步,线程可能无法看到其他线程操作的结果. 导致这种情况的原因可以有:编译器生成指令的次序可以不同于源代码的“显然”版本,编译器还会把变量存储在寄存器而不是内存中;处理器可以乱序或并行执行指令;缓存会改变写入提交到主存得到变量的次序;存储在处理器本地缓存中的变量对其他处理器不可见 等等.

Java的内存泄露

- - Java译站
Java有垃圾回收,因此不会出现内存泄露. 尽管Java的确有垃圾回收器来回收那些不用的内存块,但你不要指望它能够点铁成金. GC减轻了开发人员肩上的负担,而原本的那些工作非常容易出错,不过并不是所有内存分配的问题它都能够解决. 更糟糕的是,Java的设计允许它可以欺骗GC,使得它能够保留一些程序已经不再使用的内存.

Java的内存机制

- - Java - 编程语言 - ITeye博客
1.Java的内存机制.  Java 把内存划分成两种:一种是栈内存,另一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后(比如,在函数A中调用函数B,在函数B中定义变量a,变量a的作用域只是函数B,在函数B运行完以后,变量a会自动被销毁.

Java 内存溢出排查

- - ImportNew
Java OOM 毫无疑问是开发人员常见并且及其痛恨的问题,但是任何服务的开发都没法避免 OOM. 因此,OOM 的排查及定位是每个 Java 工程师都必备的技能. 在使用 scala 开发的一个 web 服务,在用户使用中,经常出现:  java.lang.OutOfMemoryError: Java heap space .