Java棧與堆詳解

堆:順序隨意 html

棧:後進先出(Last-in/First-Out). java

     Java的堆是一個運行時數據區,類的對象從中分配空間。這些對象經過new、newarray、anewarray和multianewarray等指令創建,它們不須要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優點是能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,由於它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些再也不使用的數據。但缺點是,因爲要在運行時動態分配內存,存取速度較慢. 程序員

    棧的優點是,存取速度比堆要快,僅次於寄存器,棧數據能夠共享。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。棧中主要存放一些基本類型的變量(int, short, long, byte, float, double, boolean, char)和對象句柄。 編程

   

1. 棧(stack)與堆(heap)都是Java用來在Ram中存放數據的地方。與C++不一樣,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/jerryao/archive/2006/07/04/874101.aspx

堆棧(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中全部對象的存儲空間都是在堆中分配的,可是這個對象的引用倒是在堆棧中分配,也就是說在創建一個對象時從兩個地方都分配內存,在堆中分配的內存實際創建這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。

 

static、final修飾符、內部類和Java內存分配

static修飾符

static用來修飾內部類,方法,字段。修飾內部類說明該內部類屬於外部類而不屬於外部類的某個實例。修飾字段說明該字段屬於類而不屬於類實例。修飾方法說明該方法屬於類而不屬於類實例。
        static修飾符可以與屬性、方法和內部類一塊兒使用,表示靜態的。類中的靜態變量和靜態方法可以與類名一塊兒使用,不須要建立一個類的對象來訪問該類的靜態成員,因此,static修飾的變量又稱做「類變量」。

static屬性的內存分配

         一個類中,一個static變量只會有一個內存空間,雖然有多個類實例,但這些類實例中的這個static變量會共享同一個內存空間。

static的變量是在類裝載的時候就會被初始化,即,只要類被裝載,無論是否使用了static變量,都會被初始化。
static的基本規則
  ·一個類的靜態方法只能訪問靜態屬性
  ·一個類的靜態方法不能直接調用非靜態方法
  ·如訪問控制權限容許,static屬性和方法可使用類名加「.」的方式調用,也可使用實例加「.」的方式調用
  ·靜態方法中不存在當前對象,於是不能使用this,也不能使用super
  ·靜態方法不能被非靜態方法覆蓋
  ·構造方法不容許聲明爲static的
  注,非靜態變量只限於實例,並只能經過實例引用被訪問。
靜態初始器——靜態塊
  靜態初始器是一個存在與類中方法外面的靜態塊,僅僅在類裝載的時候執行一次,一般用來初始化靜態的類屬性。 

 

final修飾符
  在Java聲明類、屬性和方法時,可使用關鍵字final來修飾,final所標記的成分具備終態的特徵,表示最終的意思。
  final的具體規則
    ·final標記的類不能被繼承
    ·final標記的方法不能被子類重寫
    ·final標記的變量(成員變量或局部變量)即成爲常量,只能賦值一次
    ·final標記的成員變量必須在聲明的同時賦值,若是在聲明的時候沒有賦值,那麼只有一次賦值的機會,並且只能在構造方法中顯式賦值,而後才能使用
    ·final標記的局部變量能夠只聲明不賦值,而後再進行一次性的賦值
    ·final通常用於標記那些通用性的功能、實現方式或取值不能隨意被改變的成分,以免被誤用
  若是將引用類型(即,任何類的類型)的變量標記爲final,那麼,該變量不能指向任何其它對象,但能夠改變對象的內容,由於只有引用自己是final的。

 
內部類
  在一個類(或方法、語句塊)的內部定義另外一個類,後者稱爲內部類,有時也稱爲嵌套類。
  內部類的特色
    ·內部類能夠體現邏輯上的從屬關係,同時對於其它類能夠控制內部類對外不可見等
    ·外部類的成員變量做用域是整個外部類,包括內部類,但外部類不能訪問內部類的private成員
    ·邏輯上相關的類能夠在一塊兒,能夠有效地實現信息隱藏
    ·內部類能夠直接訪問外部類的成員,能夠用此實現多繼承
    ·編譯後,內部類也被編譯爲單獨的類,名稱爲outclass$inclass的形式

內部類能夠分爲四種
    ·類級:成員式,有static修飾
    ·對象級:成員式,普通,無static修飾
    ·本地內部類:局部式
    ·匿名級:局部式
  成員式內部類的基本規則
    ·能夠有各類修飾符,能夠用4種權限、static、final、abstract定義
    ·如有static限定,就爲類級,不然爲對象級。類級能夠經過外部類直接訪問,對象級須要先生成外部的對象後才能訪問
    ·內外部類不能同名
    ·非靜態內部類中不能聲明任何static成員
    ·內部類能夠互相調用
  成員式內部類的訪問
    內部類訪問外層類對象的成員時,語法爲:
      外層類名.this.屬性
    使用內部類時,由外部類對象加「.new」操做符調用內部類的構造方法,建立內部類的對象。
  在另外一個外部類中使用非靜態內部類中定義的方法時,要先建立外部類的對象,再建立與外部類相關的內部類的對象,再調用內部類的方法。
  static內部類至關於其外部類的static成分,它的對象與外部類對象間不存在依賴關係,所以能夠直接建立。
  因爲內部類能夠直接訪問其外部類的成分,所以,當內部類與其外部類中存在同名屬性或方法時,也將致使命名衝突。因此,在多層調用時要指明。

 
  本地類是定義在代碼塊中的類,只在定義它們的代碼塊中可見。
  本地類有如下幾個重要特性:
    ·僅在定義了它們的代碼塊中可見
    ·可使用定義它們的代碼塊中的任何本地final變量(注:本地類(也能夠是局部內部類/匿名內部類等等)使用外部類的變量,原意是但願這個變量在本地類中的對象和在外部類中的這個變量對象是一致的,但若是這個變量不是final定義,它有可能在外部被修改,從而致使內外部類的變量對象狀態不一致,所以,這類變量必須在外部類中加final前綴定義)
    ·本地類不能夠是static的,裏邊也不能定義static成員
    ·本地類不能夠用public、private、protected修飾,只能使用缺省的
    ·本地類能夠是abstract的

  

匿名內部類是本地內部類的一種特殊形式,即,沒有類名的內部類,並且具體的類實現會寫在這個內部類裏。
  匿名類的規則
    ·匿名類沒有構造方法
    ·匿名類不能定義靜態的成員
    ·匿名類不能用4種權限、static、final、abstract修飾
    ·只能夠建立一個匿名類實例

 
Java的內存分配
  Java程序運行時的內存結構分紅:方法區、棧內存、堆內存、本地方法棧幾種。
  方法區存放裝載的類數據信息,包括:
    ·基本信息:每一個類的全限定名、每一個類的直接超類的全限定名、該類是類仍是接口、該類型的訪問修飾符、直接超接口的全限定名的有序列表。
    ·每一個已裝載類的詳細信息:運行時常量池、字段信息、方法信息、靜態變量、到類classloader的引用、到類class的引用。
  棧內存
    Java棧內存由局部變量區、操做數棧、幀數據區組成,以幀的形式存放本地方法的調用狀態(包括方法調用的參數、局部變量、中間結果……)。
  堆內存
    堆內存用來存放由new建立的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
  本地方法棧內存
    Java經過Java本地接口JNI(Java Native Interface)來調用其它語言編寫的程序,在Java裏面用native修飾符來描述一個方法是本地方法。
  String的內存分配
    String是一個特殊的包裝類數據,因爲String類的值不可變性,當String變量須要常常變換其值時,應該考慮使用StringBuffer或StringBuilder類,以提升程序效率。

 

 

Java內存分配、管理小結  

轉自: http://legend26.blog.163.com/blog/static/13659026020101122103954365/

 

 

首先是概念層面的幾個問題:

  • Java中運行時內存結構有哪幾種?
  • Java中爲何要設計堆棧分離?
  • Java多線程中是如何實現數據共享的?
  • Java反射的基礎是什麼?

而後是運用層面:

  •  引用類型變量和對象的區別?
  • 什麼狀況下用局部變量,什麼狀況下用成員變量?
    數組如何初始化?聲明一個數組的過程當中,如何分配內存?
  • 聲明基本類型數組和聲明引用類型的數組,初始化時,內存分配機制有什麼區?
  • 在什麼狀況下,咱們的方法設計爲靜態化,爲何

   

Java中運行時內存結構

  

   1.1 方法區: 

  

方法區是系統分配的一個內存邏輯區域,是JVM在裝載類文件時,用於存儲類型信息的(類的描述信息)。

  

方法區存放的信息包括:

 

             1.1.1類的基本信息:

  1. 每一個類的全限定名
  2. 每一個類的直接超類的全限定名(可約束類型轉換)
  3. 該類是類仍是接口
  4. 該類型的訪問修飾符
  5. 直接超接口的全限定名的有序列表

 

             1.1.2已裝載類的詳細信息

 

  1.  運行時常量池

    在方法區中,每一個類型都對應一個常量池,存放該類型所用到的全部常量,常量池中存儲了諸如文字字符串、final變量值、類名和方法名常量。它們以數組形式經過索引被訪問,是外部調用與類聯繫及類型對象化的橋樑。(存的多是個普通的字符串,而後通過常量池解析,則變成指向某個類的引用)

  2.  字段信息

    字段信息存放類中聲明的每個字段的信息,包括字段的名、類型、修飾符。

    字段名稱指的是類或接口的實例變量或類變量,字段的描述符是一個指示字段的類型的字符串,如private A a=null;a爲字段名,A爲描述符,private爲修飾符

  3.  方法信息

    類中聲明的每個方法的信息,包括方法名、返回值類型、參數類型、修飾符、異常、方法的字節碼。

    (在編譯的時候,就已經將方法的局部變量、操做數棧大小等肯定並存放在字節碼中,在裝載的時候,隨着類一塊兒裝入方法區。)

 

 

在運行時,JVM從常量池中得到符號引用,而後在運行時解析成引用項的實際地址,最後經過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯繫起來。
  1.  靜態變量

    這個沒什麼好說的,就是類變量,類的全部實例都共享,咱們只需知道,在方法區有個靜態區,靜態區專門存放靜態變量和靜態塊。

  2.  到類classloader的引用到該類的類裝載器的引用。
  3.  到類class的引用虛擬機爲每個被裝載的類型建立一個class實例,用來表明這個被裝載的類。 

  

    由此咱們能夠知道反射的基礎

 

 

在裝載類的時候,加入方法區中的全部信息,最後都會造成Class類的實例,表明這個被裝載的類。方法區中的全部的信息,都是能夠經過這個Class類對象反射得到。咱們知道對象是類的實例,類是相同結構的對象的一種抽象。同類的各個對象之間,實際上是擁有相同的結構(屬性),擁有相同的功能(方法),各個對象的區別只在於屬性值的不一樣
    一樣的,咱們全部的類,其實都是Class類的實例,他們都擁有相同的結構-----Field數組、Method數組。而各個類中的屬性都是Field屬性的一個具體屬性值,方法都是Method屬性的一個具體屬性值。

 

  

 在運行時,JVM從常量池中得到符號引用,而後在運行時解析成引用項的實際地址,最後經過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯繫起來。

  

 

1.2Java棧

 

 

JVM棧是程序運行時單位,決定了程序如何執行,或者說數據如何處理。

 

  

Java中,一個線程就會有一個線程的JVM棧與之對應,由於不過的線程執行邏輯顯然不一樣,所以都須要一個獨立的JVM棧來存放該線程的執行邏輯。

  

對方法的調用:

            Java棧內存,以的形式存放本地方法調用狀態,包括方法調用的參數局部變量、中間結果等(方法都是以方法幀的形式存放在方法區的),每調用一個方法就將對應該方法的方法幀壓入Java棧,成爲當前方法幀。當調用結束(返回)時,就彈出該幀。

  

 

這意味着:

 

            在方法中定義的一些基本類型的變量和引用變量都在方法的棧內存中分配。當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當超過變量的做用域後(方法執行完成後),Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做它用--------同時,由於變量被釋放,該變量對應的對象,也就失去了引用,也就變成了能夠被gc對象回收的垃圾。

  

 

所以咱們能夠知道成員變量與局部變量的區別:

  

 

局部變量,在方法內部聲明,當該方法運行完時,內存即被釋放。
成員變量,只要該對象還在,哪怕某一個方法運行完了,仍是存在。
從系統的角度來講,聲明局部變量有利於內存空間的更高效利用(方法運行完即回收)。
成員變量可用於各個方法間進行數據共享。

  

Java 棧內存的組成:
局部變量區、操做數棧、幀數據區組成。
(1):局部變量區爲一個以字爲單位的數組,每一個數組元素對應一個局部變量的值。調用方法時,將方法的局部變量組成一個數組,經過索引來訪問。若爲非靜態方法,則加入一個隱含的引用參數this,該參數指向調用這個方法的對象。而靜態方法則沒有this參數。所以,對象沒法調用靜態方法。

 

由此,咱們能夠知道,方法何時設計爲靜態,何時爲非靜態?

 

 

前面已經說過,對象是類的一個實例,各個對象結構相同,只是屬性不一樣。
而靜態方法是對象沒法調用的。
因此,靜態方法適合那些工具類中的工具方法,這些類只是用來實現一些功能,也不須要產生對象,經過設置對象的屬性來獲得各個不一樣的個體。

 
(2):操做數棧也是一個數組,可是經過棧操做來訪問。所謂操做數是那些被指令操做的數據。當須要對參數操做時如a=b+c,就將即將被操做的參數壓棧,如將b 和c 壓棧,而後由操做指令將它們彈出,並執行操做。虛擬機將操做數棧做爲工做區。
(3):幀數據區處理常量池解析,異常處理等

 

1.3 java堆 

 

      java的堆是一個運行時的數據區,用來存儲數據的單元,存放經過new關鍵字新建的對象數組,對象從中分配內存。
      在堆中聲明的對象,是不能直接訪問的,必須經過在棧中聲明的指向該引用的變量來調用。引用變量就至關因而爲數組或對象起的一個名稱,之後就能夠在程序中使用棧中的引用變量來訪問堆中的數組或對象。
   

     由此咱們能夠知道,引用類型變量和對象的區別: 

  

聲明的對象是在堆內存中初始化的, 真正用來存儲數據的。不能直接訪問。

引用類型變量是保存在棧當中的,一個用來引用堆中對象的符號而已(指針)。

   

 

堆與棧的比較
JAVA堆與棧都是用來存放數據的,那麼他們之間到底有什麼差別呢?既然棧也能存放數據,爲何還要設計堆呢?

   

1.從存放數據的角度:

       前面咱們已經說明:

  

      棧中存放的是基本類型的變量or引用類型的變量

        堆中存放的是對象or數組對象.

        在棧中,引用變量的大小爲32位,基本類型爲1-8個字節。
       可是對象的大小和數組的大小是動態的,這也決定了堆中數據的動態性,由於它是在運行時動態分配內存的,生存期也沒必要在編譯時肯定,Java 的垃圾收集器會自動收走這些再也不使用的數據。

   

2.從數據共享的角度:

 

     1).在單個線程類,棧中的數據可共享

 

    例如咱們定義:

Java代碼
  1. int a=3;  
  2. int b=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的狀況。

 

    而若是咱們定義: 

Java代碼
  1. Integer a=new Integer(3);//(1)  
  2. Integer b=new Integer(3);//(2)  

Integer a=new Integer(3);//(1) Integer b=new Integer(3);//(2)

 

 

 

   這個時候執行過程爲:在執行(1)時,首先在棧中建立一個變量a,而後在堆內存中實例化一個對象,而且將變量a指向這個實例化的對象。在執行(2)時,過程相似,此時,在堆內存中,會有兩個Integer類型的對象。 
 

 

    2).在進程的各個線程之間,數據的共享經過堆來實現

 

         例:那麼,在多線程開發中,咱們的數據共享又是怎麼實現的呢?

 

   如圖所示,堆中的數據是全部線程棧所共享的,咱們能夠經過參數傳遞,將一個堆中的數據傳入各個棧的工做內存中,從而實現多個線程間的數據共享

(多個進程間的數據共享則須要經過網絡傳輸了。) 

 3.從程序設計的的角度:

 從軟件設計的角度看,JVM棧表明了處理邏輯,而JVM堆表明了數據。這樣分開,使得處理邏輯更爲清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設計的方方面面都有體現。

4.值傳遞和引用傳遞的真相

有了以上關於棧和堆的種種瞭解後,咱們很容易就能夠知道值傳遞和引用傳遞的真相:

 

1.程序運行永遠都是在JVM棧中進行的,於是參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象自己。

可是傳引用的錯覺是如何形成的呢?

在運行JVM棧中,基本類型和引用的處理是同樣的,都是傳值,因此,若是是傳引用的方法調用,也同時能夠理解爲「傳引用值」的傳值調用,即引用的處理跟基本類型是徹底同樣的。

可是當進入被調用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)到JVM堆中的對象,這個時候纔對應到真正的對象。

若是此時進行修改,修改的是引用對應的對象,而不是引用自己,即:修改的是JVM堆中的數據。因此這個修改是能夠保持的了。

  

最後:

從某種意義上來講對象都是由基本類型組成的。 

 

能夠把一個對象看做爲一棵樹,對象的屬性若是仍是對象,則仍是一顆樹(即非葉子節點),基本類型則爲樹的葉子節點。程序參數傳遞時,被傳遞的值自己都是不能進行修改的,可是,若是這個值是一個非葉子節點(即一個對象引用),則能夠修改這個節點下面的全部內容。 

 

  

其實,面向對象方式的程序與之前結構化的程序在執行上沒有任何區別

面向對象的引入,只是改變了咱們對待問題的思考方式,而更接近於天然方式的思考。

當咱們把對象拆開,其實對象的屬性就是數據,存放在JVM堆中;而對象的行爲(方法),就是運行邏輯,放在JVM棧中。咱們在編寫對象的時候,其實即編寫了數據結構,也編寫的處理數據的邏輯。
相關文章
相關標籤/搜索