1.變量
在程序運行過程中,其值能被改變的量稱為變量。在Java中,所有的變量必須聲明才能使用,聲明方式為:變量類型 變量名;例如:int age,聲明了一個int類型的age變量。變量在使用前進行聲明是為了告訴編譯器這個變量的數據類型,這樣編譯器才能知道分配多大的內存空間給它,以及它能存放什么樣的類型變量。
根據代碼能訪問該變量的區域將變量分為成員變量和局部變量。成員變量在整個類中都有效,類的成員變量又分為靜態變量和實例變量。靜態變量是被static修飾的變量,有效范圍可以跨類,使用類名.靜態變量名訪問。局部變量是在方法體中定義的變量,只在當前代碼塊中有效,即局部變量的生命周期取決於當前方法的調用。結合JVM來看,成員變量存放在運行時數據區的方法區中,局部變量存放在Java虛擬機棧中。
2.對象和變量之間的關系
類是封裝對象的屬性和行為的載體,而對象的屬性以成員變量的形式存在。對象的方法以成員方法的形式存在,在成員方法內定義的變量為局部變量。
3.對象的創建過程
在代碼中,new操作符調用構造方法創建對象。那么虛擬機中對象是怎樣創建的?
①虛擬機在遇到new指令時,首先檢查該指令參數是否能在方法區的常量池中定位到一個符號,並且檢查這個符號引用是否已經被類加載,解析和初試化過,若沒有,應先進行相應的類加載過程。
- 加載是取得類的二進制流,並且將類信息轉換為方法區的運行時數據結構,最后在堆中生成一個java.lang.Class對象,用作訪問方法區中該類的信息的入口。
- 解析是將常量池內的符號引用轉換為直接引用。
- 初始化階段執行類中定義的Java代碼程序,包括為變量賦值(不是賦默認值)。
②為新生對象分配內存
如何為對象確認內存大小?對象在堆內存中的存儲區域包括三部分,對象頭,實例數據以及對齊填充。對象頭中主要包括兩種信息,一種用於存儲對象自身的運行時數據,比如哈希碼,GC分代年齡,鎖狀態標志等。另一種存儲類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針確定這個對象是哪個類的實例。實例數據是對象真正存儲的有效信息,也就是程序代碼中定義的各種類型的字段信息。對齊填充不是必須的。由於JVM系統要求對象的起始地址必須是8字節的整數倍,也就是說對象大小是8字節的倍數,由於對象頭的大小剛好是8字節倍數,因此有的實例數據部分需要對齊填充來占位。
③將分配到的內存空間初始化為零值(對象頭不包括)。
④對對象頭進行必要的設置,例如這個對象是哪個類的實例,如何才能找到元數據信息,對象的哈希碼,對象的GC分代年齡等信息。
自此,從虛擬機視角來看,一個新的對象就產生了。接下來,虛擬機在new指令之后執行<init>方法,將對象按照代碼中的值進行初始化。
4.對象的訪問定位
建立對象是為了使用,使用之前必須先要找到該對象,即對對象的訪問定位。目前的主流方式有使用句柄和直接指針兩種定位方式。
如果使用句柄訪問的話,Java堆中會划分出一塊內存區域作為句柄池,reference中存的就是對象的句柄地址,句柄地址中存儲了到對象實例數據的地址信息以及到對象類型數據的地址信息,分別指向Java堆實例池中的對象實例數據和方法區中的對象類型數據。
如果使用直接指針的定位方式,那么在Java堆對象的布局中必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象地址。
5.new對象放在循環內和循環外
5.1 先來看看對象的創建。如下的示例代碼:
public class VariableTest { int a; static int b=12; public VariableTest(){ System.out.println("構造方法執行:"); } public static void main(String[] args){ //vt-->對象的引用,存儲在Java棧的本地變量表中 // new VariableTest()-->對象,存儲在堆中,每次new VariableTest() new出來的對象都不一樣。 VariableTest vt=new VariableTest(); VariableTest vt1=new VariableTest(); System.out.println(vt); //com.java.Variable.VariableTest@15db9742 System.out.println(new VariableTest()); //@6d06d69c System.out.println(new VariableTest()); //7852e922 System.out.println(new VariableTest()); //@4e25154f System.out.println(vt1); //@70dea4e } }
在 VariableTest類中創建了構造方法public VariableTest(){},程序中使用new 構造方法就創建了對象。
VariableTest vt=new VariableTest(); vt為對象的引用,也就是對象的訪問定位中提到的Java棧本地變量表中存儲的reference引用,new VariableTest(); new指針調用構造方法創建一個對象,也就是圖中Java堆中的實例數據對象,vt對象引用指向實例數據對象。
接着代碼中又new出一個對象,VariableTest vt1=new VariableTest(); 打印出vt和vt1的值是不一樣的,也就是說vt和vt1是Java棧本地變量表中存儲的兩個不同的reference引用,那么vt1 后面的new VariableTest(); 和vt 后面的new VariableTest();是否是同一個對象呢?打印出該對象,發現只要new VariableTest();就會創建一個對象,每次創建的對象都是不同的。
5.2 new對象在循環內和循環外
如下的示例代碼:
public static void m1(){ HashMap<Integer,String> hashMap=null; hashMap=new HashMap<Integer,String>(); for(int j=0;j<b;j++){ hashMap.put(j,"A"); } for(Map.Entry<Integer,String> entry:hashMap.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue()); } }
在VariableTest類中新建了一個成員方法m1(),在方法內創建了一個HashMap<Integer,String>類型的對象hashMap,for循環中存入數據然后遍歷,整個過程只new出一個對象,所以for循環每次存數據都是向同一個hashMap中存入數據。遍歷結果:
0:A 1:A 2:A 3:A 4:A 5:A 6:A 7:A 8:A 9:A 10:A 11:A
若改為入下的示例代碼:
public static void m1(){ HashMap<Integer,String> hashMap=null; for(int j=0;j<b;j++){ hashMap=new HashMap<Integer,String>(); hashMap.put(j,"A"); } for(Map.Entry<Integer,String> entry:hashMap.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue()); } }
將new HashMap<Integer,String>();放在循環內,之前分析每次new出來的對象都是不同的,所以在for循環的代碼塊中,一次循環結束,該次循環new出來的對象就已經銷毀。也就是提前聲明的hashMap引用每次指向的對象都是不同的,一次for循環結束new出來的對象銷毀之后hashMap引用指向下一次for循環new出來的 HashMap<Integer,String>()對象。遍歷結果:
11:A