内存分配及变量存储位置

程序运行时,有六个地方能够保存数据html

1.寄存器:java

这里是最快的保存区域,由于它位于处理器内部。寄存器是内存阶层中的最顶端,也是系统得到操做资料的最快速途径。寄存器的数量有限,是根据须要由编译器分配的。咱们对此没有控制权限,不可能在本身的程序中找到任何存在的踪影。数组

2.堆栈:安全

存放基本类型的数据和对象的引用(变量),但对象自己不在栈中,而在堆中。app

堆栈中的物体具备一个特性: 最后一个放入堆栈中的物体老是被最早拿出来, 这个特性一般称为后进先出(LIFO)队列。 堆栈中定义了一些操做。 两个最重要的是PUSH和POP。 PUSH操做在堆栈的顶部加入一 个元素。POP操做相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一。在常规的RAM(随机访问存储器)区域 ,经过堆栈指针得到处理的直接支持。堆栈指针若向下移,会建立新的内存(PUSH),向上移会释放那些内存(POP)。速度仅次于寄存器。建立程序时,java编译器必须准确地知道堆栈内保存的全部数据的“长度”以及“存在时间”。这是因为它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,因此尽管有些java数据要保存在堆栈里——特别是对象句柄,但java对象并不放到其中。jvm

3.堆:ide

存放new 产生的数据。是一种常规用途的内存池,也在RAM区域,其中保留java对象。和堆栈不一样:“内存堆”或“堆”最吸引人的地方在于编译器没必要知道要从堆里分配多少存储空间,也没必要知道存储的数据要在堆里停留多长的时间。所以,用堆保存数据时会获得更大的灵活性。要求建立一个对象时,只需用new命令编制相碰的代码便可。执行这些代码时,会在堆里自动进行数据的保存。固然,为达到这种灵活性,必然会付出必定的代价:在堆里分配存储空间时会花掉更长的时间。函数

4.静态域:测试

存放在对象中用static定义的静态成员。静态是指位于固定位置。程序运行期间,静态存储的数据随时等候调用。用static修饰对象的特定元素是静态的。但java对象永远不会在静态存储空间。优化

5.常量池:

存放常量。常量值放置在程序代码内部,是安全的。由于他们永远不会改变,有的常数须要严格的保护,因此可考虑将它们置入只读存储器(ROM)

6.非RAM存储:硬盘等永久存储空间。若数据彻底独立于一个程序以外,则程序不运行时仍可存在,并在程序的控制范围以外。其中两个最主要的例子即是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,一般会发给另外一台机器,而对于固定对象,对象保存在磁盘中。即便程序停止运行,它们仍可保持本身的状态不变。对于这些类型的数据存储,一个特别有用的技艺就是它们能存在于其余媒体中,一旦须要,甚至能将它们恢复成普通的、基于RAM的对象。

 

java内存分配中的栈(后进先出)

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

当代码定义一个变量时,java就在栈中为这个变量分配内存空间(指针向下PUSH栈顶 大小+1),当该变量退出做用域后,java会自动释放掉为该变量分配的内存空间(指针向上POP栈顶 大小-1),该内存空间能够当即另作他用。栈中的数据大小和生命周期都是能够肯定的。当没有引用指向数据时,这个数据就会消失。

1.每一个线程包含一个栈区,栈中只保存基本数据类型的对象和自定义对象的引用(不是对象),对象都放在堆中。
2.每一个栈中的数据(原始类型和对象引用)都是私有的,其余栈不能访问。

3.栈分为三个部分:基本类型变量区,执行环境上下文,操做指令区(存放操做指令)

 

java内存分配中的堆

堆内存用来存放new建立的对象和数组。在堆中分配的内存,由java虚拟机垃圾回收机制来管理。

堆中产生了一个数组或对象后,还能够在栈中定义一个特殊的变量,让栈中的这个变量取值等于数组或对象在堆中的首地址,栈中的这个变量就变成了数组或对象的引用变量。引用变量就至关因而为数组或对象起的一个名称,之后就能够在程序中使用栈中的引用变量来访问堆中的数组或对象。

引用变量是普通的变量,定义是在栈中分配,引用变量在程序运行到其做用域以外后被释放。而数组和对象自己在堆中分配,即便程序运行到使用new 产生数组或对象的语句所在的代码块以外,数组或对象自己占用的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能在被使用,但仍然占据内存空间,等待垃圾回收机制 在不定时间 回收。java比较占内存的缘由。

1.存储的所有是对象,每一个对象都包含一个与之对应的class的信息,class的目的是获得操做指令。

2.jvm只有一个堆区(heap) 被全部线程共享。堆中不存放基本类型和对象引用,只存放对象自己。

实际上,栈中的变量指向堆内存中的变量,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, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另外一个对象引用变量。

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

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

 

静态域

方法区

1.又叫静态区,跟堆同样,被全部线程共享。方法区含有全部的class和static变量

2.方法区包含的都是在整个程序中永远惟一的元素,如class static变量。

java里面是没有静态变量这个概念的,不信你本身在方法里面定义一个static int i =0;java里只有静态成员变量。它属于类的属性。至于他放在那里?深刻jvm里是翻译为静态方法区的(应该也可叫域吧)。虚拟机的体系结构:堆,方法区,本地方法栈,pc寄存器。而方法区保存的就是一个类的模板,堆是放类的实例的。栈是通常来用来函数计算的。随便找本计算机底层的书都知道了。栈里的数据,函数执行完就不会存储了。这就是为何局部变量每一次都是同样的。就算给他加一后,下次执行函数的时候仍是原来的样子。

 

常量池 (constant pool)

 常量池指的是在编译期被肯定,并被保存在已编译的.class文件中的一些数据。

除了包含代码中所定义的各类基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,好比:

 类和接口的全限定名;

 字段的名称和描述符;

 方法和名称和描述符。

若是是编译期已经建立好(直接用双引号定义的)的就存储在常量池中,若是是运行期(new出来的)才能肯定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

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常量池问题的几个例子

String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );

结果为

true

true

首先知道Java 会确保一个字符串常量只有一个拷贝

由于例子中的 s0和s1中的”kvill”都是字符串常量,它们在编译期就被肯定了,因此s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字 符串由多个字符串常量链接而成时,它本身确定也是字符串常量,因此s2也一样在编译期就被解析为一个字符串常量,因此s2也是常量池中” kvill”的一个引用。因此咱们得出s0==s1==s2;

例2.

String s0="kvill";
String s1=new String("kvill");
String s2="kv" + new String("ill");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );

结果为:

  false

  false

  false

用new String() 建立的字符串不是常量,不能在编译期就肯定,因此new String() 建立的字符串不放入常量池中,它们有本身的地址空间。

s0仍是常量池 中"kvill”的应用,s1由于没法在编译期肯定,因此是运行时建立的新对象”kvill”的引用,s2由于有后半部分 new String(”ill”)因此也没法在编译期肯定,因此也是一个新建立对象”kvill”的应用;明白了这些也就知道为什么得出此结果了。

例三

String a = "a1";
  String b = "a" + 1;
  System.out.println((a == b)); //result = true 
  
  String a = "atrue";
  String b = "a" + "true";
  System.out.println((a == b)); //result = true 
  
  String a = "a3.4";
  String b = "a" + 3.4;
  System.out.println((a == b)); //result = true

JVM对于字符串常量的"+"号链接,将程序编译期,JVM就将常量字符串的"+"链接优化为链接后的值,拿"a" + 1来讲,经编译器优化后在class中就已是a1。在编译期其字符串常量的值就肯定下来,故上面程序最终的结果都为true。

String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false

JVM对于字符串引用,因为在字符串的"+"链接中,有字符串引用存在,而引用的值在程序编译期是没法肯定的,即"a" + bb没法被编译器优化,只有在程序运行期来动态分配并将链接后的新地址赋给b。因此上面程序的结果也就为false。

例四

String a = "ab";
String bb = "b";
String b = "a" + bb;

System.out.println((a == b)); //result = false

分析:JVM对于字符串引用,因为在字符串的"+"链接中,有字符串引用存在,而引用的值在程序编译期是没法肯定的,即"a" + bb没法被编译器优化,只有在程序运行期来动态分配并将链接后的新地址赋给b。因此上面程序的结果也就为false。

例五

String a = "ab";
final String bb = "b";
String b = "a" + bb;

System.out.println((a == b)); //result = true

和例四中惟一不一样的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到本身的常量池中或嵌入到它的字节码流中。因此此时的"a" + bb和"a" + "b"效果是同样的。故上面程序的结果为true。

例六

String a = "ab";
final String bb = getBB();
String b = "a" + bb;

System.out.println((a == b)); //result = false

private static String getBB() 
{  
    return "b";  
 }

JVM对于字符串引用bb,它的值在编译期没法肯定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态链接并分配地址为b,故上面 程序的结果为false

 

关于String 是不可变的

经过上面例子能够得出得知:

  String  s  =  "a" + "b" + "c";

  就等价于String s = "abc";

  String  a  =  "a";

  String  b  =  "b";

  String  c  =  "c";

  String  s  =   a  +  b  +  c;

  这个就不同了,最终结果等于:

StringBuffer temp = new StringBuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();

 由上面的分析结果,可就不难推断出String 采用链接运算符(+)效率低下缘由分析,形如这样的代码:

public class Test {
 
      public static void main(String args[]) {
 
        String s = null;
 
        for(int i = 0; i < 100; i++) {
 
            s += "a";
       }
    } 
}

 每作一次 + 就产生个StringBuilder对象,而后append后就扔掉。下次循环再到达时从新产生个StringBuilder对象,而后 append 字符串,如此循环直至结束。若是咱们直接采用 StringBuilder 对象进行 append 的话,咱们能够节省 N - 1 次建立和销毁对象的时间。因此对于在循环中要进行字符串链接的应用,通常都是用StringBuffer或StringBulider对象来进行 append操做。

  因为String类的immutable性质,这一说又要说不少,你们只 要知道String的实例一旦生成就不会再改变了,好比说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,而后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是由于String的”不可变”产生了不少临时变量,这也就是为何建议用StringBuffer的原 因了,由于StringBuffer是可改变的。

 

String中的final用法和理解

Java代码

  final StringBuffer a = new StringBuffer("111");

  final StringBuffer b = new StringBuffer("222");

  a=b;//此句编译不经过

  final StringBuffer a = new StringBuffer("111");

  a.append("222");// 编译经过

  可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会致使编译期错误。至于它所指向的对象 的变化,final是不负责的。

总结

 栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String,数组.对象等等)但不存放对象内容

 堆中存放使用new关键字建立的对象.

 字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据建立方式不一样定(常量池和堆).有的是编译期就已经建立好,存放在字符串常 量池中,而有的是运行时才被建立.使用new关键字,存放在堆中。

转载自:https://www.cnblogs.com/protected/p/6419217.html

相关文章
相关标签/搜索