JVM 介紹
JVM詳解
本文詳細講解了JVM(Java Virtual Machine)的方方面面,首先由java的特性來描繪JVM的大致應用,再細細闡述了JVM的原理及記憶體管理機制和調優.最後講述了與JVM密切相關的Java GC機制.
本文內容大多來自網路,但內容十分豐富,是學習JVM的好資料.
後面會再針對JVM的兩大職責classloader和 execution engine進行講解
目錄
3.2.1 Reference Counting(引用計數)...22
Java相關
JVM記憶體模型
2.1 JVM規範
JVM specification對JVM記憶體的描述
首先我們來了解JVM specification中的JVM整體架構。如下圖:
主要包括兩個子系統和兩個元件: Class loader(類裝載器) 子系統,Executionengine(執行引擎) 子系統;Runtime data area (執行時資料區域)元件, Native interface(本地介面)元件。
Class loader子系統的作用 :根據給定的全限定名類名(如java.lang.Object)來裝載class檔案的內容到 Runtime data area中的method area(方法區域)。Javsa程式設計師可以extendsjava.lang.ClassLoader類來寫自己的Class loader。
Execution engine子系統的作用 :執行classes中的指令。任何JVMspecification實現(JDK)的核心是Execution engine, 換句話說:Sun 的JDK 和IBM的JDK好壞主要取決於他們各自實現的Execution engine的好壞。每個執行中的執行緒都有一個Execution engine的例項。
Native interface元件 :與native libraries互動,是其它程式語言互動的介面。
Runtime data area 元件:這個元件就是JVM中的記憶體。下面對這個部分進行詳細介紹。
Runtime data area的整體架構圖
Runtime data area 主要包括五個部分:Heap (堆), Method Area(方法區域),Java Stack(java的棧), Program Counter(程式計數器), Native method stack(本地方法棧)。Heap 和MethodArea是被所有執行緒的共享使用的;而Java stack, Program counter 和Native method stack是以執行緒為粒度的,每個執行緒獨自擁有。
Heap
Java程式在執行時建立的所有類實或陣列都放在同一個堆中。而一個Java虛擬例項中只存在一個堆空間,因此所有執行緒都將共享這個堆。每一個java程式獨佔一個JVM例項,因而每個java程式都有它自己的堆空間,它們不會彼此干擾。但是同一java程式的多個執行緒都共享著同一個堆空間,就得考慮多執行緒訪問物件(堆資料)的同步問題。(這裡可能出現的異常java.lang.OutOfMemoryError: Java heapspace)
Method area
在Java虛擬機器中,被裝載的class的資訊儲存在Method area的記憶體中。當虛擬機器裝載某個型別時,它使用類裝載器定位相應的class檔案,然後讀入這個class檔案內容並把它傳輸到虛擬機器中。緊接著虛擬機器提取其中的型別資訊,並將這些資訊儲存到方法區。該型別中的類(靜態)變數同樣也儲存在方法區中。與Heap 一樣,method area是多執行緒共享的,因此要考慮多執行緒訪問的同步問題。比如,假設同時兩個執行緒都企圖訪問一個名為Lava的類,而這個類還沒有內裝載入虛擬機器,那麼,這時應該只有一個執行緒去裝載它,而另一個執行緒則只能等待。(這裡可能出現的異常java.lang.OutOfMemoryError:
PermGen full)
Java stack
Java stack以幀為單位儲存執行緒的執行狀態。虛擬機器只會直接對Javastack執行兩種操作:以幀為單位的壓棧或出棧。每當執行緒呼叫一個方法的時候,就對當前狀態作為一個幀儲存到java stack中(壓棧);當一個方法呼叫返回時,從java stack彈出一個幀(出棧)。棧的大小是有一定的限制,這個可能出現StackOverFlow問題。下面的程式可以說明這個問題。
public class TestStackOverFlow {
public static void main(String[] args) {
Recursive r = new Recursive();
r.doit(10000);
// Exception in thread "main" java.lang.StackOverflowError
}
}
class Recursive {
public int doit(int t) {
if (t <= 1) {
return 1;
}
return t + doit(t - 1);
}
}
Program counter
每個執行中的Java程式,每一個執行緒都有它自己的PC暫存器,也是該執行緒啟動時建立的。PC暫存器的內容總是指向下一條將被執行指令的餓“地址”,這裡的“地址”可以是一個本地指標,也可以是在方法區中相對應於該方法起始指令的偏移量。
Nativemethod stack
對於一個執行中的Java程式而言,它還能會用到一些跟本地方法相關的資料區。當某個執行緒呼叫一個本地方法時,它就進入了一個全新的並且不再受虛擬機器限制的世界。本地方法可以通過本地方法介面來訪問虛擬機器的執行時資料區,不止如此,它還可以做任何它想做的事情。比如,可以呼叫暫存器,或在作業系統中分配記憶體等。總之,本地方法具有和JVM相同的能力和許可權。(這裡出現JVM無法控制的記憶體溢位問題native
heap OutOfMemory )
2.2 SunJVM
Sun JVM中對JVM Specification的實現(記憶體部分)
JVM Specification只是抽象的說明了JVM例項按照子系統、記憶體區、資料型別以及指令這幾個術語來描述的, 但是規範並非是要強制規定Java虛擬機器實現內部的體系結構,更多的是為了嚴格地定義這些實現的外部特徵。
Sun JVM實現中:Runtime data area(JVM記憶體)五個部分中的Java Stack
,Program Counter, Native method stack三部分和規範中的描述基本一致;但對Heap和 Method Area進行了自己獨特的實現。這個實現和Sun
JVM的Garbage collector(垃圾回收)機制有關,下面的章節進行詳細描述。
垃圾分代回收演算法(GenerationalCollecting)
基於對物件生命週期分析後得出的垃圾回收演算法。把物件分為年青代、年老代、持久代,對不同生命週期的物件使用不同的演算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此演算法的。
如上圖所示,為Java堆中的各代分佈。
1. Young(年輕代)JVM specification中的 Heap的一部份
年輕代分三個區。一個Eden區,兩個Survivor區。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來物件,和從前一個Survivor複製過來的物件,而複製到年老區的只有從第一個Survivor去過來的物件。而且,Survivor區總有一個是空的。
2. Tenured(年老代)JVM specification中的 Heap的一部份
年老代存放從年輕代存活的物件。一般來說年老代存放的都是生命期較長的物件。
3. Perm(持久代) JVM specification中的 Method area
用於存放靜態檔案,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者呼叫一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些執行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設定。
2.3 SUN JVM記憶體管理(優化)
在我做J2EE系統開發的工作生涯中,經常遇到技術人員或客戶發出諸如此類的感慨:我的J2EE應用系統處理的資料量不大,系統體積也不大,技術架構也沒有問題,我的應用伺服器的記憶體有4G或8G;系統執行起來很慢,還經常出現記憶體溢位錯誤。真是無奈!每次遇到這樣的情況,我心中都會忍不住竊笑之。
其實他們所遇到這種情況,不是技術架構上的問題,不是系統本身的問題,也不是應用伺服器的問題,也可能不是伺服器的記憶體資源真的不足的問題。他們花了很多時間在J2EE應用系統本身上找問題(當然一般情況下,這種做法是對的;當出現問題時,在自身上多找找有什麼不足),結果還是解決不了問題。他們卻忽略了很重要的一點:J2EE應用系統是執行在J2EE應用伺服器上的,而J2EE應用伺服器又是執行在JVM(JavaVirtual Machine)上的。
其實在生產環境中JVM引數的優化和設定對J2EE應用系統效能有著決定性的作用。本篇我們就來分析JAVA的建立者SUN 公司的JVM的記憶體管理機制(在現實中絕大多數的應用伺服器是執行在SUN公司的JVM上的,當然除了SUN公司的JVM,還有IBM的JVM,Bea的JVM等);下篇我們們具體講解怎樣優化JVM的引數以達到優化J2EE應用的目的。
我們們先來看JVM的記憶體管理制吧,JVM的早期版本並沒有進行分割槽管理;這樣的後果是JVM進行垃圾回收時,不得不掃描JVM所管理的整片記憶體,所以蒐集垃圾是很耗費資源的事情,也是早期JAVA程式的效能低下的主要原因。隨著JVM的發展,JVM引進了分割槽管理的機制。
採用分割槽管理機制的JVM將JVM所管理的所有記憶體資源分為2個大的部分。永久儲存區(Permanent Space)和堆空間(The Heap Space)。其中堆空間又分為新生區(Young (New)generation space)和養老區(Tenure (Old) generation space),新生區又分為伊甸園(Eden space),倖存者0區(Survivor0 space)和倖存者1區(Survivor 1 space)。具體分割槽如下圖:
那JVM他的這些分割槽各有什麼用途,請看下面的解說。
永久儲存區(Permanent Space):永久儲存區是JVM的駐留記憶體,用於存放JDK自身所攜帶的Class,Interface的後設資料,應用伺服器允許必須的Class,Interface的後設資料和Java程式執行時需要的Class和Interface的後設資料。被裝載進此區域的資料是不會被垃圾回收器回收掉的,關閉JVM時,釋放此區域所控制的記憶體。
堆空間(The Heap Space):是JAVA物件生死存亡的地區,JAVA物件的出生,成長,死亡都在這個區域完成。堆空間又分別按JAVA物件的建立和年齡特徵分為養老區和新生區。
新生區(Young (New) generation space):新生區的作用包括JAVA物件的建立和從JAVA物件中篩選出能進入養老區的JAVA物件。
伊甸園(Eden space):JAVA對空間中的所有物件在此出生,該區的名字因此而得名。也即是說當你的JAVA程式執行時,需要建立新的物件,JVM將在該區為你建立一個指定的物件供程式使用。建立物件的依據即是永久儲存區中的後設資料。
倖存者0區(Survivor 0 space)和倖存者1區(Survivor1 space):當伊甸園的空間用完時,程式又需要建立物件;此時JVM的垃圾回收器將對伊甸園區進行垃圾回收,將伊甸園區中的不再被其他物件所引用的物件進行銷燬工作。同時將伊甸園中的還有其他物件引用的物件移動到倖存者0區。倖存者0區就是用於存放伊甸園垃圾回收時所幸存下來的JAVA物件。當將伊甸園中的還有其他物件引用的物件移動到倖存者0區時,如果倖存者0區也沒有空間來存放這些物件時,JVM的垃圾回收器將對倖存者0區進行垃圾回收處理,將倖存者0區中不在有其他物件引用的JAVA物件進行銷燬,將倖存者0區中還有其他物件引用的物件移動到倖存者1區。倖存者1區的作用就是用於存放倖存者0區垃圾回收處理所幸存下來的JAVA物件。
養老區(Tenure (Old) generation space):用於儲存從新生區篩選出來的JAVA物件。
上面我們看了JVM的記憶體分割槽管理,現在我們來看JVM的垃圾回收工作是怎樣運作的。首先當啟動J2EE應用伺服器時,JVM隨之啟動,並將JDK的類和介面,應用伺服器執行時需要的類和介面以及J2EE應用的類和介面定義檔案也及編譯後的Class檔案或JAR包中的Class檔案裝載到JVM的永久儲存區。在伊甸園中建立JVM,應用伺服器執行時必須的JAVA物件,建立J2EE應用啟動時必須建立的JAVA物件;J2EE應用啟動完畢,可對外提供服務。
JVM在伊甸園區根據使用者的每次請求建立相應的JAVA物件,當伊甸園的空間不足以用來建立新JAVA物件的時候,JVM的垃圾回收器執行對伊甸園區的垃圾回收工作,銷燬那些不再被其他物件引用的JAVA物件(如果該物件僅僅被一個沒有其他物件引用的物件引用的話,此物件也被歸為沒有存在的必要,依此類推),並將那些被其他物件所引用的JAVA物件移動到倖存者0區。
如果倖存者0區有足夠空間存放則直接放到倖存者0區;如果倖存者0區沒有足夠空間存放,則JVM的垃圾回收器執行對倖存者0區的垃圾回收工作,銷燬那些不再被其他物件引用的JAVA物件(如果該物件僅僅被一個沒有其他物件引用的物件引用的話,此物件也被歸為沒有存在的必要,依此類推),並將那些被其他物件所引用的JAVA物件移動到倖存者1區。
如果倖存者1區有足夠空間存放則直接放到倖存者1區;如果倖存者0區沒有足夠空間存放,則JVM的垃圾回收器執行對倖存者0區的垃圾回收工作,銷燬那些不再被其他物件引用的JAVA物件(如果該物件僅僅被一個沒有其他物件引用的物件引用的話,此物件也被歸為沒有存在的必要,依此類推),並將那些被其他物件所引用的JAVA物件移動到養老區。
如果養老區有足夠空間存放則直接放到養老區;如果養老區沒有足夠空間存放,則JVM的垃圾回收器執行對養老區區的垃圾回收工作,銷燬那些不再被其他物件引用的JAVA物件(如果該物件僅僅被一個沒有其他物件引用的物件引用的話,此物件也被歸為沒有存在的必要,依此類推),並保留那些被其他物件所引用的JAVA物件。如果到最後養老區,倖存者1區,倖存者0區和伊甸園區都沒有空間的話,則JVM會報告“JVM堆空間溢位(java.lang.OutOfMemoryError: Javaheap space)”,也即是在堆空間沒有空間來建立物件。
這就是JVM的記憶體分割槽管理,相比不分割槽來說;一般情況下,垃圾回收的速度要快很多;因為在沒有必要的時候不用掃描整片記憶體而節省了大量時間。
通常大家還會遇到另外一種記憶體溢位錯誤“永久儲存區溢位(java.lang.OutOfMemoryError:Java Permanent Space)”。
好,本篇對SUN 的JVM記憶體管理機制講解就到此為止,下一篇我們將詳細講解怎樣優化SUN 的JVM讓我們的J2EE系統執行更快,不出現記憶體溢位等問題。
JVM相關引數:
引數名 引數說明
-server 啟用能夠執行優化的編譯器, 顯著提高伺服器的效能,但使用能夠執行優化的編譯器時,伺服器的預備時間將會較長。生產環境的伺服器強烈推薦設定此引數。
-Xss 單個執行緒堆疊大小值;JDK5.0以後每個執行緒堆疊大小為1M,以前每個執行緒堆疊大小為256K。在相同實體記憶體下,減小這個值能生成更多的執行緒。但是作業系統對一個程式內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000左右。
-XX:+UseParNewGC 可用來設定年輕代為併發收集【多CPU】,如果你的伺服器有多個CPU,你可以開啟此引數;開啟此引數,多個CPU可併發進行垃圾回收,可提高垃圾回收的速度。此引數和+UseParallelGC,-XX:ParallelGCThreads搭配使用。
+UseParallelGC 選擇垃圾收集器為並行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用併發收集,而年老代仍舊使用序列收集 。可提高系統的吞吐量。
-XX:ParallelGCThreads 年輕代並行垃圾收集的前提下(對併發也有效果)的執行緒數,增加並行度,即:同時多少個執行緒一起進行垃圾回收。此值最好配置與處理器數目相等。
永久儲存區相關引數:
引數名 引數說明
-Xnoclassgc 每次永久儲存區滿了後一般GC演算法在做擴充套件分配記憶體前都會觸發一次FULL GC,除非設定了-Xnoclassgc.
-XX:PermSize 應用伺服器啟動時,永久儲存區的初始記憶體大
-XX:MaxPermSize 應用執行中,永久儲存區的極限值。為了不消耗擴大JVM永久儲存區分配的開銷,將此引數和-XX:PermSize這個兩個值設為相等。
堆空間相關引數
引數名 引數說明
-Xms 啟動應用時,JVM堆空間的初始大小值。
-Xmx 應用執行中,JVM堆空間的極限值。為了不消耗擴大JVM堆空間分配的開銷,將此引數和-Xms這個兩個值設為相等,考慮到需要開執行緒,將此值設定為總記憶體的80%.
-Xmn 此引數硬性規定堆空間的新生代空間大小,推薦設為堆空間大小的1/4。
上面所列的JVM引數關係到系統的效能,而其中-XX:PermSize,-XX:MaxPermSize,-Xms,-Xmx和-Xmn這5個引數更是直接關係到系統的效能,系統是否會出現記憶體溢位。
-XX:PermSize和-XX:MaxPermSize分別設定應用伺服器啟動時,永久儲存區的初始大小和極限大小;在生成環境中強烈推薦將這個兩個值設定為相同的值,以避免分配永久儲存區的開銷,具體的值可取系統“疲勞測試”獲取到的永久儲存區的極限值;如果不進行設定-XX:MaxPermSize預設值為64M,一般來說系統的類定義檔案大小都會超過這個預設值。
-Xms和-Xmx分別是伺服器啟動時,堆空間的初始大小和極限值。-Xms的預設值是實體記憶體的1/64但小於1G,-Xmx的預設值是實體記憶體的1/4但小於1G.在生產環境中這些預設值是肯定不能滿足我們的需要的。也就是你的伺服器有8g的記憶體,不對JVM引數進行設定優化,應用伺服器啟動時還是按預設值來分配和約束JVM對記憶體資源的使用,不會充分的利用所有的記憶體資源。
到此我們就不難理解上文提到的“我的伺服器有8g記憶體,系統也就100M左右,居然出現記憶體溢位”這個“怪現象”了。在上文我曾提到“永久儲存區溢位(java.lang.OutOfMemoryError: JavaPermanent Space)”和“JVM堆空間溢位(java.lang.OutOfMemoryError: Java heap space)”這兩種溢位錯誤。現在大家都知道答案了:“永久儲存區溢位(java.lang.OutOfMemoryError: JavaPermanent Space)”乃是永久儲存區設定太小,不能滿足系統需要的大小,此時只需要調整-XX:PermSize和-XX:MaxPermSize這兩個引數即可。“JVM堆空間溢位(java.lang.OutOfMemoryError:
Javaheap space)”錯誤是JVM堆空間不足,此時只需要調整-Xms和-Xmx這兩個引數即可。
到此我們知道了,當系統出現記憶體溢位時,是哪些引數設定不合理需要調整。但我們怎麼知道伺服器啟動時,到底JVM記憶體相關引數的值是多少呢。在實踐中,經常遇到對JVM引數進行設定了,並且自己心裡覺得應該不會出現記憶體溢位了;但不幸的是記憶體溢位還是發生了。很多人百思不得其解,那我可以肯定地告訴你,你設定的JVM引數並沒有起作用(本文我們不探討沒有起作用的原因)。不信我們就去看看,下面介紹如何使用SUN公司的記憶體使用監控工具jvmstat.
本文只介紹如何使用jvmstat檢視記憶體使用,不介紹其安裝配置。有興趣的讀者,可到SUN公司的官方網站下載一個,他本身已經帶有非常詳細的安裝配置文件了。這裡假設你已經在你的應用伺服器上配置好了jvmstat了。那我們就開始使用他來看看我們的伺服器到底是有沒有按照我們設定的引數啟動。
首先啟動伺服器,等伺服器啟動完。開啟DOS視窗(此例子是在windows下完成,linux下同樣),在dos視窗中輸入jps這個命令。如下圖
視窗中會顯示所有JAVA應用程式列表,列表的第一列為應用的程式ID,第二列為應用的名字。在列表中找到你的應用伺服器的程式ID,比如我這裡的應用伺服器程式ID為1856.在命令列輸入visualgc 1856回車。進入jvmstat的主介面,如下圖:
上圖分別標註了伊甸園,倖存者0區,倖存者1區,養老區和永久儲存區。圖上直觀的反應出各儲存區的大小,已經使用的大小,剩下的空間大小,並用數字標出了各區的大小;如果你這上面的數字和你設定的JVM引數相同的話,那麼恭喜你,你設定的引數已經起作用,如果和你設定的不一致的話,那麼你設定的引數沒有起作用(可能是伺服器的啟動方式沒有載入你的JVM引數設定。)
在優化伺服器的時候,這個工具很有用,他佔用資源少。可以隨應用伺服器一直保持開啟狀態,如果系統發生內粗溢位,可以一眼就看出是那個區發生了溢位。根據觀察結果進行進一步優化。
2.5.JVM簡單理解
2.5.1 Java棧
Java棧是與每一個執行緒關聯的,JVM在建立每一個執行緒的時候,會分配一定的棧空間給執行緒。它主要用來儲存執行緒執行過程中的區域性變數,方法的返回值,以及方法呼叫上下文。棧空間隨著執行緒的終止而釋放。
StackOverflowError:如果線上程執行的過程中,棧空間不夠用,那麼JVM就會丟擲此異常,這種情況一般是死遞迴造成的。
2.5.2 堆
Java中堆是由所有的執行緒共享的一塊記憶體區域,堆用來儲存各種JAVA物件,比如陣列,執行緒物件等。
JVM堆一般又可以分為以下三部分:
Ø Perm
Perm代主要儲存class,method,filed物件,這部分的空間一般不會溢位,除非一次性載入了很多的類,不過在涉及到熱部署的應用伺服器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署後,類的class沒有被解除安裝掉,這樣就造成了大量的class物件儲存在了perm中,這種情況下,一般重新啟動應用伺服器可以解決問題。
Ø Tenured
Tenured區主要儲存生命週期長的物件,一般是一些老的物件,當一些物件在Young複製轉移一定的次數以後,物件就會被轉移到Tenured區,一般如果系統中用了application級別的快取,快取中的物件往往會被轉移到這一區間。
Ø Young
Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製物件用,在Young區間變滿的時候,minor GC就會將存活的物件移到空閒的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的物件將被移動到Tenured區間。
JVM提供了相應的引數來對記憶體大小進行配置。
正如上面描述,JVM中堆被分為了3個大的區間,同時JVM也提供了一些選項對Young,Tenured的大小進行控制。
Ø Total Heap
-Xms :指定了JVM初始啟動以後初始化記憶體
-Xmx:指定JVM堆得最大記憶體,在JVM啟動以後,會分配-Xmx引數指定大小的記憶體給JVM,但是不一定全部使用,JVM會根據-Xms引數來調節真正用於JVM的記憶體
-Xmx -Xms之差就是三個Virtual空間的大小
Ø Young Generation
-XX:NewRatio=8意味著tenured 和 young的比值8:1,這樣eden+2*survivor=1/9
堆記憶體
-XX:SurvivorRatio=32意味著eden和一個survivor的比值是32:1,這樣一個Survivor就佔Young區的1/34.
-Xmn 引數設定了年輕代的大小
Ø Perm Generation
-XX:PermSize=16M -XX:MaxPermSize=64M
Thread Stack
-XX:Xss=128K
2.5.3 堆疊分離的好處
呵 呵,其它的先不說了,就來說說物件導向的設計吧,當然除了物件導向的設計帶來的維護性,複用性和擴充套件性方面的好處外,我們看看物件導向如何巧妙的利用了堆疊分離。如果從JAVA記憶體模型的角度去理解物件導向的設計,我們就會發現物件它完美的表示了堆和棧,物件的資料放在堆中,而我們編寫的那些方法一般都是執行在棧中,因此物件導向的設計是一種非常完美的設計方式,它完美的統一了資料儲存和執行。
2.5.4 堆(heap)和棧(stack)
什麼叫堆?你用十幾個麻將牌豎直疊成一摞這叫堆,你可以從上面、下面、中間任意抽出一張牌,也可以任意插入一張。
什麼叫棧?AK-47的彈匣就是一個棧,在上面的子彈沒被取出之前,你無法取出下面的子彈——儘管你可以從邊上的透明部分讀出裡面裝的是什麼型號、顏色的子彈。
堆很靈活,但是不安全。對於物件,我們要動態地建立、銷燬,不能說後建立的物件沒有銷燬,先前建立的物件就不能銷燬,那樣的話我們的程式就寸步難行,所以Java中用堆來儲存物件。而一旦堆中的物件被銷燬,我們繼續引用這個物件的話,就會出現著名的 NullPointerException,這就是堆的缺點——錯誤的引用邏輯只有在執行時才會被發現。
棧不靈活,但是很嚴格,是安全的,易於管理。因為只要上面的引用沒有銷燬,下面引用就一定還在,所以,在棧中,上面引用永遠可以通過下面引用來查詢物件,同時如果確認某一區間的內容會一起存在、一起銷燬,也可以上下互相引用。在大部分程式中,都是先定義的變數、引用先進棧,後定義的後進棧,同時,區塊內部的變數、引用在進入區塊時壓棧,區塊結束時出棧,理解了這種機制,我們就可以很方便地理解各種程式語言的作用域的概念了,同時這也是棧的優點——錯誤的引用邏輯在編譯時就可以被發現。
舉例說明
簡單的說 其實 棧 就是存放變數引用的一個地方, 堆 就是存放實際物件的地方 也就是.
比如:int i = 7; 這個 其實是存在棧裡邊的。內容為i = 7。
Apple app = new Apple(); 這個 app 是在棧裡邊的他對應的是一個記憶體地址也在堆裡邊,而這個記憶體地址對應的是堆裡邊存放Apple 例項的地址。
String s = "Hello World!"; 這個其實是存在另外一塊靜態程式碼區。
總體來說: 棧--主要存放引用 和基本資料型別。
堆--用來存放 new 出來的物件例項。
JAVA垃圾收集器
3.1 垃圾收集簡史
垃圾收集提供了記憶體管理的機制,使得應用程式不需要在關注記憶體如何釋放,記憶體用完後,垃圾收集會進行收集,這樣就減輕了因為人為的管理記憶體而造成的錯誤,比如在C++語言裡,出現記憶體洩露時很常見的。
Java語言是目前使用最多的依賴於垃圾收集器的語言,但是垃圾收集器策略從20世紀60年代就已經流行起來了,比如Smalltalk,Eiffel等程式語言也整合了垃圾收集器的機制。
3.2 常見的垃圾收集策略
所有的垃圾收集演算法都面臨同一個問題,那就是找出應用程式不可到達的記憶體塊,將其釋放,這裡面得不可到達主要是指應用程式已經沒有記憶體塊的引用了,而在JAVA中,某個物件對應用程式是可到達的是指:這個物件被根(根主要是指類的靜態變數,或者活躍在所有執行緒棧的物件的引用)引用或者物件被另一個可到達的物件引用。
3.2.1 Reference Counting(引用計數)
引用計數是最簡單直接的一種方式,這種方式在每一個物件中增加一個引用的計數,這個計數代表當前程式有多少個引用引用了此物件,如果此物件的引用計數變為0,那麼此物件就可以作為垃圾收集器的目標物件來收集。
優點:
簡單,直接,不需要暫停整個應用
缺點:
1.需要編譯器的配合,編譯器要生成特殊的指令來進行引用計數的操作,比如每次將物件賦值給新的引用,或者物件的引用超出了作用域等。
2.不能處理迴圈引用的問題
3.2.2 跟蹤收集器
跟蹤收集器首先要暫停整個應用程式,然後開始從根物件掃描整個堆,判斷掃描的物件是否有物件引用,這裡面有三個問題需要搞清楚:
1.如果每次掃描整個堆,那麼勢必讓GC的時間變長,從而影響了應用本身的執行。因此在JVM裡面採用了分代收集,在新生代收集的時候minor gc只需要掃描新生代,而不需要掃描老生代。
2.JVM採用了分代收集以後,minor gc只掃描新生代,但是minor gc怎麼判斷是否有老生代的物件引用了新生代的物件,JVM採用了卡片標記的策略,卡片標記將老生代分成了一塊一塊的,劃分以後的每一個塊就叫做一個卡片,JVM採用卡表維護了每一個塊的狀態,當JAVA程式執行的時候,如果發現老生代物件引用或者釋放了新生代物件的引用,那麼就JVM就將卡表的狀態設定為髒狀態,這樣每次minor gc的時候就會只掃描被標記為髒狀態的卡片,而不需要掃描整個堆。具體如下圖:
3.GC在收集一個物件的時候會判斷是否有引用指向物件,在JAVA中的引用主要有四種:Strong reference,Soft reference,Weak reference,Phantom reference.
Ø Strong Reference
強引用是JAVA中預設採用的一種方式,我們平時建立的引用都屬於強引用。如果一個物件沒有強引用,那麼物件就會被回收。
public void testStrongReference(){
Object referent = new Object();
Object strongReference = referent;
referent = null;
System.gc();
assertNotNull(strongReference);
}
Ø Soft Reference
軟引用的物件在GC的時候不會被回收,只有當記憶體不夠用的時候才會真正的回收,因此軟引用適合快取的場合,這樣使得快取中的物件可以儘量的再記憶體中待長久一點。
public void testSoftReference(){
String str = "test";
SoftReference<String> softreference = new SoftReference<String>(str);
str=null;
System.gc();
assertNotNull(softreference.get());
}
Ø Weak reference
弱引用有利於物件更快的被回收,假如一個物件沒有強引用只有弱引用,那麼在GC後,這個物件肯定會被回收。
public void testWeakReference(){
String str = "test";
WeakReference<String> weakReference = new WeakReference<String>(str);
str=null;
System.gc();
assertNull(weakReference.get());
}
Ø Phantom reference
PhantonReference, 是一種特殊的Reference,正如他的名字所表達的,幻影引用,他可以像幻影一樣附著在referent上。
當GC在遍歷引用關係時,如果發現被phantom reference包裝過的referent不存在strong, weak, soft引用時(就是除phantom外沒有任何引用,幻影的由來),GC會將 phantom reference 放入 Reference queue。以便程式在另一邊通過queue的remove/poll方法,感知referent被GC回收的事件
標記清除收集器最早由Lisp的發明人於1960年提出,標記清除收集器停止所有的工作,從根掃描每個活躍的物件,然後標記掃描過的物件,標記完成以後,清除那些沒有被標記的物件。
優點:
1 解決迴圈引用的問題
2 不需要編譯器的配合,從而就不執行額外的指令
缺點:
1.每個活躍的物件都要進行掃描,收集暫停的時間比較長。
複製收集器將記憶體分為兩塊一樣大小空間,某一個時刻,只有一個空間處於活躍的狀態,當活躍的空間滿的時候,GC就會將活躍的物件複製到未使用的空間中去,原來不活躍的空間就變為了活躍的空間。
複製收集器具體過程可以參考下圖:
優點:
1 只掃描可以到達的物件,不需要掃描所有的物件,從而減少了應用暫停的時間
缺點:
1.需要額外的空間消耗,某一個時刻,總是有一塊記憶體處於未使用狀態
2.複製物件需要一定的開銷
標記整理收集器汲取了標記清除和複製收集器的優點,它分兩個階段執行,在第一個階段,首先掃描所有活躍的物件,並標記所有活躍的物件,第二個階段首先清除未標記的物件,然後將活躍的的物件複製到堆得底部。標記整理收集器的過程示意圖請參考下圖:
Mark-compact策略極大的減少了記憶體碎片,並且不需要像Copy Collector一樣需要兩倍的空間。
3.3 JVM的垃圾收集策略
GC的執行時要耗費一定的CPU資源和時間的,因此在JDK1.2以後,JVM引入了分代收集的策略,其中對新生代採用"Mark-Compact"策略,而對老生代採用了“Mark-Sweep"的策略。其中新生代的垃圾收集器命名為“minor gc”,老生代的GC命名為"Full Gc 或者Major GC".其中用System.gc()強制執行的是Full Gc.
3.3.1 Serial Collector
Serial Collector是指任何時刻都只有一個執行緒進行垃圾收集,這種策略有一個名字“stop the whole world",它需要停止整個應用的執行。這種型別的收集器適合於單CPU的機器。
Serial Copying Collector
此種GC用-XX:UseSerialGC選項配置,它只用於新生代物件的收集。1.5.0以後.
-XX:MaxTenuringThreshold來設定物件複製的次數。當eden空間不夠的時候,GC會將eden的活躍物件和一個名叫From survivor空間中尚不夠資格放入Old代的物件複製到另外一個名字叫To Survivor的空間。而此引數就是用來說明到底From survivor中的哪些物件不夠資格,假如這個引數設定為31,那麼也就是說只有物件複製31次以後才算是有資格的物件。
這裡需要注意幾個個問題:
Ø From Survivor和To survivor的角色是不斷的變化的,同一時間只有一塊空間處於使用狀態,這個空間就叫做From Survivor區,當複製一次後角色就發生了變化。
Ø 如果複製的過程中發現To survivor空間已經滿了,那麼就直接複製到old generation.
Ø 比較大的物件也會直接複製到Old generation,在開發中,我們應該儘量避免這種情況的發生。
Serial Mark-Compact Collector
序列的標記-整理收集器是JDK5 update6之前預設的老生代的垃圾收集器,此收集使得記憶體碎片最少化,但是它需要暫停的時間比較長
3.3.2 Parallel Collector
Parallel Collector主要是為了應對多CPU,大資料量的環境。
Parallel Collector又可以分為以下兩種:
Parallel Copying Collector
此種GC用-XX:UseParNewGC引數配置,它主要用於新生代的收集,此GC可以配合CMS一起使用。1.4.1以後
Parallel Mark-Compact Collector
此種GC用-XX:UseParallelOldGC引數配置,此GC主要用於老生代物件的收集。1.6.0
Parallel scavenging Collector
此種GC用-XX:UseParallelGC引數配置,它是對新生代物件的垃圾收集器,但是它不能和CMS配合使用,它適合於比較大新生代的情況,此收集器起始於jdk 1.4.0。它比較適合於對吞吐量高於暫停時間的場合。
Serial gc和Parallel gc可以用如下的圖來表示:
3.3.3 Concurrent Collector
Concurrent Collector通過並行的方式進行垃圾收集,這樣就減少了垃圾收集器收集一次的時間,這種GC在實時性要求高於吞吐量的時候比較有用。
此種GC可以用引數-XX:UseConcMarkSweepGC配置,此GC主要用於老生代和Perm代的收集。
Java虛擬機器(JVM)引數配置說明
在Java、J2EE大型應用中,JVM非標準引數的配置直接關係到整個系統的效能。
JVM非標準引數指的是JVM底層的一些配置引數,這些引數在一般開發中預設即可,不需要任何配置。但是在生產環境中,為了提高效能,往往需要調整這些引數,以求系統達到最佳新能。
另外這些引數的配置也是影響系統穩定性的一個重要因素,相信大多數Java開發人員都見過“OutOfMemory”型別的錯誤。呵呵,這其中很可能就是JVM引數配置不當或者就沒有配置沒意識到配置引起的。
為了說明這些引數,還需要說說JDK中的命令列工具一些知識做鋪墊。
首先看如何獲取這些命令配置資訊說明:
假設你是windows平臺,你安裝了J2SDK,那麼現在你從cmd控制檯視窗進入J2SDK安裝目錄下的bin目錄,然後執行java命令,出現如下結果,這些就是包括java.exe工具的和JVM的所有命令都在裡面。
-----------------------------------------------------------------------
D:\j2sdk15\bin>java
Usage: java[-options] class [args...]
(to execute a class)
or java [-options] -jar jarfile [args...]
(to execute a jar file)
where optionsinclude:
-client to select the "client" VM
-server to select the "server" VM
-hotspot is a synonym for the "client"VM [deprecated]
The default VM is client.
-cp <class search path of directories and zip/jar files>
-classpath <class search path of directories and zip/jar files>
A ; separated list of directories, JAR archives,
and ZIP archives to search for class files.
-D<name>=<value>
set a system property
-verbose[:class|gc|jni]
enable verbose output
-version print product version and exit
-version:<value>
require the specified version to run
-showversion print product version and continue
-jre-restrict-search | -jre-no-restrict-search
include/exclude user private JREs in the version search
-? -help print this help message
-X print helpon non-standard options
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
enable assertions
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
disable assertions
-esa | -enablesystemassertions
enable system assertions
-dsa | -disablesystemassertions
disable system assertions
-agentlib:<libname>[=<options>]
load native agent library <libname>, e.g. -agentlib:hprof
see also, -agentlib:jdwp=help and -agentlib:hprof=help
-agentpath:<pathname>[=<options>]
load native agent library by full pathname
-javaagent:<jarpath>[=<options>]
load Java programming language agent, see java.lang.instrument
-----------------------------------------------------------------------
在控制檯輸出資訊中,有個-X(注意是大寫)的命令,這個正是檢視JVM配置引數的命令。
其次,用java -X 命令檢視JVM的配置說明:
執行後如下結果,這些就是配置JVM引數的祕密武器,這些資訊都是英文的,為了方便閱讀,我根據自己的理解翻譯成中文了(不準確的地方還請各位博友斧正)
-----------------------------------------------------------------------
D:\j2sdk15\bin>java -X
-Xmixed mixed modeexecution (default)
-Xint interpreted mode execution only
-Xbootclasspath:<directories and zip/jar files separated by ;>
set search path for bootstrap classes and resources
-Xbootclasspath/a:<directories and zip/jar files separated by ;>
append to end of bootstrap class path
-Xbootclasspath/p:<directories and zip/jar files separated by ;>
prepend in front of bootstrap class path
-Xnoclassgc disable class garbagecollection
-Xincgc enableincremental garbage collection
-Xloggc:<file> log GC status to a file with time stamps
-Xbatch disablebackground compilation
-Xms<size> set initial Javaheap size
-Xmx<size> set maximum Javaheap size
-Xss<size> set java threadstack size
-Xprof outputcpu profiling data
-Xfuture enable strictestchecks, anticipating future default
-Xrs reduce use of OS signals by Java/VM (see documentation)
-Xcheck:jni perform additional checks forJNI functions
-Xshare:off do not attempt to use sharedclass data
-Xshare:auto use shared class data if possible(default)
-Xshare:on require using shared classdata, otherwise fail.
The -X options arenon-standard and subject to change without notice.
-----------------------------------------------------------------------
JVM配置引數中文說明:
-----------------------------------------------------------------------
1、-Xmixed mixed mode execution (default)
混合模式執行
2、-Xint interpreted mode execution only
解釋模式執行
3、-Xbootclasspath:<directories and zip/jarfiles separated by ;>
set search path for bootstrap classes and resources
設定zip/jar資源或者類(.class檔案)存放目錄路徑
3、-Xbootclasspath/a:<directories andzip/jar files separated by ;>
append to end of bootstrap class path
追加zip/jar資源或者類(.class檔案)存放目錄路徑
4、-Xbootclasspath/p:<directories andzip/jar files separated by ;>
prepend in front of bootstrap class path
預先載入zip/jar資源或者類(.class檔案)存放目錄路徑
5、-Xnoclassgc disable class garbage collection
關閉類垃圾回收功能
6、-Xincgc enable incremental garbage collection
開啟類的垃圾回收功能
7、-Xloggc:<file> logGC status to a file with time stamps
記錄垃圾回日誌到一個檔案。
8、-Xbatch disable background compilation
關閉後臺編譯
9、-Xms<size> set initial Java heap size
設定JVM初始化堆記憶體大小
10、-Xmx<size> set maximum Java heap size
設定JVM最大的堆記憶體大小
11、-Xss<size> set java thread stack size
設定JVM棧記憶體大小
12、-Xprof output cpu profiling data
輸入CPU概要表資料
13、-Xfuture enable strictest checks, anticipating future default
執行嚴格的程式碼檢查,預測可能出現的情況
14、-Xrs reduce use of OS signals by Java/VM (see documentation)
通過JVM還原作業系統訊號
15、-Xcheck:jni perform additional checks for JNI functions
對JNI函式執行檢查
16、-Xshare:off do not attempt to use shared class data
儘可能不去使用共享類的資料
17、-Xshare:auto use shared class data if possible (default)
儘可能的使用共享類的資料
18、-Xshare:on require using shared class data, otherwise fail.
儘可能的使用共享類的資料,否則執行失敗
The -Xoptions are non-standard and subject to change without notice.
-----------------------------------------------------------------------
怎麼用這這些引數呢?其實所有的命令列都是這麼一用,下面我就給出一個最簡單的HelloWorl的例子來演示這個引數的用法,非常的簡單。
HelloWorld.java
-----------------------------------------------
public class HelloWorld
{
public staticvoid main(String[] args)
{
System.out.println("HelloWorld!");
}
}
編譯並執行:
D:\j2sdk15\bin>javacHelloWorld.java
D:\j2sdk15\bin>java -Xms256M -Xmx512MHelloWorld
Hello World!
呵呵,這下滿足了吧!
實踐:在大型系統或者應用中配置JVM引數
比如你配置IDE工具的引數,常見的有IDEA、Eclipse,這個是在一個配置檔案中指定即可。
如果你要在J2EE環境中配置這些引數,那麼你需要在J2EE應用伺服器或者Servlet容器相關啟動引數設定處指定,其啟動檔案中來配置,Tomcat是在catalina.bat中配置,weblogic和websphere是在其他地方,具體我就說了,相信玩過的這些大型伺服器的人都知道,沒玩過的看看這篇文章,玩玩就知道了,呵呵。
另外常常有人問到jdk的一些相關命令用法,其實,當你看到這裡的時候,你應該知道如何獲取這些命令的用法了。如果你還不會,那麼,建議你去學學DOS,我是沒轍了。如果你會這些,還是沒有看明白,那麼你趕緊學學英語吧,這樣你就能看懂了。
另外:我在最後給出常用的幾個Java命令列說明,以供參考:
(1)、javac
用法:javac <選項> <原始檔>
其中,可能的選項包括:
-g 生成所有除錯資訊
-g:none 不生成任何除錯資訊
-g:{lines,vars,source} 只生成某些除錯資訊
-nowarn 不生成任何警告
-verbose 輸出有關編譯器正在執行的操作的訊息
-deprecation 輸出使用已過時的 API 的源位置
-classpath<路徑> 指定查詢使用者類檔案的位置
-cp <路徑> 指定查詢使用者類檔案的位置
-sourcepath<路徑> 指定查詢輸入原始檔的位置
-bootclasspath <路徑> 覆蓋引導類檔案的位置
-extdirs<目錄> 覆蓋安裝的擴充套件目錄的位置
-endorseddirs <目錄> 覆蓋簽名的標準路徑的位置
-d <目錄> 指定存放生成的類檔案的位置
-encoding<編碼> 指定原始檔使用的字元編碼
-source <版本> 提供與指定版本的源相容性
-target <版本> 生成特定 VM 版本的類檔案
-version 版本資訊
-help 輸出標準選項的提要
-X 輸出非標準選項的提要
-J<標誌> 直接將<標誌>傳遞給執行時系統
(2)、jar
用法:jar {ctxu}[vfm0Mi] [jar-檔案][manifest-檔案] [-C 目錄] 檔名 ...
選項:
-c 建立新的存檔
-t 列出存檔內容的列表
-x 展開存檔中的命名的(或所有的〕檔案
-u 更新已存在的存檔
-v 生成詳細輸出到標準輸出上
-f 指定存檔檔名
-m 包含來自標明檔案的標明資訊
-0 只儲存方式;未用ZIP壓縮格式
-M 不產生所有項的清單(manifest〕檔案
-i 為指定的jar檔案產生索引資訊
-C 改變到指定的目錄,並且包含下列檔案:
如果一個檔名是一個目錄,它將被遞迴處理。
清單(manifest〕檔名和存檔檔名都需要被指定,按'm' 和 'f'標誌指定的相同順序。
示例1:將兩個class檔案存檔到一個名為 'classes.jar' 的存檔檔案中:
jar cvf classes.jar Foo.class Bar.class
示例2:用一個存在的清單(manifest)檔案 'mymanifest' 將foo/ 目錄下的所有
檔案存檔到一個名為 'classes.jar' 的存檔檔案中:
jar cvfm classes.jar mymanifest -C foo/ .
(3)、javadoc
javadoc: 錯誤 - 未指定軟體包或類。
用法:javadoc [選項] [軟體包名稱] [原始檔] [@file]
-overview <檔案> 讀取 HTML 檔案的概述文件
-public 僅顯示公共類和成員
-protected 顯示受保護/公共類和成員(預設)
-package 顯示軟體包/受保護/公共類和成員
-private 顯示所有類和成員
-help 顯示命令列選項並退出
-doclet <類> 通過替代 doclet 生成輸出
-docletpath <路徑> 指定查詢 doclet 類檔案的位置
-sourcepath <路徑列表> 指定查詢原始檔的位置
-classpath <路徑列表> 指定查詢使用者類檔案的位置
-exclude <軟體包列表> 指定要排除的軟體包的列表
-subpackages <子軟體包列表> 指定要遞迴裝入的子軟體包
-breakiterator 使用 BreakIterator 計算第 1句
-bootclasspath <路徑列表> 覆蓋引導類載入器所裝入的
類檔案的位置
-source <版本> 提供與指定版本的源相容性
-extdirs <目錄列表> 覆蓋安裝的擴充套件目錄的位置
-verbose 輸出有關 Javadoc 正在執行的操作的訊息
-locale <名稱> 要使用的語言環境,例如 en_US 或en_US_WIN
-encoding <名稱> 原始檔編碼名稱
-quiet 不顯示狀態訊息
-J<標誌> 直接將<標誌>傳遞給執行時系統
通過標準 doclet 提供:
-d <目錄> 輸出檔案的目標目錄
-use 建立類和軟體包用法頁面
-version 包含 @version 段
-author 包含 @author 段
-docfilessubdirs 遞迴複製文件檔案子目錄
-splitindex 將索引分為每個字母對應一個檔案
-windowtitle <文字> 文件的瀏覽器視窗標題
-doctitle <html 程式碼> 包含概述頁面的標題
-header <html 程式碼> 包含每個頁面的頁首文字
-footer <html 程式碼> 包含每個頁面的頁尾文字
-bottom <html 程式碼> 包含每個頁面的底部文字
-link<url> 建立指向位於 <url> 的 javadoc 輸出的連結
-linkoffline<url> <url2> 利用位於 <url2> 的軟體包列表連結至位於 <url>
的文件
-excludedocfilessubdir<名稱 1>:..排除帶有給定名稱的所有文件檔案子目錄。
-group <名稱><p1>:<p2>.. 在概述頁面中,將指定的軟體包分組
-nocomment 抑止描述和標記,只生成宣告。
-nodeprecated 不包含 @deprecated 資訊
-noqualifier <名稱 1>:<名稱 2>:...從輸出中排除限定符的列表。
-nosince 不包含 @since 資訊
-notimestamp 不包含隱藏時間戳
-nodeprecatedlist 不生成已過時的列表
-notree 不生成類分層結構
-noindex 不生成索引
-nohelp 不生成幫助連結
-nonavbar 不生成導航欄
-serialwarn 生成有關 @serial 標記的警告
-tag <名稱>:<位置>:<標題> 指定單個變數自定義標記
-taglet 要註冊的 Taglet 的全限定名稱
-tagletpath Taglet 的路徑
-charset <字符集> 用於跨平臺檢視生成的文件的字符集。
-helpfile <檔案> 包含幫助連結所連結到的檔案
-linksource 以 HTML 格式生成源
-sourcetab <製表符長度> 指定源中每個製表符佔據的空格數
-keywords 使軟體包、類和成員資訊附帶 HTML 元標記
-stylesheetfile<路徑> 用於更改生成文件的樣式的檔案
-docencoding <名稱> 輸出編碼名稱
(4)、rmid
rmid: 非法選項:-?
用法:rmid <option>
其中,<option> 包括:
-port<option> 指定供 rmid 使用的埠
-log<directory> 指定 rmid 將日誌寫入的目錄
-stop 停止當前的 rmid 呼叫(對指定埠)
-C<runtime 標記> 向每個子程式傳遞引數(啟用組)
-J<runtime 標記> 向 java 解釋程式傳遞引數
相關文章
- JVM中堆的介紹JVM
- JVM常用調優工具介紹JVM
- JVM 從入門到實戰--- 01 JVM 基本介紹JVM
- JVM中記憶體和GC的介紹JVM記憶體GC
- JVM(三)----垃圾收集演算法及Safe Point介紹JVM演算法
- 介紹
- Proxy介紹
- Reflect介紹
- Azkaban介紹
- 模式介紹模式
- ZooKeeper介紹
- css介紹CSS
- PostgreSQLHooK介紹SQLHook
- DuelJS 介紹JS
- Docker介紹Docker
- StarRocks 介紹
- JCache 介紹
- zigbee 介紹
- GO 介紹Go
- RPC介紹RPC
- springcloud介紹SpringGCCloud
- CSRedisCore 介紹Redis
- AJAX 介紹
- php介紹PHP
- Pyzmq介紹MQ
- uniswap介紹
- LDAP 介紹LDA
- rustyline 介紹Rust
- SpringBoot介紹Spring Boot
- JSON 介紹JSON
- BitMap介紹
- Yocto 介紹
- 自我介紹
- git介紹Git
- FontFamily介紹
- Dubbo介紹
- Duktape 介紹
- jsoncpp 介紹JSON
- 公文介紹