日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
細說Java內(nèi)存管理:棧、堆、引用類型

朋友,您在使用Java進行編程時,是否了解過其調(diào)用內(nèi)存的工作原理?總的說來,作為一個不錯的靜默式垃圾回收器,Java具有自動管理內(nèi)存的功能,能夠在后臺工作,清理未使用的對象,并釋放內(nèi)存。話雖如此,如果您的程序設(shè)計不到位,Java的垃圾收集器和內(nèi)存管理特性,恐怕也無法自動生效。

網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、小程序設(shè)計、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了通化免費建站歡迎大家使用!

可見,了解內(nèi)存在Java中的實際原理是至關(guān)重要的。它不但能夠輔助您編寫出高性能的應(yīng)用程序,還能夠盡量避免程序因OutOfMemoryError而崩潰;或者是在程序運行狀況不佳時,協(xié)助您快速發(fā)現(xiàn)內(nèi)存泄漏的原因。

下面,讓我們首先來看一下Java語言中的內(nèi)存組織結(jié)構(gòu):

如上圖所示,內(nèi)存通常被分為兩大部分:棧和堆。請記住,該圖片中的內(nèi)存類型大小與實際內(nèi)存大小并不成比例。也就是說:與棧相比,堆是更大塊的內(nèi)存。

棧(Stack)

棧內(nèi)存既負責保存那些針對堆對象(heap objects)的引用,又負責保存各種值的類型,即:存儲的是數(shù)值本身,而不是對堆中某個對象的引用。在Java中,我們稱為原始類型(primitive types)。

另外,棧上的變量具有一定的可見性,我們稱為范圍(scope)。通常,只有活躍范圍(active scope)中的對象,才可以被使用。例如:假設(shè)我們沒有任何全局作用域的變量(或字段),而只有局部的變量,那么如果編譯器要執(zhí)行某個方法的主體,就只能從棧中訪問該方法主體內(nèi)的對象。而且由于超出了范圍,因此它無法訪問其他局部變量。一旦該方法被執(zhí)行完成并給出了返回,它就會彈出棧的頂部,并更改活躍范圍。

也許您已經(jīng)注意到,由于Java的棧內(nèi)存是按照線程分配的,因此在上圖中會有多個棧存儲器。而且,程序在每次創(chuàng)建和啟動一個線程時,都擁有自己的棧內(nèi)存,無需也無法訪問另一個線程的棧內(nèi)存。

堆(Heap)

這部分的內(nèi)存存儲著實際對象,它們會被棧的變量所引用。讓我們來看如下代碼行:

 
 
 
 
  1. StringBuilder builder = new StringBuilder(); 

關(guān)鍵字new負責確保堆能夠獲取足夠的可用空間。它在存儲器中創(chuàng)建StringBuilder類型的對象,并通過“builder”的引用,其壓入棧中。

由于每個正在運行的JVM進程只有一個堆內(nèi)存,因此無論系統(tǒng)當前正在運行多少個線程,它們都會共享內(nèi)存的指定部分。實際上,堆的真實結(jié)構(gòu)與上圖不盡相同,它會根據(jù)垃圾收集的過程,被分成幾個部分。

而是否需要預定義棧和堆的最大容量,將完全取決于正在運行程序的計算機。在后面的討論中,我們將研究JVM的相關(guān)配置,以便為正在運行的應(yīng)用程序,顯式地指定大小。

引用類型

如果仔細觀察上述圖片,您可能會注意到,來自于堆的、表示對象引用的箭頭,實際上具有不同的類型。這是因為在Java編程語言中,我們具有不同類型的引用,即:強引用、弱引用、軟引用、以及虛引用(phantom references)。引用類型之間的區(qū)別在于:堆上的對象在不同條件下,可以引用的垃圾回收有所不同。下面,我們來逐個進行討論。

1.強引用

這是最流行,也是開發(fā)人員最常用的引用類型。在上述StringBuilder示例中,我們實際上對堆中的對象采取了強引用。堆上的對象不會被垃圾回收,而是有一個指向了它的強引用,或者通過一串強引用來獲取該對象。

2.弱引用

弱引用可通過如下方式被創(chuàng)建:

 
 
 
 
  1. WeakReference reference = new WeakReference<>(new StringBuilder()); 

弱引用的一種最佳使用場景是緩存方案。設(shè)想,您檢索了一些數(shù)據(jù),并且希望將其存儲在內(nèi)存中,以便下次能夠直接作出響應(yīng)。當然,您并不確定何時或者是否有對該數(shù)據(jù)的請求。那么,您就可以對其采用弱引用,以免堆上的對象被垃圾收集器回收掉,以致在檢索該對象時,返回null值??梢?,WeakHashMap

 
 
 
 
  1. /** 
  2.     * The entries in this hash table extend WeakReference, using its main ref 
  3.     * field as the key. 
  4. */ 
  5. private static class Entry extends WeakReference implements Map.Entry { 
  6.     V value; 
  7. 一旦WeakHashMap中某個鍵被垃圾回收,那么整個條目就會從映射中被刪除。

    3.軟引用

    此類引用可用于那些對于內(nèi)存非常敏感的方案。例如,只有在應(yīng)用程序的內(nèi)存不足時,該引用才會被垃圾回收。也就是說,不到迫不得已,垃圾收集器就不會處置軟引用對應(yīng)的對象。而且,Java的相關(guān)文檔已提到了:在虛擬機拋出OutOfMemoryError之前,所有軟引用的對象早已被清除了。

    與弱引用類似,我們可以按照如下方式來創(chuàng)建軟引用:

     
     
     
     
    1. SoftReference reference = new SoftReference<>(new StringBuilder()); 

    4.虛引用

    虛引用可被用于事后清理操作,畢竟我們可以確定對象已不復存在。此類引用的.get()方法將始終返回null。虛引用必須和引用隊列(ReferenceQueue)一起使用。也就是說,當垃圾回收器準備回收某個對象時,如果發(fā)現(xiàn)它尚存有虛引用,就會在回收該對象的內(nèi)存之前,把虛引用加入到與之相關(guān)聯(lián)的引用隊列中。

    如何引用字符串

    Java中的字符串(String)類型有些特殊,它是不可變的。這就意味著程序每次使用字符串進行操作時,實際上都會在堆上創(chuàng)建另一個對象。而對于字符串而言,由于Java管理著內(nèi)存中的字符串池,因此Java會盡可能地存儲和重用字符串。例如:

     
     
     
     
    1. String localPrefix = "297"; //1 
    2. String prefix = "297";      //2 
    3. if (prefix == localPrefix) 
    4.     System.out.println("Strings are equal" ); 
    5. else 
    6.     System.out.println("Strings are different"); 

    在上述代碼被運行后,將會打印出:

    字符串相等(Strings are equal)

    可見,事實證明,在比較了String類型的兩個引用之后,這些引用實際上指向的是堆上的相同對象。但是,這對于那些經(jīng)過計算的字符串來說是無效的。例如:我們將上述代碼的//1行更改為:

     
     
     
     
    1. String localPrefix = new Integer(297).toString(); //1 

    那么輸出則變?yōu)椋?/p>

    字符串不同(Strings are different)

    可見,在這種情況下,堆上有兩個不同的對象。如果我們認為經(jīng)過計算的字符串會經(jīng)常被使用的話,則可以在經(jīng)過計算的字符串末尾,添加.intern()方法來,強制JVM將其添加到字符串池中。如下代碼再次修改了//1行:

     
     
     
     
    1. String localPrefix = new Integer(297).toString().intern(); //1 

    那么輸出則變?yōu)椋?/p>

    字符串相等(Strings are equal)

    垃圾收集程序

    如前所述,根據(jù)棧中的變量被保存到堆中的對象所引用類型,在某個時間點,該對象將會成為垃圾收集器的“合格對象”。

    如上圖所示,所有紅色的對象都有資格被垃圾收集器收集。您可能會注意到,堆上有一個對象具有其他對象的強引用(例如,既可以是對其進行引用的列表,又可以是具有兩種引用類型字段的對象)。由于它丟失了在棧中的引用,程序無法再對其進行訪問,因此它也成為了垃圾。

    在向下更深入討論之前,讓我們先明確如下三點:

    • 此過程由Java自動觸發(fā),并由Java決定何時、以及是否啟動此過程。
    • 當垃圾收集器運行時,應(yīng)用程序中的所有線程都將暫停,因此該過程代價不菲。
    • 該過程并非僅為垃圾收集和釋放內(nèi)存那么簡單。

    鑒于這是一個非常復雜的過程,并且可能會影響程序的性能,因此我們可以使用所謂的“標記和清掃(Mark and Sweep)”過程,即:讓Java分析棧中的變量,并“標記”所有需要保持活躍狀態(tài)的對象,然后清除所有未在使用的對象。顯然,被標記為垃圾的對象越多,需要保持活躍的對象就越少,該過程就會越快。為了使之更加高效,我們可以使用Java JDK附帶的工具—JvisualVM,來可視化內(nèi)存的使用情況和其他實用的信息。當然,您需要安裝一個名為Visual GC的插件,以查看到內(nèi)存的實際結(jié)構(gòu)。

    如上圖所示,我們創(chuàng)建了一個對象,并將其分配到Eden(1)空間上。由于Eden空間并不大,因此很快就會被填滿。此時,垃圾收集器運行在Eden空間上,并將各個對象標記為活躍。

    一旦某個對象在垃圾回收的過程中留存下來,它就將會被移到所謂的留存空間--S0(2)中。當垃圾收集器第二次在Eden空間上運行時,它會將所有留存的對象移到S1(3)空間中。同樣,當前在S0(2)上的所有對象,也都被移到S1(3)空間中。如果一個對象經(jīng)過n輪垃圾回收,仍被留存下來的話,那么它被視為需要持久存在,并被移入Old(4)空間。

    至此,在垃圾回收器graph(6)中,您會看到各種對象在每一次運行后,被轉(zhuǎn)移到留存空間,Eden空間同時也會重新產(chǎn)生。而Metaspace(5)可被用于讓元數(shù)據(jù)存儲JVM加載的各種類。

    上述圖片實際上是一個Java 8的應(yīng)用程序。而在Java 8之前的版本中,內(nèi)存結(jié)構(gòu)會略有不同。metaspace實際上被稱為PermGen空間。例如,在Java 6中,該空間還存儲了字符串池的內(nèi)存。因此,如果Java 6應(yīng)用程序中的字符串過多,就可能會崩潰。

    垃圾收集器(GC)的類型

    實際上,JVM具有如下三種類型的垃圾收集器,可供開發(fā)人員進行選擇。默認情況下,Java會根據(jù)實際環(huán)境中的底層硬件,來進行選用。

    1. 串行GC – 單線程收集器。它通常適用于數(shù)據(jù)量較少的小型應(yīng)用程序。您可以通過指定命令行選項:-XX:+UseSerialGC,來啟用。

    2. 并行GC – 吞吐量收集器。它是使用多個線程來執(zhí)行垃圾收集的過程。您可以通過顯式指定選項:-XX:+UseParallelGC,來啟用。

    3. 并發(fā)GC – 如前文所述,垃圾收集過程在運行時,會暫停所有的線程。而并發(fā)GC的許多操作(并非所有)與應(yīng)用程序的業(yè)務(wù),存在著并發(fā)關(guān)系。在具有多個處理內(nèi)核的計算機上,應(yīng)用程序線程可以在收集的并發(fā)期間使用處理器,因此并發(fā)垃圾收集器線程不會暫停應(yīng)用程序。其效果當然會使得停頓的時間更短,但是應(yīng)用程序可用的處理器資源也會相應(yīng)地變得更少,而且可能出現(xiàn)降速,特別是當應(yīng)用程序正在最大限度地使用所有處理內(nèi)核時。通常有兩種并發(fā)GC可被選用:

    3.1垃圾優(yōu)先 – 它在滿足垃圾收集暫停時間目標的同時,實現(xiàn)了高吞吐量。您可以通過:-XX:+UseG1GC,來啟用它。

    3.2并發(fā)標記清掃 – 此收集器適用于那些追求更短的垃圾收集暫停時間,且能夠與垃圾收集共享處理器資源的應(yīng)用程序。您可以通過:-XX:+UseConcMarkSweepGC,來啟用它。不過從JDK 9開始,該GC類型不再被推薦使用。

    技巧和竅門

    • 為了最大程度地減少內(nèi)存的占用,請盡可能地限制變量的范圍。記住,在每次彈出棧頂部的作用域時,該作用域中的引用都會被丟失,并會導致對象被判定為適合垃圾收集。
    • 將過時的引用標記為null,以便讓此類引用對象適合垃圾收集。
    • 由于finalizer會減慢垃圾收集的過程,因此最好使用虛引用。
    • 不要在可以使用弱引用或軟引用之處使用強引用。最常見的內(nèi)存陷阱是??在緩存場景中,即使數(shù)據(jù)不在被需要,卻仍要保留在內(nèi)存中。
    • JVisualVM還具有在特定時間點進行堆轉(zhuǎn)儲的功能,因此您可以針對每個類分析其占用的內(nèi)存量。
    • 根據(jù)您的應(yīng)用程序需求來配置JVM。在運行應(yīng)用程序時,可明確指定JVM堆的尺寸,分配合理的初始和最大內(nèi)存量。您可以借鑒如下指定原則:
    1. 初始堆的尺寸 -Xms512m – 將初始堆的尺寸設(shè)置為512 MB。
    2. 最大堆的尺寸 -Xmx1024m – 將最大堆的尺寸設(shè)置為1024 MB。
    3. 線程棧大小 -Xss1m – 將線程棧的大小設(shè)置為1 MB。
    4. 新生大小 -Xmn256m – 將新生的大小設(shè)置為256 MB。
    • 如果Java應(yīng)用程序出現(xiàn)OutOfMemoryError,而且崩潰了,那么您可以使用–XX:HeapDumpOnOutOfMemory參數(shù),來運行該過程。它將會在下一次發(fā)生此類錯誤時創(chuàng)建一個堆dump文件,以方便您收集到內(nèi)存泄漏的相關(guān)信息。
    • 請使用-verbose:gc選項,來獲取垃圾收集到的輸出。

    小結(jié)

    綜上所述,了解內(nèi)存的組織方式,您不但可以從內(nèi)存資源的合理使用角度,編寫出良好且經(jīng)過優(yōu)化的代碼,還可以通過優(yōu)化配置,來調(diào)整正在運行的JVM。此外,通過使用恰當?shù)墓ぞ?,您還可以輕松地修復各類程序中的內(nèi)存泄漏錯誤。


    網(wǎng)頁名稱:細說Java內(nèi)存管理:棧、堆、引用類型
    鏈接分享:http://www.5511xx.com/article/dhidgei.html