jvm優化筆記

一. JAVA虛擬機是個啥

所謂虛擬機,就是一臺虛擬的機器(Virtual Machine).指通過軟件模擬的具有完整硬件系統功能的、運行在一個完全隔離環境中的完整計算機系統。

分為系統虛擬機和程序虛擬機。系統虛擬機有vmware、VirtualBox等,基於系統級的虛擬化技術。系統虛擬機是對物理計算機的仿真,提供了一個可運行完整操作的操作系統平臺。

程序虛擬機典型的就是JAVA虛擬機,為JAVA的跨平臺特性提供保障,將Java源碼編譯成.class文件一次編寫各處運行。現在主流的JAVA虛擬機是HotSpot。

二. JAVA虛擬機的組成部分

①類加載子系統:負責從文件系統或者網絡中加載class信息的,加載的信息存放在一塊被稱之為方法區的內存空間中。

②方法區:存放類信息、常量信息、常量池信息、包括字符串字面量(literal)和數字常量等。

③JAVA堆:在JVM啟動的時候建立JAVA堆,是JAVA程序最主要的內存工作區域,幾乎所有的對象實例都存放到JAVA堆中,堆空間是所有線程共享的。

④直接內存:JAVA NIO庫允許JAVA程序使用直接內存,從而提高性能,通常直接內存速度會優於JAVA堆。讀寫頻繁的場合可以考慮使用。

⑤每個JAVA虛擬機線程都有一個私有的棧,一個線程的JAVA棧在線程創建的時候被創建,JAVA棧中保存著局部變量、方法參數、同時JAVA的方法調用、返回值等。

⑥垃圾回收系統(GC):JVM重點部分,JAVA程序猿無需手工清理。

jvm還包括:本地方法棧、PC寄存器(線程私有)、執行引擎(執行字節碼文件)等九塊基本結構。

三. 堆、棧、方法區之間的密切聯繫

堆說:"我解決的是數據存儲的問題,這個數據怎麼放、放哪兒這些事都歸我管!我做的事比較多。“

棧有些不服了,說:"我主要做的是解決程序如何執行的問題,程序運行中如何處理數據都是我弄的。我包括局部變量表(報錯函數參數及局部變量存儲的地兒),操作數棧(存儲臨時變量),偵數據區(常量池的指針和程序執行時的異常信息)三部分。"

方法區看這倆小夥吵得不可開交,過來說:"我是你倆的先決條件,沒我你倆誰都不可能存在!我在jdk1.7屬於堆中永久區的一部分,jdk1.8移到了元空間(物理內存中了)。注意哦:方法區的大小決定系統能保存多少個類!"

"… …"。

堆內存:JAVA創建一個類的實例對象或數組時,都在堆內存中為新對象分配內存空間。所有線程共享。

棧內存:棧中只保存基本數據類型和自定義對象的引用。線程私有的棧。

方法區:類型信息和類靜態變量都保存在方法區中,jdk1.7方法區是屬於堆中永久區的一部分,jdk1.8移到了元空間(物理內存中了)。方法區中包含的都是在整個程序中永遠唯一的元素,如class,static變量。所有線程共享哦。

如圖:1、類的信息和靜態方法、變量(模板信息)都是放在方法區中的。2、new 一個對象的實例即在堆中開闢空間存儲對象的實例。3、在JAVA棧中執行的線程中創建實例的引用。

參考資料來自:https://www.open-open.com/lib/view/open1432200119489.html

四. JAVA 堆又是啥回事

JAVA 堆是和JAVA 應用程序關係最密切的內存空間,幾乎所有的對象都放在其中,並且自動管理的,通過垃圾回收機制來清理垃圾對象,不需要顯示地釋放。

根據 GC 回收機制不同,JAVA 堆有可能擁有不同的結構。最為常見的是將整個JAVA 堆分為新生代(DefNew)和老年代(Tenured)。其中新生代存放新生對象或者年齡不大的對象,老年代則存放老年對象。

新生代分為Eden區、s0區、s1區,s0和s1也被稱為from和to區域,他們是兩塊大小相等並且可以互換角色的空間。s0和s1來回copy和比較來判斷JAVA對象是否在使用,一段程序運行時新生代頻繁地gc,頻繁的生產、消亡。

絕大多數的情況下,對象首先分配在Eden區,在一次新生代回收後,如果對象還存活,則會進入s0或者s1區,之後每經過一次新生代回收,如果對象還存活則他的年齡就+1,當對象達到一定的年齡後,則進入老年代。

五. JVM 參數

棧、堆、方法區都是極其神通廣大的,jvm的參數其實也是圍繞著他們來配置的。

①-XX:+PrintGC 使用這個參數,虛擬機啟動後,遇到GC就會打印日誌。

②-XX:+UseSerialGC 配置串行回收器。

③-XX:+PrintGCDetails 可以查看詳細信息,包括各個區的信息。

④-Xms: 設置JAVA啟動時初始堆大小。

⑤-Xmx: 設置JAVA程序能夠獲得最大堆的大小。

⑥-Xmx20m -Xms5m -XX:+PrintCommandLineFlags:可以將隱式或者顯示傳遞給虛擬機的參數輸出。

注意:在實際工作中,我們可以直接將初始的堆大小和最大堆大小設置相等,這樣的好處是可以減少程序運行垃圾回收的次數,從而提高性能。-XX是系統級別的jvm之上的比如日誌信息、使用什麼樣的GC機制。非-XX是應用層面的配置的。後面的+號表示啟用、-號表示禁用。

  1. -Xmn:設置堆裡面的新生代的大小,設置一個比較大的新生代會減少老年代的大小,這個參數對系統的性能以及GC行為有很大的影響,新生代大小一般會設置為整個對空間的1/3到1/4左右。
  2. -XX:SurvivorRatio: 用來設置新生代中的eden空間和from/to空間的比例。含義:-XX:SurvivorRatio=eden/from=eden/to

注意:不同的堆分佈情況,對系統執行會產生一定的影響,在實際工作中,根據系統的特點做出合理的配置,基本策略:儘可能將對象預留在新生代,減少對老年代gc次數。

9.-XX:NewRatio=老年代/新生代 甚至新老年代的比例。

10.(1)-XX:+HeapDumpOnOutOfMemoryError參數表示當JVM發生OOM時,自動生成DUMP文件。

(2)-XX:HeapDumpPath={目錄}參數表示生成DUMP文件的路徑,也可以指定文件名稱,例如:-XX:HeapDumpPath=​${目錄}/java_heapdump.hprof。如果不指定文件名,默認為:java__<date>_<time>_heapDump.hprof。 /<time>/<date>

當JAVA出現內存溢出的時候,輸出dump文件。

六. 垃圾回收機制

GC :Garbage Collections 字面意思是垃圾回收器,釋放垃圾佔用的空間。讓創建的對象不需要像c、c++那樣delete、free掉 。對於c、c++的開發人員來說內存是開發人員分配的,也就是說還要對內存進行維護和釋放。對於Java程序員來說,一個對象的內存分配是在虛擬機的自動內存分配機制的幫助下,不再需要為每一個new操作去寫配對的delete/free代碼,而且不容易出現內存洩露和內存溢出問題,但是,如果出現了內存洩露和內存溢出問題,而開發者又不瞭解虛擬機是怎麼分配內存的話,那麼定位錯誤和排除錯誤將是一件很困難的事情。

這裡要介紹幾種垃圾收集算法:

  ①Mark-Sweep(標記-清除)算法

  這是最基礎的垃圾回收算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。

標記-清除算法實現起來比較容易,但是有一個比較嚴重的問題就是容易產生內存碎片,碎片太多可能會導致後續過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。 

②.Copying(複製)算法

  為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象複製到另外一塊上面,然後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。

這種算法雖然實現簡單,運行高效且不容易產生內存碎片,但是卻對內存空間的使用做出了高昂的代價,因為能夠使用的內存縮減到原來的一半。 很顯然,Copying算法的效率跟存活對象的數目多少有很大的關係,如果存活對象很多,那麼Copying算法的效率將會大大降低。我們的新生代GC算法採用的是這種算法。

③Mark-Compact(標記-整理)算法

  為了解決Copying算法的缺陷,充分利用內存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。

 在一般廠商JVM中老年代GC就是使用的這種算法,由於老年代的特點是每次回收都只回收少量對象。

  上面的是一些常見的垃圾收集算法,垃圾收集算法是內存回收的理論基礎,而垃圾收集器就是內存回收的具體實現。下面有幾種創建的垃圾收集器,用戶可以根據自己的需求組合出新年代和老年代使用的收集器。下面是常見的劃分辦法

  新生代GC :串行GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew)

  串行GC:在整個掃描和複製過程採用單線程的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是非常高的應用上,是client級別默認的GC方式,可以通過-XX:+UseSerialGC來強制指定。

  並行回收GC:在整個掃描和複製過程採用多線程的方式來進行,適用於多CPU、對暫停時間要求較短的應用上,是server級別默認採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數。

並行GC:與老年代的併發GC配合使用。

  老年代GC:串行GC(Serial MSC)、並行GC(Parallel MSC)和併發GC(CMS)。

  串行GC(Serial MSC):client模式下的默認GC方式,可通過-XX:+UseSerialGC強制指定。每次進行全部回收,進行Compact,非常耗費時間。

  並行GC(Parallel MSC):吞吐量大,但是GC的時候響應很慢:server模式下的默認GC方式,也可用-XX:+UseParallelGC=強制指定。可以在選項後加等號來制定並行的線程數。

  併發GC(CMS):響應比並行gc快很多,但是犧牲了一定的吞吐量。

參考:https://www.cnblogs.com/wjtaigwh/p/6635484.html

如上

推薦:jdk提供了好多的性能檢測工具如:jconsole,jVisualVm和jstatd等工具挺實用的。


分享到:


相關文章: