從 GC 算法的角度,G1 選擇的是複合算法,能夠簡化理解爲:java
從性能的角度看,一般關注三個方面,內存佔用(footprint)、延時(latency)和吞吐量(throughput),大多數狀況下調優會側重於其中一個或者兩個方面的目標,不多有狀況能夠兼顧三個不同的角度。固然,除了上面一般的三個方面,也可能須要考慮其餘 GC 相關的場景,例如,OOM 也可能與不合理的 GC 相關參數有關;或者,應用啓動速度方面的需求,GC 也會是個考慮的方面。 基本的調優思路能夠總結爲:程序員
經過分析肯定具體調整的參數或者軟硬件配置。驗證是否達到調優目標,若是達到目標,便可以考慮結束調優;不然,重複完成分析、調整、驗證這 個過程。面試
新對象預留在年輕代 經過設置一個較大的年輕代預留新對象,設置合理的 Survivor 區而且提供 Survivor 區的使用率,能夠將年輕對象保存在年輕代。算法
大對象進入年老代 使用參數-XX:PetenureSizeThreshold 設置大對象直接進入年老代的閾值spring
設置對象進入年老代的年齡 這個閾值的最大值能夠經過參數-XX:MaxTenuringThreshold 來設置,默認值是 15編程
穩定的 Java 堆 得到一個穩定的堆大小的方法是使-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 次數,應儘量將對象預留在年輕代。bash
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。
你們若是想獲取更多的面試資料與架構知識,你們能夠加個人程序員交流羣: 833145934,羣內每晚都會有阿里技術大牛講解的最新Java架構技術。並會錄製錄播視頻分享在羣公告中,做爲給廣大朋友的加羣的福利——分佈式(Dubbo、Redis、RabbitMQ、Netty、RPC、Zookeeper、高併發、高可用架構)/微服務(Spring Boot、Spring Cloud)/源碼(Spring、Mybatis)/性能優化(JVM、TomCat、MySQL)【加羣備註好消息領取最新面試資料】