精华内容
下载资源
问答
  • Java堆和栈的区别

    千次阅读 2017-05-24 10:43:51
    Java把内存分成两种,一种叫做内存,一种叫做内存 在函数中定义的一些基本类型的变量对象的引用变量都是在函数的内存中分配。当在一段代码块中定义一个变量时,Java就在中为这个变量分配内存空间,当...

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存

    在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

    堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

    引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!

     

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

      1 内存分配策略 

      按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 

      静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 

      栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。 

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

      2 堆和栈的比较 

      上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈: 

      从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的: 

      在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时. 

      堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~). 

      3 JVM中的堆和栈 

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

      我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的. 

      从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。 

      每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。 

      Java 中的堆和栈 

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

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

      当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 

      堆内存用来存放由new创建的对象和数组。 

      在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 

      在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 

      引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。 

      具体的说: 

      栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,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, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

    展开全文
  • Java 堆和栈详解 -最新整理

    千次阅读 2016-06-30 10:02:17
    在函数中定义的一些基本类型的变量对象的引用变量都在函数的内存中分配。 当在一段代码块定义一个变量时,Java就在中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存...
    

    题引:

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


    引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因!

    ......

    栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。


    正文:


    1. Java中堆栈(stack)和堆(heap)

    (1)内存分配的策略   
        按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.  
      静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不 允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
      栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未 知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟 知的栈一样,栈式存储分配按照先进后出的原则进行分配。


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


    (2)堆和栈的比较  
    上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,从堆和栈的功能和作用来集中比较堆和栈,
    栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:

      在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈 顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程 序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.   


      堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的 优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面 向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花 掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).


    (3)JVM中的堆和栈  
      JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
      我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译 原理中的活动纪录的概念是差不多的.
      从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
        每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程 共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也 就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。

    2.栈和堆的优势

       栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    3. Java中的两种数据类型

    一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

    另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
    复制内容到剪贴板代码:
    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与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

    另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

    4. String是一个特殊的包装类数据。

    即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。

    Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

    5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

    (1)先定义一个名为str的对String类的对象引用变量:String str;

    (2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。

    (3)将str指向对象o的地址。
    值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

    为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。 

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


    注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
    结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。 


    我们再来更进一步,将以上代码改成: 

    String str1 = "abc"; 
    String str2 = "abc"; 
    str1 = "bcd"; 
    System.out.println(str1 + "," + str2); //bcd, abc 
    System.out.println(str1==str2); //false


    这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

    事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

    再修改原来代码: 

    String str1 = "abc"; 
    String str2 = "abc"; 
    
    str1 = "bcd"; 
    
    String str3 = str1; 
    System.out.println(str3); //bcd 
    
    String str4 = "bcd"; 
    System.out.println(str1 == str4); //true


    str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

    我们再接着看以下的代码。 

    String str1 = new String("abc"); 
    String str2 = "abc"; 
    System.out.println(str1==str2); //false 创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 
    
    String str1 = "abc"; 
    String str2 = new String("abc"); 
    System.out.println(str1==str2); //false 创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

     
    以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

    6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。


    7. 结论与建议:

    (1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

    (2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

    (3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

    (4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率


    文章资料参考自:

    http://blog.csdn.net/shimiso/article/details/8601523

    http://bbs.csdn.net/topics/290004554

    http://blog.sina.com.cn/s/blog_65ca444f01011q14.html

    http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html





    展开全文
  • 自己谈谈对java堆和栈的理解

    千次阅读 2013-05-21 19:47:34
    最近看了几篇关于java堆和栈的区别的几个帖子。自己对这两个概念有了一定 了理解。  其实java的堆和栈是一种内存资源。这里提到了内存资源,顺便说一 下其实java内存不仅仅是栈和堆。还有寄存器,常量池。  ...

     

     最近看了几篇关于java堆和栈的区别的几个帖子。自己对这两个概念有了一定

    了理解。
            其实java的堆和栈是一种内存资源。这里提到了内存资源,顺便说一

    下其实java内存不仅仅是栈和堆。还有寄存器,常量池。
            java堆内存是一个运行时数据区域,可以动态分配内存的大小,生存

    周期不必实现告诉编译器,存放对象和数组,优点是动态分配具有灵活性,缺

    点是存取慢。
            java栈内存是一个数据可共享的区域,栈中数据的大小和生存周期是

    确定的。栈中存放的一般是一些基本变量和对象句柄。缺乏灵活性,存取快。
            其实我感觉自己这两句话的总结应付一些面试题是没问题的。比如说

    string str1=new  string(“fish”);new出的是对象,自然在堆。
    string str2=“fish” 是一个变量的引用会存在栈中。所以很明显str1!

    =str2。若str3=“fish”。他也是在栈中而且栈中数据共享所以str2==str3.再

    说string str4= new  string(“fish”) ;虽然str4和str1都是创建对象在

    堆中。但是我没说过堆中数据共享所以,所以他们只是值相同,但是str1!

    =str4在字符串中equals方法我把他理解是比较数值是否相等。所以这样很好理

    解这三个值都是一样的所以相等。
        说到str1!=str4,我想到一个问题就是因为他们是两个独立的对象,有各

    自生命周期。所以他们生命周期是不确定的,因为运行调用而创建,不用而消

    亡。所以会被GC自动回收(java的垃圾回收器)。所以堆中的数据可以通过GC

    自动回收,如果不能回收,会可能出现内存溢出,一般内存溢出可能是短生命

    周期的变量用长生命周期定义导致数据在内存不用,却占据内存资源,所以也

    可以自己手动回收。
    如果是new的对象,说明他的生命周期因为运行而生,不用而亡。说明这样可以

    提高内存效率,不必总是霸占资源。所以现在我们可以知道string str=new

    string(“”);比string str=“”;要好。

    展开全文
  • Java 堆和栈、垃圾回收

    千次阅读 2018-09-23 17:31:19
    Java运行程序包含: Stack: 保存局部变量的值,包括: 1- 保存基本数据类型的值 2-保存类的实例,即区对象的引用 3-保存加载方法时的祯 Heap 用来存储动态产生的数据 1-比如new 出来的...

    简单的解释一下垃圾回收

    Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中

    一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,
    它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。
    进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。
    比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性. 如何确定一个对象是否可以被回收?

    所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象
    在年轻代中经历了N(15)次垃圾回收后仍然存活的对象,就会被放到年老代中。 用于存放静态文件,如今Java类、方法等。

    引用计数算法:判断对象的引用数量 任何引用计数为0的对象实例可以被当作垃圾收集

    1. 标记——清除算法
      1、标记所有需要回收的对象2、标记完成后,清除被标记的对象。

      1. 标记——复制算法

         标记——复制存储算法通过采用双区域交替使用这种方式解决了标记——清除算法中效率低下的问题。
         1.存活的对象将被复制到另外一块区域。2.原先被使用的区域被重置,转为空闲区
        
      2. 标记——整理算法

        标记-复制算法在对象存活率较高的情况下就要进行较多的复制操作,更重要的是该算法浪费一般的内存空间,为了解决该问题,
        出现了标记——整理算法:
        其标记的过程和“标记-清除”算法一样,而整理的过程则是让所有存活的对象都想另外一端移动,然后直接清理掉端边界以外的内存。

      4.分代收集算法

        新生代,大多都会死去《《《《《   标记——复制算法   
        老年代:  标记-清除  标记-复制
      

      5.增量收集算法
      以上所述的算法,都存在一个缺点:在进行垃圾回首时需要暂停当前应用的执行,
      也就是这时候的垃圾回收线程不能和应用线程同时运行
      这也是增量收集算法的目标,即在不中断应用线程的状态下垃圾回收线程也能进行垃圾回收。

    但是垃圾回收的知识堆内存,栈内存是JVM自动管理的,栈的内存都是随着函数的开始执行和结束自动分配,释放的

    堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,
    因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。
    但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例

    栈的优势是,存取速度比堆要快,而且栈数据可以共享。
    但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int,
    short, long, byte, float, double, boolean,
    char)和对象句柄。栈有一个很重要的特殊性,就是存在栈中的数据可以共享
    每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。

    栈和线程的关系

    栈:线程私有,每个线程都会创建一个虚拟机栈,生命周期与线程相同。每个方法被执行的时候就会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。一个方法执行的过程对应着一个栈帧的入栈到出栈过程。

    虚拟机栈的内存大小会直接影响线程创建的数量。
    假定内存中的堆内存的大小不变都是512M,虚拟机栈的大小会直接影响可创建线程数量的大小。虚拟机栈内存越大可创建的数量越小。
    即:JVM内存 = 堆内存 + 线程数量 * 栈内存

    Java运行程序包含:

    1. 栈Stack:

      保存局部变量的值,包括:
      

      1- 保存基本数据类型的值
      2-保存类的实例,即堆区对象的引用
      3-保存加载方法时的祯

    2. 堆Heap

      用来存储动态产生的数据
      

      1-比如new 出来的对象,注意创建出来的对象只包含各自的成员变量
      不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,
      但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

    举例:
    Person p =new Person()
    1- 上面产生了两个东西,一个时变量p ,存放在栈中
    2- 产生了Person对象,存放在堆中
    对象

    1. 寄存器
      JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

    方法区:Method Area

    1. 常量池
      JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。

    2. 代码段
      用来存放从硬盘上读取的源程序代码。

    3. 数据段
      用来存放static定义的静态成员。
      内存图

    总结
    1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

    2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

    运行

    ===========================================================================================================================================

    1. JVM自动寻找main方法,执行第一句代码,创建一个Test类的实例,在栈中分配一块内存,存放一个指向堆区对象的指针110925。

    2. 创建一个int型的变量date,由于是基本类型,直接在栈中存放date对应的值9。

    3. 创建两个BirthDate类的实例d1、d2,在栈中分别存放了对应的指针指向各自的对象(对象在堆中)。他们在实例化时调用了有参数的构造方法,因此对象中有自定义初始值。

    4. 但是注意有一个change1()方法。
      调用test对象的change1方法,并且以date为参数。JVM读到这段代码时,检测到i是局部变量,因此会把i放在栈中,并且把date的值赋给i。

    5. change1方法执行完毕,立即释放局部变量i所占用的栈空间。

    6. 但是如果一个方法必须change2(Person p)需要的是一个对象,而且这个对象是在方法区域内产生的,这时内存情况又该如何分配?
      change2方法中又实例化了一个Person对象,并且赋给p。在内部执行过程是:在堆区new了一个对象,并且把该对象的指针保存在栈中的p对应空间

    7. change2方法执行完毕,立即释放局部引用变量b所占的栈空间,注意只是释放了栈空间,堆空间要等待自动回收。

    8. 如果Person p1 =new Person()
      p1.name =“张三”;
      test.changName(p1);传入的是一个对象
      但是:
      public void changeName (Person bb){
      bb.setName =“李四”
      }
      并没有像6中那样创建一个对象,这时内存该如何分配呢?

    9. 调用test实例的change3方法,以实例d2为参数。同理,JVM会在栈中为局部引用变量b分配空间,并且把d2中的指针存放在b中,此时d2和b指向同一个对象。再调用实例b的setDay方法,其实就是调用d2指向的对象的setDay方法。
      调用实例b的setDay方法会影响d2,因为二者指向的是同一个对象。
      change3方法执行完毕,立即释放局部引用变量b。

      public  void change2 (Person p2){
            p2 =new Person();
            p2.setName("李四");
            System.out.println("change2执行:"+p2.toString());
        }
        public  void change3 (Person p3){
            p3.setName("李四");
            System.out.println("change3执行"+p3.toString());
        }
        public static void main(String[] args) {
            Person p =new Person();
            p.setName("张三");
            System.out.println("1111111111111:"+p.toString());
            p.change2(p);
            System.out.println("change2之后:"+p.toString());
            p.change3(p);
            System.out.println("change3执行"+p.toString());
        }
    

    输出结果:

    1111111111111:Person{name='张三'}
    change2执行:Person{name='李四'}
    change2之后:Person{name='张三'}
    change3执行Person{name='李四'}
    change3执行Person{name='李四'}
    
    

    以上就是Java程序运行时内存分配的大致情况。其实也没什么,掌握了思想就很简单了。无非就是两种类型的变量:基本类型和引用类型。二者作为局部变量,都放在栈中,基本类型直接在栈中保存值,引用类型只保存一个指向堆区的指针,真正的对象在堆里。作为参数时基本类型就直接传值,引用类型传指针。

    小结:

    1.分清什么是实例什么是对象。Class a= new Class();此时a叫实例,而不能说a是对象(new Class()才是对象)。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

    2.栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

    3.以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

    4.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

    以上分析只涉及了栈和堆,还有一个非常重要的内存区域:常量池,这个地方往往出现一些莫名其妙的问题。常量池是干嘛的上边已经说明了,也没必要理解多么深刻,只要记住它维护了一个已加载类的常量就可以了。接下来结合一些例子说明常量池的特性。

    预备知识:

    基本类型和基本类型的包装类。

    基本类型有:byte、short、char、int、long、boolean。

    基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。

    注意区分大小写。

    二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。

    因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。
    什么是成员变量?成员方法?


    JVM 详解

    jvm

    堆栈上面分析过了

    方法区:

    (1)被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。
    被Java虚拟机描述为堆的一个逻辑部分。
    习惯是也叫它永久代(仅仅是因为HotSpot虚拟机选择把GC分代收集扩展至方法区);
    (2)垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。
    (3)会有异常OutOfMemoneyError;线程私有区域

    static 修饰的静态变量,什么时候被回收?

    在不同的类和包中都可以使用.
    类被加载的时候,静态变量被分配内存,并且在虚拟机中单独占用内存,静态变量在类被卸载的时候才会被销毁,而类只有在进程结束的时候才会被卸载,也就是说被static修饰的静态变量只有在进程被销毁的时候才会被回收

    static一般用于公具类,所有人都可以访问,final是不能更改的意思

    private static final String configFileName = “fastDFS/client.properties”;

    程序计数器:

    (1)当前线程所执行的字节码指令的行号指示器,如分支、跳转、循环、异常处理、线程恢复都依赖程序计数器实现;
    (2)Java多线程是通过线程轮流切换并分配CPU时间片来执行的,为了线程切换后能恢复到正确的位置,
    所以每个线程都有一个单独的程序计数器,所以程序计数器是私有的; (3)Jvm没有规定OutOfMemory的区块;

    Java虚拟机栈:

    (1)为执行Java方法服务‘
    (2)当线程创建的时候,为线程分配一块内存区域,在线程执行的过程中,
    每个方法的执行都会创建一个栈帧,用于存放局部变量表、操作栈、动态链接,方法出口等。
    每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程; (3)会有两种异常StackOverFlowError和
    OutOfMemoneyError。 当线程请求栈深度大于虚拟机所允许的深度就会抛出StackOverFlowError错误;
    虚拟机栈动态扩展,当扩展无法申请到足够的内存空间时候,抛出OutOfMemoneyError; (4)它是线程私有的,生命周期与线程相同;

    局部变量表存放了编译期可知的各种基本数据类型
    (boolean、byte、char、short、int、float、long、double)、对象引用

    本地方法栈:

    (1)与java虚拟机栈所发挥的作用非常相似,它们之间的区别在于java虚拟机栈执行java方法服务的, 本地方法栈是执行本地方法服务的

    运行时常量池

    运行时常量池(Runtime Constant Pool)是方法区的一部分。 Class 文件中除了有

    类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool

    Table), 用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放

    到方法区的运行时常量池中。

    展开全文
  • Java虚拟机:Java堆和栈的详细区别

    万次阅读 2019-04-12 13:02:15
    一、Java把内存划分成两种:一种是内存,一种是内存。 在函数中定义的一些基本类型的变量对象的引用变量都在函数的内存中分配。 当在一段代码块定义一个变量时,Java就在中为这个变量分配内存空间,当...
  • 本来打算自己写个总结,但是看了一篇文章后,自觉很难超出其文,就直接copy过来啦 ,然后本地写了大概笔记,算是...Java垃圾回收概况Java内存区域Java对象的访问方式Java内存分配机制Java GC机制垃圾收集器 Java垃圾
  • Java实现堆和栈

    2009-05-21 11:24:00
    实现堆 从1输入... / /我讨厌数组0  从1读出 //0可以作为指针点.可以从1开始 实现栈 从1输入...... 搞个计数器.还是从1开始加 从计数器读出. //也就是末尾读出 用高级语言实现了低级的东西...
  • 程序计数器:可以看做当前线程运行所执行的...的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一个线程中的指令。为了使线程切换后能够恢复到正确的 位置,程序计数器必须是线程私有的,独立存储且各线程之
  • 原文出处:http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.htmlJava把内存分成两种,一种叫做内存,一种叫做内存在函数中定义的一些基本类型的变量对象的引用变量都是在函数的内存中分配。...
  • Java堆栈和常量池详解

    千次阅读 2019-02-26 18:49:47
    Java堆栈和常量池详解 1. (stack)与堆(heap)都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置或堆。 2. 的优势是,存取速度比堆要快,仅次于直接位于CPU中的...
  • Java虚拟机中的堆和栈前言什么是JVMJava程序执行流程运行时数据区PC(program counter) Register(程序计数器)Heap(堆)模拟堆内OutOfMemoryErrorMethod Area(方法区)Run-Time Constant Pool(运行时常量池)字面量String...
  • Java 内存管理(堆和栈)及 垃圾回收算法一.Jvm虚拟机内存简介1.1 Java运行时内存区1.2 线程私有的如下:1.3 线程共享的如下:二.Java 栈和堆2.1 堆栈的概念和特点2.2 栈与堆的异同2.3 举个例子另注:三.垃圾回收...
  • java堆、常量池

    2018-01-30 15:53:32
    java堆 突然在网上看到一篇关于java堆栈共享问题帖子,所以回忆一下java堆栈。首先看看那个帖子 首先做一下解答,堆是所有线程共享的内存区域,是每个线程独享的,所以那篇博文肯定是错误的。 其次呢博文...
  • java堆和栈和方法区的区别

    千次阅读 2019-05-09 16:44:26
    堆和栈很好理解,这里讲下方法区 方法区通俗点理解就是虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中, 方法区的好处: 常量池是为了避免频繁的创建和销毁对象而影响系统性能...
  • Java里的堆和栈(系统方面的堆和栈)3.1 常见概念3.2 总结4. 申请响应、申请限制4.1 申请响应4.2 申请限制5. 数据存储 转自知乎,原文链接。我对原回答进行了排版整理。 1. 前言 先说一下栈和堆栈,我们听老一辈的...
  • 举例讲解JAVA中的堆和栈

    千次阅读 2014-05-03 22:20:56
    Java堆和堆栈的区别  1. (stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理和堆,程序员不能直接地设置或堆。 (降低内存或者说是程序方面的内存泄露等问题出现的几率?...
  • java 运行时内存分配 堆和栈区别 Java 内存区域补充 线程共享的区域有 Java 堆(java heap) 和 方法区域(method area) 回收内存也主要针对这两个区域 java 堆 所有的对象实例以及数组都在堆上分配, 堆可以处于...
  • 一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register)。 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。...
  • java堆、堆栈的区别

    千次阅读 2017-10-19 16:52:42
    与C++不同,Java自动管理栈和堆,程序员不能直接地设置。   2. 的优势是,存取速度比要快,仅次于直接位于CPU中的寄存器。但缺点是,存在中的数据大小与生存期必须是确定的,缺乏灵活性。另外,...
  • 【JVM】Java内存详解:堆和栈的区别

    千次阅读 2017-03-27 17:53:20
    又回到了堆和栈这个话题,在很早之前就研究过,只知道这两种数据结构一个是先进后出,一个是先进先出,借这个机会,再细致研究一下,对比二者的不同:Java把内存划分为两种:一种是栈内存,一种是堆内存。...
  • JAVA内存处理--堆和栈

    千次阅读 2010-08-03 13:16:00
    1. 容易被搞晕的--堆和栈 由于"堆"和"栈"这两个概念是看不见摸不着的东西,让很多程序员都整不明白是怎么回 事,其实这两个概念也没有什么好研究的,因为堆和栈程序员根本没有办法控制其具体内容。 我们只需要...
  • 四、堆和栈的概念和区别 五、浅谈Java反射机制 一、JDK 和 JRE 的区别? JDK:java development kit (java开发工具) JRE:java runtime environment (java运行时环境) JVM:java v...
  • 初学者对java数组中栈和堆的认识

    千次阅读 2017-05-19 12:54:58
    java数组中的堆和栈
  • Java堆、方法区、常量池

    千次阅读 2016-03-11 01:57:38
    1 堆与Java的数据根据不同的使用情况,有不同的分类,接下来先简单概括一下各种数据类别(不是类型)的内存分配情况,首先帮助区分一下java堆和java: 基础数据类型(Value type)直接在(stack)空间分配,方法的...
  • Java中对象存储位置 - 堆和栈

    千次阅读 2016-08-09 11:00:33
    与C++不同,Java自动管理栈和堆,程序员不能直接地设置。 2. 的优势是,存取速度比要快,仅次于直接位于CPU中的寄存器。但缺点是,存在中的数据大小与生存期必须是确定的,缺乏灵活性。另外,数据...
  • JAVA中的栈和堆

    万次阅读 多人点赞 2019-05-29 15:22:56
    JAVA在程序运行时,在内存中划分5片空间进行数据的存储。分别是:1:寄存器。2:本地方法区。3:方法区。...以下是这几天栈和堆的学习记录心得。得些记录下来。以后有学到新的,会慢慢补充。 ...
  • Java 、方法区的区别

    万次阅读 2016-11-21 12:49:10
    结构化语言里函数(子程序)调用最方便的实现方式就是用,以至于现在绝大部分芯片都对提供芯片级的硬件支持,一条指令即可搞定的pop操作。的好处是:方便、快、有效避免内存碎片化。的问题是:不利于管理...
  • 实例理解Java中的堆和栈

    千次阅读 2008-02-10 10:42:00
    首先理解一下Java中内存的管理方法,Java把内存划分为两种,内存和栈内存。 内存是用来储存数组和对象的内存空间,栈内存是用来存放基本数据类型和对象的引用。栈内存中信息会在生命周期结束时自动释放,而...
  • 堆和栈Java数据结构)

    千次阅读 2014-03-24 15:41:50
    常见使用场景: (英语:heap)亦被称为:优先队列(英语:priority queue),是计算机科学中一类特殊的数据结构的统称。通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业...
  • 我是看到了大佬的博客还能这样解析,我们来解析一下思路并扩展一下,传送门:java实现公式解析 1. Stack的介绍 (stack)在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。是一种数据结构,它按照...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,685
精华内容 62,274
关键字:

java堆和栈的实现

java 订阅