• Java中堆、栈、常量池等概念解析


    转自zy77612

    程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:
    (1) 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。


    (2) 栈(stack)。存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。) 驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内 存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时 间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些Java数据要保存在堆栈里——特别是对象 句柄,但Java对象并不放到其中。


    (3) 堆(heap)。存放所有new出来的对象,一种常规用途的内存池(也在RAM区域),其中保 存了Java对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆 里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进 行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!


    (4) 静态存储。存放静态成员(static定义的),这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。


    (5) 常数存储。存放字符串常量和基本类型常量(public static final)。 常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。


    (6) 非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之 外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。 即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成 普通的、基于RAM的对象。Java 1.1提供了对Lightweight persistence的支持。未来的版本甚至可能提供更完整的方案

     

    这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。 
    对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。 
    如以下代码:

    Java代码

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

    这里解释一下黄色这3个箭头,对于通过new产生一个字符串(假设为”china”)时,会先去常量池中查找是否已经有了”china”对象,如果 没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”china”对象的拷贝对象。这也就是有道面试题:String s = new String(“xyz”);产生几个对象?一个或两个,如果常量池中原来没有”xyz”,就是两个。

    对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。 
    如以下代码:

    Java代码

    int i1 = 9;   
    int i2 = 9;   
    int i3 = 9;    
    public static final int INT1 = 9;   
    public static final int INT2 = 9;   
    public static final int INT3 = 9;  

     
    对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。 
    形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。 
    成员变量存储在堆中的对象里面,由垃圾回收器负责回收。 
    如以下代码:

    Java代码

    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 test = new 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()将等待垃圾回收。

  • 相关阅读:
    Web前端可以转行做游戏吗?
    SublimeText3常用快捷键和优秀插件(亲测)
    jQuery编程规范与最佳实践(附带一些个人的笔记)
    outline详解
    几种创建XMLHttpRequest对象的方法
    8条规则图解JavaScript原型链继承原理
    技术跟产品杂谈
    谈一谈URL
    React + Reflux 渲染性能优化原理
    Interesting JavaScript
  • 原文地址:https://www.cnblogs.com/lixiaolun/p/2832774.html
Copyright © 2020-2023  润新知