原文:https://github.com/linsheng97...java
從 GC 算法的角度,G1 選擇的是複合算法,能夠簡化理解爲:git
從性能的角度看,一般關注三個方面,內存佔用(footprint)、延時(latency)和吞吐量(throughput),大多數狀況下調優會側重於其中一個或者兩個方面的目標,不多有狀況能夠兼顧三個不同的角度。固然,除了上面一般的三個方面,也可能須要考慮其餘 GC 相關的場景,例如,OOM 也可能與不合理的 GC 相關參數有關;或者,應用啓動速度方面的需求,GC 也會是個考慮的方面。
基本的調優思路能夠總結爲:程序員
經過分析肯定具體調整的參數或者軟硬件配置。驗證是否達到調優目標,若是達到目標,便可以考慮結束調優;不然,重複完成分析、調整、驗證這
個過程。github
經過設置一個較大的年輕代預留新對象,設置合理的 Survivor 區而且提供 Survivor 區的使用率,能夠將年輕對象保存在年輕代。算法
使用參數-XX:PetenureSizeThreshold 設置大對象直接進入年老代的閾值spring
這個閾值的最大值能夠經過參數-XX:MaxTenuringThreshold 來設置,默認值是 15編程
得到一個穩定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 同樣。緩存
–Xmx380m –Xms3800m:設置 Java 堆的最大值和初始值。通常狀況下,爲了不堆內存的頻繁震盪,致使系統性能降低,咱們的作法是設置最大堆等於最小堆。假設這裏把最小堆減小爲最大堆的一半,即 1900m,那麼 JVM 會盡量在 1900MB 堆空間中運行,若是這樣,發生 GC 的可能性就會比較高;
-Xss128k:減小線程棧的大小,這樣可使剩餘的系統內存支持更多的線程;
-Xmn2g:設置年輕代區域大小爲 2GB;
–XX:+UseParallelGC:年輕代使用並行垃圾回收收集器。這是一個關注吞吐量的收集器,能夠儘量地減小 GC 時間。
–XX:ParallelGC-Threads:設置用於垃圾回收的線程數,一般狀況下,能夠設置和 CPU 數量相等。但在 CPU 數量比較多的狀況下,設置相對較小的數值也是合理的;
–XX:+UseParallelOldGC:設置年老代使用並行回收收集器。安全
–XX:+LargePageSizeInBytes:設置大頁的大小。
內存分頁 (Paging) 是在使用 MMU 的基礎上,提出的一種內存管理機制。它將虛擬地址和物理地址按固定大小(4K)分割成頁 (page) 和頁幀 (page frame),並保證頁與頁幀的大小相同。這種機制,從數據結構上,保證了訪問內存的高效,並使 OS 能支持非連續性的內存分配。網絡
爲下降應用軟件的垃圾回收時的停頓,首先考慮的是使用關注系統停頓的 CMS 回收器,其次,爲了減小 Full GC 次數,應儘量將對象預留在年輕代。
gc()函數的做用只是提醒虛擬機:程序員但願進行一次垃圾回收。可是它不能保證垃圾回收必定會進行,並且具體何時進行是取決於具體的虛擬機的,不一樣的虛擬機有不一樣的對策。
Parallel GC的Young區採用的是Mark-Copy算法,Old區採用的是Mark-Sweep-Compact來實現,Parallel執行,因此決定了Parallel GC在執行YGC、FGC時都會Stop-The-World,但完成GC的速度也會比較快。
CMS GC的Young區採用的也是Mark-Copy,Old區採用的是Concurrent Mark-Sweep,因此決定了CMS GC在對old區回收時形成的STW時間會更短,避免對應用產生太大的時延影響。
G1 GC採用了Garbage First算法,比較複雜,實現的好呢,理論上是會比CMS GC能夠更高效,同時對應用的影響也很小。
ZGC、Azul Pauseless GC採用的算法很不同,尤爲是Pauseless GC,其中的很重要的一個技巧是經過增長Read Barrier來更好的識別對GC而言最關鍵的references變化的狀況。
當young gen中的eden區分配滿的時候觸發young gc,當年老代內存不足時,將執行Major GC,也叫 Full GC。
gc()函數的做用只是提醒虛擬機:程序員但願進行一次垃圾回收。可是它不能保證垃圾回收必定會進行,並且具體何時進行是取決於具體的虛擬機的,不一樣的虛擬機有不一樣的對策。
不一樣的引用類型,主要體現的是對象不一樣的可達性(reachable)狀態和對垃圾收集的影響。
所謂強引用("Strong" Reference),就是咱們最多見的普通對象引用,只要還有強引用指向一個對象,就能代表對象還「活着」,垃圾收集器不會碰這種對象。對於一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲 null,就是能夠被垃圾收集的了,固然具體回收時機仍是要看垃圾收集策略。
軟引用(SoftReference),是一種相對強引用弱化一些的引用,可讓對象豁免一些垃圾收集,只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象。JVM 會確保在拋出OutOfMemoryError 以前,清理軟引用指向的對象。軟引用一般用來實現內存敏感的緩存,若是還有空閒內存,就能夠暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。
SoftReference 在「弱引用WeakReference」中屬於最強的引用。SoftReference 所指向的對象,當沒有強引用指向它時,會在內存中停留一段的時間,垃圾回收器會根據 JVM 內存的使用狀況(內存的緊缺程度)以及 SoftReference 的 get() 方法的調用狀況來決定是否對其進行回收。
對於幻象引用(PhantomReference ),有時候也翻譯成虛引用,你不能經過它訪問對象。幻象引用僅僅是提供了一種確保對象被 finalize 之後,作某些事情的機制,好比,一般用來作所謂的 Post-Mortem 清理機制,如 Java 平臺自身 Cleaner 機制等,也有人利用幻象引用監控對象的建立和銷燬。
Object counter = new Object(); ReferenceQueue refQueue = new ReferenceQueue<>(); PhantomReference<Object> p = new PhantomReference<>(counter, refQueue); counter = null; System.gc(); try { // Remove 是一個阻塞方法,能夠指定 timeout,或者選擇一直阻塞 Reference<Object> ref = refQueue.remove(1000L); if (ref != null) { // do something } } catch (InterruptedException e) { // Handle it }
通常來講,咱們把 Java 的類加載過程分爲三個主要步驟:加載、連接、初始化。
首先是加載階段(Loading),它是 Java 將字節碼數據從不同的數據源讀取到 JVM 中,並映射爲 JVM 承認的數據結構(Class 對象),這里的數據源多是各類各樣的形態,如 jar 文件、class 文件,甚至是網絡數據源等;若是輸入數據不是 ClassFile 的結構,則會拋出 ClassFormatError。加載階段是用戶參與的階段,咱們能夠自定義類加載器,去實現本身的類加載過程。
第二階段是連接(Linking),這是核心的步驟,簡單說是把原始的類定義信息平滑地轉化入 JVM 運行的過程當中。這里可進一步細分爲三個步驟:
最後是初始化階段(initialization),這一步真正去執行類初始化的代碼邏輯,包括靜態字段賦值的動做,以及執行類定義中的靜態初始化塊內的邏輯,編譯器在編譯階段就會把這部分邏輯整理好,父類型的初始化邏輯優先於當前類型的邏輯。
簡單說就是當類加載器(Class-Loader)試圖加載某個類型的時候,除非父加載器找不到相應類型,不然盡量將這個任務代理給當前加載器的父加載器去作。使用委派模型的目的是避免重複加載 Java 類型。
Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實現代碼則是做爲 Java 應用所依賴的 jar 包被包含進類路徑(CLASSPATH)裏。SPI接口中的代碼常常須要加載具體的實現類。那麼問題來了,SPI的接口是Java核心庫的一部分,是由啓動類加載器(Bootstrap Classloader)來加載的;SPI的實現類是由系統類加載器(System ClassLoader)來加載的。引導類加載器是沒法找到 SPI 的實現類的,由於依照雙親委派模型,BootstrapClassloader沒法委派AppClassLoader來加載類。而線程上下文類加載器破壞了「雙親委派模型」,能夠在執行線程中拋棄雙親委派加載鏈模式,使程序能夠逆向使用類加載器。
ServiceLoader 的加載代碼:
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
ContextClassLoader默認存放了AppClassLoader的引用,因爲它是在運行時被放在了線程中,因此無論當前程序處於何處(BootstrapClassLoader或是ExtClassLoader等),在任何須要的時候均可以用Thread.currentThread().getContextClassLoader()取出應用程序類加載器來完成須要的操做。
自定義類加載器,常見的場景有:
從本地路徑 load class 的例子:
public class CustomClassLoader extends ClassLoader { @Override public Class findClass(String name) throws ClassNotFoundException { byte[] b = loadClassFromFile(name); return defineClass(name, b, 0, b.length); } private byte[] loadClassFromFile(String fileName) { InputStream inputStream = getClass().getClassLoader().getResourceAsStream( fileName.replace('.', File.separatorChar) + ".class"); byte[] buffer; ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); int nextValue = 0; try { while ( (nextValue = inputStream.read()) != -1 ) { byteStream.write(nextValue); } } catch (IOException e) { e.printStackTrace(); } buffer = byteStream.toByteArray(); return buffer; } }
反射機制是 Java 語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。經過反射咱們能夠直接操做類或者對象,好比獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方法或者構造對象,甚至能夠運行時修改類定義。 動態代理是一種方便運行時動態構建代理、動態處理代理方法調用的機制,不少場景都是利用相似機制作到的,好比用來包裝 RPC 調用、面向切面的編程(AOP)。 實現動態代理的方式不少,好比 JDK 自身提供的動態代理,就是主要利用了上面提到的反射機制。還有其餘的實現方式,好比利用傳說中更高性能的字節碼操做機制,相似 ASM、cglib(基於 ASM)、Javassist 等。
public class MyDynamicProxy { public static void main (String[] args) { HelloImpl hello = new HelloImpl(); MyInvocationHandler handler = new MyInvocationHandler(hello); // 構造代碼實例 Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler); // 調用代理方法 proxyHello.sayHello(); } } interface Hello { void sayHello(); } class HelloImpl implements Hello { @Override public void sayHello() { System.out.println("Hello World"); } } class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Invoking sayHello"); Object result = method.invoke(target, args); return result; } }
JDK動態代理只能對實現了接口的類生成代理,而不能針對類,CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法(繼承)。
JDK Proxy 的優點:
基於相似 cglib 框架的優點:
(1)當Bean實現接口時,Spring就會用JDK的動態代理
(2)當Bean沒有實現接口時,Spring使用CGlib是實現
(3)能夠強制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
(1)使用CGLib實現動態代理,CGLib底層採用ASM字節碼生成框架,使用字節碼技術生成代理類,比使用Java反射效率要高。惟一須要注意的是,CGLib不能對聲明爲final的方法進行代理,由於CGLib原理是動態生成被代理類的子類。可是JDK也在升級,開始引入不少字節碼技術來實現部分動態代理的功能,因此在某些測試下不必定是CGLib更快。
ASM、Javassist、CGLib、Byte Budy。