在说堆和栈之前,我们先说一下JVM(虚拟机)内存的划分:

      Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以要单独进行管理。

      JVM内存的划分有五片:

       1.   寄存器;

       2.   本地方法区;

       3.   方法区:类信息、类变量(静态变量和常量)、方法 

       4.   栈内存:局部变量 

       5.   堆内存:对象、成员变量 

       我们重点来说一下堆和栈:

       栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。

       堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。

      下面我们通过一个图例详细讲一下堆和栈:

      比如主函数里的语句   int [] arr=new int [3];在内存中是怎么被定义的:

      主函数先进栈,在栈中定义一个变量arr,接下来为arr赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过new关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:

                                                  (转)堆和栈的概念和区别    HeapOutOfMemory和StackOverflow解释_JVM性能调优

     那么堆和栈是怎么联系起来的呢?

     我们刚刚说过给堆分配了一个地址,把堆的地址赋给arr,arr就通过地址指向了数组。所以arr想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫他基本数据类型,而叫引用数据类型。称为arr引用了堆内存当中的实体。(可以理解为c或c++的指针,Java成长自c++和c++很像,优化了c++)                                                                (转)堆和栈的概念和区别    HeapOutOfMemory和StackOverflow解释_c++_02

     

              如果当int [] arr=null;

              arr不做任何指向,null的作用就是取消引用数据类型的指向。

              当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为Java有一个自动回收机制,(而c++没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以Java在内存管理上优于c++)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。

所以堆与栈的区别很明显:

            1.栈内存存储的是局部变量而堆内存存储的是实体;

            2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

            3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

补充说明:

         (1)当程序运行时,首先通过类装载器加载字节码文件,经过解析后装入方法区!在方法区中存了类的各种信息,包括类变量、常量及方法。对于同一个方法的调用,同一个类的不同实例调用的都是存在方法区的同一个方法。类变量的生命周期从程序开始运行时创建,到程序终止运行时结束! 

         (2)当程序中new一个对象时,这个对象存在堆中,对象的变量存在栈中,指向堆中的引用!对象的成员变量都存在堆中,当对象被回收时,对象的成员变量随之消失! 

         (3)当方法调用时,JVM会在栈中分配一个栈桢,存储方法的局部变量。当方法调用结束时,局部变量消失!

          类变量:属于类的属性信息,与类的实例无关,多个实例共用同一个类变量,存在与方法区中。类变量用static修饰,包括静态变量和常量。静态变量有默认初始值,常量必须声明同时初始化。

         成员变量:属于实例的变量,只与实例有关,写在类下面,方法外,非static修饰。成员变量会随着成员的创建而生存,随着成员的回收而销毁。

         局部变量:声明在方法中,没有默认初始值,随着方法的调用而创建,存储于栈中,随着方法调用的结束而销毁。

outofmemory/stackoverflow说明:


HeapOutOfMemory


堆溢出 情况多见于对象过多,存在多余引用,使对象未及时释放


 





[java] view plain copy

 


  1. public class Miao {  
  2.     public static void main(String[] args) throws Exception{  
  3.         ArrayList<String> strs = new ArrayList<>(10000_0000);  
  4.         for(int i = 0 ;i <= 10000_0000; ++ i){  
  5.             strs.add(Integer.toString(i));  
  6.             if(i % 10000 == 0){  
  7.                 System.out.println(i);  
  8.             }  
  9.         }  
  10.     }  

Stack OutOfMemory Stack OverFlow


栈溢出 一般与方法递归次数过多或者方法中有产生大量数据的循环有关





[java] view plain copy

 


  1. public class Miao {  
  2.     public static void main(String[] args) throws Exception{  
  3.         new Miao().miao();  
  4.     }  
  5.   
  6.     public void miao(){  
  7.         long time = System.currentTimeMillis();  
  8.         miao();  
  9.     }  
  10. }