目錄
? ? 1 Java基礎(chǔ)
? ? 2 Java集合
? ? 3 Java多線程
? ? 4 JVM
? ? 5 常見問題匯總參考資料
? ? ·《Java編程思想》
? ? ·《Java Web 技術(shù)內(nèi)幕》
? ? ·《Java 并發(fā)編程實(shí)戰(zhàn)》
3 Java多線程
3.1 基本線程機(jī)制
? ??使用線程可以把占據(jù)時(shí)間長(zhǎng)的程序中的任務(wù)放到后臺(tái)去處理适篙,程序的運(yùn)行速度可能加快。進(jìn)程是所有線程的集合意推,每一個(gè)線程是進(jìn)程中的一條執(zhí)行路徑。
? ? 底層機(jī)制是切分CPU時(shí)間吞鸭。
3.1.1 定義線程的方式
(1)實(shí)現(xiàn)Runable接口寺董,實(shí)現(xiàn)run()方法。
class CreateRunnable implements Runnable {
????@Override
????????public void run() {
? ? ? ? ????for (inti = 0; i< 10; i++) {
????????????????System.out.println("i:" + i);
????????????}
????????}
}
publicclass ThreadDemo2 {
????????public static void main(String[] args) {
????????????System.out.println("-----多線程創(chuàng)建開始-----");
????????????????// 1.創(chuàng)建一個(gè)線程
????????????????CreateRunnable createThread = new CreateRunnable();
// 2.開始執(zhí)行線程 注意?開啟線程不是調(diào)用run方法瞒大,而是start方法
????????????????System.out.println("-----多線程創(chuàng)建啟動(dòng)-----");
????????????????Thread thread = new Thread(createThread);
????????????????thread.start();
????????????????System.out.println("-----多線程創(chuàng)建結(jié)束-----");
????????}
}
(2)繼承Thread類螃征,重寫run()方法
public class ThreadDemo01 extends Thread{
? ? public ThreadDemo01(){
? ? ? ? //編寫子類的構(gòu)造方法搪桂,可缺省
? ? }
? ? public void run(){
? ? ? ? //編寫自己的線程代碼
? ? ? ? System.out.println(Thread.currentThread().getName());
? ? }
? ? public static void main(String[] args){
? ? ? ? ThreadDemo01 threadDemo01 = new ThreadDemo01();
? ? ? ? threadDemo01.setName("我是自定義的線程1");
? ? ? ? threadDemo01.start();? ? ?
? ? ? ? System.out.println(Thread.currentThread().toString());?
? ? }
}
(3)通過Callable和FutureTask創(chuàng)建線程
a:創(chuàng)建Callable接口的實(shí)現(xiàn)類 透敌,并實(shí)現(xiàn)Call方法
b:創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)現(xiàn),使用FutureTask類包裝Callable對(duì)象踢械,該FutureTask對(duì)象封裝了Callable對(duì)象的Call方法的返回值
c:使用FutureTask對(duì)象作為Thread對(duì)象的target創(chuàng)建并啟動(dòng)線程
d:調(diào)用FutureTask對(duì)象的get()來獲取子線程執(zhí)行結(jié)束的返回值
public class ThreadDemo03 {
? ? public static void main(String[] args) {
? ? ? ? // TODO Auto-generated method stub
? ? ? ? Callable<Object> oneCallable = new Tickets<Object>();
? ? ? ? FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);
? ? ? ? Thread t = new Thread(oneTask);
? ? ? ? System.out.println(Thread.currentThread().getName());
? ? ? ? t.start();
? ? }
}
class Tickets<Object> implements Callable<Object>{
//重寫call方法
? ? @Override
? ? public Object call() throws Exception {
? ? ? ? // TODO Auto-generated method stub
? ? ? ? System.out.println(Thread.currentThread().getName()+"-->我是通過實(shí)現(xiàn)Callable接口通過FutureTask包裝器來實(shí)現(xiàn)的線程");
? ? ? ? return null;
? ? }?
}
(4)通過線程池創(chuàng)建線程
public class ThreadDemo05{
? ? private static int POOL_NUM = 10;? ? //線程池?cái)?shù)量
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? // TODO Auto-generated method stub
?ExecutorService executorService = Executors.newFixedThreadPool(5);?
? ? ? ? for(int i = 0; i<POOL_NUM; i++)?
? ? ? ? {?
? ? ? ? ? ? RunnableThread thread = new RunnableThread();
? ? ? ? ? ? //Thread.sleep(1000);
? ? ? ? ? ? executorService.execute(thread);?
? ? ? ? }
? ? ? ? //關(guān)閉線程池
? ? ? ? executorService.shutdown();
? ? }?
}
class RunnableThread implements Runnable?
{? ?
? ? @Override
? ? public void run()?
? ? {?
? ? ? ? System.out.println("通過線程池方式創(chuàng)建的線程:" + Thread.currentThread().getName() + " ");?
? ? }?
}?
·總結(jié):
????前面兩種可以歸結(jié)為一類:無返回值酗电,原因很簡(jiǎn)單,通過重寫run方法内列,run方式的返回值是void撵术,所以沒有辦法返回結(jié)果
????后面兩種可以歸結(jié)成一類:有返回值,通過Callable接口话瞧,就要實(shí)現(xiàn)call方法嫩与,這個(gè)方法的返回值是Object,所以返回的結(jié)果可以放在Object對(duì)象中
· 注意事項(xiàng)
? ? · 創(chuàng)建線程交排,優(yōu)先選擇Runable划滋,接口可實(shí)現(xiàn)擴(kuò)展。
? ? · 開啟線程需要調(diào)用start()方法埃篓,若直接調(diào)用run()处坪,則為普通方法,在main函數(shù)中執(zhí)行架专。
3.1.2 Thread類常用API
常用線程api方法
· start() 啟動(dòng)線程
·?currentThread() 獲取當(dāng)前線程對(duì)象
· getID() 獲取當(dāng)前線程IDThread-編號(hào)? 該編號(hào)從0開始
· getName() 獲取當(dāng)前線程名稱
· sleep(long mill) 休眠線程static方法
· Stop() 停止線程
常用線程構(gòu)造函數(shù)
· Thread()分配一個(gè)新的Thread?對(duì)象
· Thread(String name)分配一個(gè)新的Thread對(duì)象同窘,具有指定的name正如其名。
· Thread(Runable r)分配一個(gè)新的Thread對(duì)象
· Thread(Runable r, String name)分配一個(gè)新的Thread對(duì)象
3.1.3 線程狀態(tài)
線程從創(chuàng)建部脚、運(yùn)行到結(jié)束總是處于下面五個(gè)狀態(tài)之一:新建狀態(tài)想邦、就緒狀態(tài)、運(yùn)行狀態(tài)委刘、阻塞狀態(tài)及死亡狀態(tài)丧没。
(1)新建狀態(tài)
? ??????當(dāng)用new操作符創(chuàng)建一個(gè)線程時(shí)。
(2)就緒狀態(tài)
? ??????當(dāng)start()方法返回后钱雷,線程就處于就緒狀態(tài)骂铁,等待CPU的線程調(diào)度。
(3)運(yùn)行狀態(tài)
當(dāng)線程獲得CPU時(shí)間后罩抗,它才進(jìn)入運(yùn)行狀態(tài)拉庵,真正開始執(zhí)行run()方法.
(4)阻塞狀態(tài)
? ??????線程運(yùn)行過程中,可能由于各種原因進(jìn)入阻塞狀態(tài):
? ? ? ? ? ? · 線程通過調(diào)用sleep方法進(jìn)入睡眠狀態(tài)套蒂;
? ? ? ? ? ? · 線程調(diào)用一個(gè)在I/O上被阻塞的操作钞支,即該操作在輸入輸出操作完成之前不會(huì)返回到它的調(diào)用者茫蛹;
? ? ? ? ? ? · 線程試圖得到一個(gè)鎖,而該鎖正被其他線程持有烁挟;
? ? ? ? ? ? · 線程在等待某個(gè)觸發(fā)條件婴洼;
(5)死亡狀態(tài)
? ??????· run方法正常退出而自然死亡
? ? ? ? · 一個(gè)未捕獲的異常終止了run方法而使線程猝死。
????????isAlive方法撼嗓。如果是可運(yùn)行或被阻塞柬采,這個(gè)方法返回true; 如果線程仍舊是new狀態(tài)且不是可運(yùn)行的且警, 或者線程死亡了粉捻,則返回false.
3.2 線程無處不在
(1)每個(gè)Java應(yīng)用程序都使用線程
? ? ? ? · JVM(GC、終結(jié)操作)創(chuàng)建后臺(tái)線程斑芜。
? ? ? ? · 創(chuàng)建主線程執(zhí)行main方法肩刃。
(2)需要注意線程安全的框架
? ? ? ? · Timer類,需要確保TimerTask訪問的對(duì)象本身是線程安全的杏头。
? ? ? ? · Servlet和JSP燥爷,需要保證ServletContext帝美、HttpSession等容器中保存的對(duì)象線程安全。
? ? ? ? · RMI 遠(yuǎn)程方法調(diào)用,確保被調(diào)用的對(duì)象線程安全
? ? ? ? · Swing和AWT
3.3 多線程同步
3.3.1 加鎖機(jī)制
3.3.1.1 內(nèi)置鎖
? ? synchronized同步代碼塊? ??
? ? · 每個(gè)Java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖冒萄,與對(duì)象頭的Mark Word有關(guān)荸哟。
? ? · 由于每次只能有一個(gè)線程執(zhí)行內(nèi)置鎖保護(hù)的代碼塊嘱能,因此同步代碼塊會(huì)以原子的方式執(zhí)行擎浴。
動(dòng)態(tài)高并發(fā)時(shí)為什么推薦重入鎖而不是Synchronized? - 簡(jiǎn)書
參考:深入理解synchronized底層原理根暑,一篇文章就夠了力试! - 知乎
共有三種使用方式:
(1)同步代碼塊
? ? ? ? 使用一個(gè)Java對(duì)象作為鎖。
private Object mutex = new Object();// 自定義多線程同步鎖
????public void sale() {
????????synchronized (mutex) {
????????????if (trainCount > 0) {
????????????????try {
????????????????????Thread.sleep(10);
????????????????} catch (Exception e) {
????????????}
????????????System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
????????????trainCount--;
????????}
????}
}
(2)同步方法
? ??使用this對(duì)象作為鎖排嫌。
public synchronized void sale() {
????if (trainCount > 0) {
????????try {
????????????Thread.sleep(40);
????????} catch (Exception e) {
????????}
????????System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
????????trainCount--;
????}
}
(3)靜態(tài)同步方法
? ? 當(dāng)方法被static關(guān)鍵字修飾畸裳,鎖使用class對(duì)象,即當(dāng)前類的字節(jié)碼文件
synchronized (ThreadTrain.class) {
????System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
????trainCount--;
????try {
????????Thread.sleep(100);
? ? } catch (Exception e) {
????}
}
3.3.1.2 可重入鎖
(1)概念:某個(gè)線程試圖獲得一個(gè)已經(jīng)由它自己持有的鎖淳地。重入鎖的鎖粒度是“線程”怖糊。
(2)重入鎖為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)值和一個(gè)所有者線程。同一個(gè)線程每次獲取鎖颇象,計(jì)數(shù)值加1伍伤,退出同步代碼塊時(shí),計(jì)數(shù)值減1.當(dāng)計(jì)數(shù)值為0時(shí)遣钳,這個(gè)鎖被釋放扰魂。
(3)synchronized是可重入鎖。
3.3.2 顯示鎖
3.3.2.1 Lock與ReentrantLock
(1)Lock接口
提供了一種無條件的、可輪詢的劝评、定時(shí)的以及可以中斷的鎖獲取操作姐直。
public interface Lock{
? ? void lock();
? ? void lockInterruptibly() throws InterruptedException;
? ? boolean tryLock();
? ? boolean tryLock(Long timeout, TimeUnit unit) throws InterruptedException;
? ? void unlock();
? ? Condition newCondition();
}
(2)ReetrantLock 實(shí)現(xiàn)了Lock接口
? ? · 提供了可重入鎖
? ? · 配合try/finally使用,必須在finally塊中蒋畜,使用unlock()方法釋放鎖声畏。
(3)Lock接口的其他特性
? ? · 輪詢鎖與定時(shí)鎖
? ? ? ? 由tryLock()方法實(shí)現(xiàn),可以避免死鎖的問題姻成。它會(huì)釋放已經(jīng)獲得的鎖插龄,然后重新嘗試獲取所有鎖
? ? · 可中斷鎖
? ? ? ? 實(shí)現(xiàn)可取消的任務(wù)
? ? · 非塊結(jié)構(gòu)的加鎖
? ? ? ? 鎖粒度會(huì)比synchronized細(xì),可以是代碼中的某幾行佣渴。
3.3.2.2 公平性
(1)分類
? ? · 公平鎖
? ? ? ? 按照發(fā)出請(qǐng)求順序獲得鎖辫狼。如果沒有獲取到鎖初斑,則直接加入隊(duì)列尾部辛润。
? ? · 非公平鎖(默認(rèn))
? ? ? ? 如果線程在發(fā)出請(qǐng)求的同時(shí)該鎖的狀態(tài)變?yōu)榭捎茫敲催@個(gè)線程將跳過隊(duì)列中所有的等待線程并獲得這個(gè)鎖见秤。嘗試獲取鎖會(huì)進(jìn)行兩次砂竖,兩次都失敗則直接加入CLH隊(duì)列尾部,后面流程和公平鎖一致鹃答。
(2)特性
????????在激烈競(jìng)爭(zhēng)時(shí)乎澄,非公平鎖性能高于公平鎖的性能。
(3)對(duì)比
? ? ? ? · synchronized 只能是非公平鎖
? ? ? ? · ReetrantLock 兩者都可
3.3.3 Volatile
(1)作用
? ? ? ? 是一種稍弱的同步機(jī)制测摔,用來確保將變量的更新操作通知到其他線程置济。
(2)禁止“重排序”
? ? ? ? · 重排序:編譯器和處理器以及運(yùn)行時(shí)等可能對(duì)操作的執(zhí)行順序進(jìn)行一些意想不到的調(diào)整。
? ? ? ? · volatile聲明的變量锋八,不會(huì)將該變量上的操作與其他內(nèi)存操作一起重排序浙于。
(3)典型應(yīng)用
? ? ? ? 用作狀態(tài)標(biāo)記或者條件判斷。
(4)使用條件
? ? ? ? · 只有單個(gè)線程更新變量值或者不依賴當(dāng)前值挟纱。
? ? ? ? · 訪問時(shí)不需要加鎖
(5)對(duì)比synchronized
? ? ? ? · volatile不會(huì)造成阻塞
? ? ? ? · synchronized可以保證原子性和可見性羞酗,Volatile只能保證可見性。
? ? ? ? · volatile用來保證可見紊服,synchronized用來同步
3.3.4 原子類
(1)java.util.concurrent.atomic包中的原子類
? ??AtomicInteger檀轨、AtomicLong等。
private final AtomicLong count = new AtomicLong(0);
(2)注意事項(xiàng)
? ? ? ? 當(dāng)有多個(gè)原子變量時(shí)欺嗤,若涉及到相互依賴参萄,需要保證在單個(gè)原子操作中更新所有相關(guān)的狀態(tài)變量。
(3)原理分析
// setup to use Unsafe.compareAndSwapInt for updates(更新操作時(shí)提
供“?較并替換”的作?)
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
????try {
????????valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
????} catch (Exception ex) { throw new Error(ex); }
????}
????private volatile int value;
? ? AtomicInteger類主要利用CAS(compare and swap)+ volatile和native方法(objectFieldOffset)來保證原子操作煎饼。
3.3.5 ThreadLocal
3.3.5.1 線程封閉
如果僅在單線程內(nèi)訪問數(shù)據(jù)讹挎,就不需同步共享的可變數(shù)據(jù),稱之為線程封閉。
3.3.5.2 ThreadLocal
(1)作用:根除對(duì)可變的單實(shí)例變量或全局變量共享淤袜。
(2)方式:
? ? ? ? ThreadLocal提供了get與set等訪問接口或方法痒谴,這些方法為每個(gè)使用該變量的線程都存有一份獨(dú)立的副本,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時(shí)設(shè)置的最新值铡羡。
(3)相關(guān)方法:
????· void set(Object value)設(shè)置當(dāng)前線程的線程局部變量的值积蔚。
????· public Object get()該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量。
????· public void remove()將當(dāng)前線程局部變量的值刪除烦周,目的是為了減少內(nèi)存的占用尽爆,該方法是JDK 5.0新增的方法。需要指出的是读慎,當(dāng)線程結(jié)束后漱贱,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作夭委,但它可以加快內(nèi)存回收的速度幅狮。
????· protected Object initialValue()返回該線程局部變量的初始值,該方法是一個(gè)protected的方法株灸,顯然是為了讓子類覆蓋而設(shè)計(jì)的崇摄。這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行慌烧,并且僅執(zhí)行1次逐抑。ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null。
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
????protected Integer initialValue() {
????????return 0;
????};
};
(4)底層原理
? ? ? ? 利用ThreadLocal.ThreadLocalMap<Thread, T>保存變量
3.3.6 活躍性與性能
(1)不良并發(fā)
? ? ? ? 可同時(shí)調(diào)用的用戶請(qǐng)求數(shù)量屹蚊,不僅受到可用處理資源的限制厕氨,還受到應(yīng)用程序本身結(jié)構(gòu)的限制。
(2)解決方案
? ? ? ? · 縮小同步代碼塊的作用范圍汹粤。 盡量將不影響共享狀態(tài)且執(zhí)行時(shí)間較長(zhǎng)的操作從同步代碼塊中分離出去命斧。
? ? ? ? · 盡量避免不同的同步機(jī)制一起使用
? ? ? ? · 當(dāng)執(zhí)行時(shí)間較長(zhǎng)的計(jì)算或者可能無法快速完成的操作時(shí)(例如,網(wǎng)絡(luò)I/O或控制臺(tái)IO)玄括,一定不要持有鎖冯丙。
3.4 線程三大特性
3.4.1 原子性
? ??即一個(gè)操作或者多個(gè)操作要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷,要么就都不執(zhí)行遭京。
3.4.2 可見性
? ??當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí)胃惜,一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值哪雕。
3.4.3 有序性
????程序執(zhí)行的順序按照代碼的先后順序執(zhí)行船殉。 程序在運(yùn)行時(shí),為了優(yōu)化可能對(duì)執(zhí)行順序重排序斯嚎。
3.5 Java內(nèi)存模型(JMM)
(1)概念
? ? · JMM決定一個(gè)線程對(duì)共享變量的寫入時(shí),能對(duì)另一個(gè)線程可見利虫。
· JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中挨厚,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本糠惫。
· 本地內(nèi)存是JMM的一個(gè)抽象概念疫剃,并不真實(shí)存在。它涵蓋了緩存硼讽,寫緩沖區(qū)巢价,寄存器以及其他的硬件和編譯器優(yōu)化。
(2)模型圖
? ??????當(dāng)線程A和線程B需要通信時(shí)固阁,線程A首先會(huì)把自己本地內(nèi)存中修改后的值刷新到主內(nèi)存中壤躲,隨后,線程B到主內(nèi)存中去讀取線程A更新后的值备燃。
? ? ? ? 如果線程本地內(nèi)存沒有及時(shí)刷新到主內(nèi)存中碉克,則可能發(fā)生線程安全問題。
(3)意義
為Java程序員提供內(nèi)存可見性保證并齐。
3.6 多線程通訊
3.6.1 Object類中的相關(guān)方法
(1)相關(guān)方法
wait()漏麦、notify()、notifyAll()是三個(gè)定義在Object類里的方法冀膝,可以用來控制線程的狀態(tài)唁奢。這三個(gè)方法最終調(diào)用的都是JVM級(jí)的native方法。隨著jvm運(yùn)行平臺(tái)的不同可能有些許差異窝剖。
? ? 這些方法可以配合synchronized關(guān)鍵字一起使用。
· 如果對(duì)象調(diào)用了wait方法就會(huì)使持有該對(duì)象的線程把該對(duì)象的控制權(quán)交出去酥夭,然后處于等待狀態(tài)赐纱。
· 如果對(duì)象調(diào)用了notify方法就會(huì)通知某個(gè)正在等待這個(gè)對(duì)象的控制權(quán)的線程可以繼續(xù)運(yùn)行。
· 如果對(duì)象調(diào)用了notifyAll方法就會(huì)通知所有等待這個(gè)對(duì)象控制權(quán)的線程繼續(xù)運(yùn)行熬北。
(2)注意事項(xiàng)
? ? ·notify和notifyAll的區(qū)別
·如果線程調(diào)用了對(duì)象的 wait()方法疙描,那么線程便會(huì)處于該對(duì)象的等待池中,等待池中的線程不會(huì)去競(jìng)爭(zhēng)該對(duì)象的鎖讶隐。
·當(dāng)有線程調(diào)用了對(duì)象的notifyAll()方法(喚醒所有 wait 線程)或notify()方法(只隨機(jī)喚醒一個(gè) wait 線程)起胰,被喚醒的的線程便會(huì)進(jìn)入該對(duì)象的鎖池中,鎖池中的線程會(huì)去競(jìng)爭(zhēng)該對(duì)象鎖巫延。也就是說效五,調(diào)用了notify后只要一個(gè)線程會(huì)由等待池進(jìn)入鎖池,而notifyAll會(huì)將該對(duì)象等待池內(nèi)的所有線程移動(dòng)到鎖池中炉峰,等待鎖競(jìng)爭(zhēng)
·優(yōu)先級(jí)高的線程競(jìng)爭(zhēng)到對(duì)象鎖的概率大畏妖,假若某線程沒有競(jìng)爭(zhēng)到該對(duì)象鎖,它還會(huì)留在鎖池中疼阔,唯有線程再次調(diào)用 wait()方法戒劫,它才會(huì)重新回到等待池中半夷。而競(jìng)爭(zhēng)到對(duì)象鎖的線程則繼續(xù)往下執(zhí)行,直到執(zhí)行完了 synchronized 代碼塊迅细,它會(huì)釋放掉該對(duì)象鎖巫橄,這時(shí)鎖池中的線程會(huì)繼續(xù)競(jìng)爭(zhēng)該對(duì)象鎖。
? ? · Thead.sleep()方法與wait()方法區(qū)別
? ? ? ? sleep()不會(huì)釋放鎖茵典,wait()方法會(huì)釋放鎖嗦随。
3.6.2 Condition對(duì)象
3.6.2.1 條件隊(duì)列
? ? ? ? 它使得一組線程(等待線程集合)能夠通過某種方式等待特定的條件變?yōu)檎妗?/p>
2.6.2.2 Conditon是Lock的廣義條件隊(duì)列
(1)常用方法
void await() throws InterruptedException
void signal()
void signalAll()
(2)對(duì)比synchronized
? ? · synchronized只能有一個(gè)關(guān)聯(lián)的條件隊(duì)列。
? ? · Lock可以由任意數(shù)量的Condition對(duì)象
3.6.3 線程中的異常
? ? 異常不能跨線程捕捉敬尺,必須在線程內(nèi)部處理枚尼。
3.6.4 守護(hù)線程
(1)當(dāng)進(jìn)程不存在或主線程停止,守護(hù)線程也會(huì)被停止砂吞。
(2)意義及應(yīng)用場(chǎng)景
????????當(dāng)主線程結(jié)束時(shí)署恍,結(jié)束其余的子線程(守護(hù)線程)自動(dòng)關(guān)閉,就免去了還要繼續(xù)關(guān)閉子線程的麻煩蜻直。
????????如:Java垃圾回收線程就是一個(gè)典型的守護(hù)線程盯质;內(nèi)存資源或者線程的管理,但是非守護(hù)線程也可以
通過thread.setDaemon(true)設(shè)置線程為守護(hù)線程(后臺(tái)線程)概而。
public class DaemonThread {
????public static void main(String[] args) {
????????Thread thread = new Thread(new Runnable() {
????????@Override
????????public void run() {
????????????while (true) {
???????????????try {
????????????????????Thread.sleep(100);
????????????????} catch (Exception e) {
????????????????}
????????????????System.out.println("我是子線程...");
????????????}
????????}
????});
????thread.setDaemon(true);
????thread.start();
????for (int i = 0; i < 10; i++) {
????????try {
????????????Thread.sleep(100);
????????} catch (Exception e) {
????????}
????????System.out.println("我是主線程");
????}
????System.out.println("主線程執(zhí)行完畢!");
????}
}
3.6.5 join()方法
? ??????join作用是讓其他線程變?yōu)榈却?/p>
class JoinThread implements Runnable {
????public void run() {
????????for (int i = 0; i < 100; i++) {
????????????System.out.println(Thread.currentThread().getName() + "---i:" + i);
????????}
????}
}
public class JoinThreadDemo {
????public static void main(String[] args) {
????????JoinThread joinThread = new JoinThread();
????????Thread t1 = new Thread(joinThread);
????????Thread t2 = new Thread(joinThread);
????????t1.start();
????????t2.start();
????????try {
? ? ????? //其他線程變?yōu)榈却隣顟B(tài)呼巷,等t1線程執(zhí)行完成之后才能執(zhí)行。
????????????t1.join();
????????} catch (Exception e) {
????????}
????????for (int i = 0; i < 100; i++) {
????????????System.out.println("main ---i:" + i);
????????}
????}
}
3.6.6 優(yōu)先級(jí)
????現(xiàn)代操作系統(tǒng)基本采用時(shí)分的形式調(diào)度運(yùn)行的線程赎瑰,線程分配得到的時(shí)間片的多少?zèng)Q定了線程使用處理器資源的多少王悍,也對(duì)應(yīng)了線程優(yōu)先級(jí)這個(gè)概念。
在JAVA線程中餐曼,通過一個(gè)int priority變量來控制優(yōu)先級(jí)压储,范圍為1-10,其中10最高源譬,默認(rèn)值為5集惋。下面是源碼(基于1.8)中關(guān)于priority的一些量和方法,如getPriority()踩娘,setPriority(int)刮刑。
public class ThreadDemo4 {
????public static void main(String[] args) {
????????PrioritytThread prioritytThread = new PrioritytThread();
????????Thread t1 = new Thread(prioritytThread);
????????Thread t2 = new Thread(prioritytThread);
????????t1.start();
????????????// 注意設(shè)置了優(yōu)先級(jí), 不代表每次都一定會(huì)被執(zhí)行养渴。 只是CPU調(diào)度會(huì)有限分配
??t1.setPriority(10);
????????t2.start();
????}
}
3.6.7 yield()方法
(1)Thread.yield()方法的作用:暫停當(dāng)前正在執(zhí)行的線程雷绢,并執(zhí)行其他線程。(可能沒有效果)
(2)yield()讓當(dāng)前正在運(yùn)行的線程回到可運(yùn)行狀態(tài)厚脉,以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行的機(jī)會(huì)习寸。因此,使用yield()的目的是讓具有相同優(yōu)先級(jí)的線程之間能夠適當(dāng)?shù)妮啌Q執(zhí)行傻工。
(3)實(shí)際中無法保證yield()達(dá)到讓步的目的霞溪,因?yàn)榉踔停尣降木€程可能被線程調(diào)度程序再次選中。大多數(shù)情況下鸯匹,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài)坊饶,但有可能沒有效果。
3.7 死鎖
(1)概念
? ? ? ? 多個(gè)線程同時(shí)被阻塞殴蓬,他們中的一個(gè)或者全部都在等待某個(gè)資源被釋放匿级。由于線程被無限期地阻塞,因此程序不可能正常終止染厅。
????????同步中嵌套同步,導(dǎo)致鎖無法釋放痘绎。
(2)死鎖條件
? ? ? ? · 互斥:任意時(shí)刻資源只能由一個(gè)線程占用
? ? ? ? · 請(qǐng)求與保持:對(duì)已獲得的資源保持不放
? ? ? ? · 不剝奪:獲得資源后不能被其他線程搶占
? ? ? ? · 循環(huán)等待: 若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
????破壞其中的任意一個(gè)條件即可:
? ? ? ? · 一次申請(qǐng)完所有資源
? ? ? ? · 主動(dòng)釋放資源
? ? ? ? · 按序申請(qǐng)資源
3.8 線程池
3.8.1 概念
????線程池是指在初始化一個(gè)多線程應(yīng)用程序過程中創(chuàng)建一個(gè)線程集合肖粮,然后在需要執(zhí)行新的任務(wù)時(shí)重用這些線程而不是新建一個(gè)線程孤页。
3.8.2 優(yōu)點(diǎn)
? ? (1)降低資源消耗
? ? (2)提高響應(yīng)速度
? ? (3)提高線程的可管理性
3.8.3 Executors工具類
? ? (1)通過Executors.callable(Runable task)實(shí)現(xiàn)Runable對(duì)象和Callable對(duì)象的相互轉(zhuǎn)換。
? ? (2)ExecutorService的兩個(gè)方法
? ? ? ? ? ? ? ? 通過Executors工具類創(chuàng)建ExecutorService對(duì)象涩馆。
? ? ? ? ? ? ? ? · execute()方法用于提交不需要返回值的任務(wù)行施,無法判斷任務(wù)是否被線程池執(zhí)行成功
? ? ? ? ? ? ? ? · submit()方法用于提交需要返回值的任務(wù),線程池返回Future對(duì)象魂那。
3.8.4 創(chuàng)建線程池的方式
3.8.4.1 傳統(tǒng)方式:利用Executors類
?下列四種方式的方法內(nèi)部蛾号,實(shí)際上都是調(diào)用了ThreadPoolExecutor的構(gòu)造方法。
(1)newCachedThreadPool
????????創(chuàng)建一個(gè)可緩存線程池涯雅,如果線程池長(zhǎng)度超過處理需要鲜结,可靈活回收空閑線程,若無可回收斩芭,則新建線程轻腺。
? ??????線程池為無限大,當(dāng)執(zhí)行第二個(gè)任務(wù)時(shí)第一個(gè)任務(wù)已經(jīng)完成划乖,會(huì)復(fù)用執(zhí)行第一個(gè)任務(wù)的線程,而不用每次新建線程挤土。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
????for (int i = 0; i < 10; i++) {
????????final int index = i;
cachedThreadPool.execute(new Runnable() {
????????????public void run() {
????????????System.out.println(Thread.currentThread().getName() + "---" + index);
????????????}
????????});
????}
(2)newFixedThreadPool
????????創(chuàng)建一個(gè)定長(zhǎng)線程池琴庵,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待仰美。
因?yàn)榫€程池大小為3迷殿,每個(gè)任務(wù)輸出index后sleep 1秒,所以每?jī)擅氪蛴?個(gè)數(shù)字咖杂。
// 創(chuàng)建一個(gè)定長(zhǎng)線程池庆寺,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待
final ExecutorService newCachedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
????final int index = i;
????newCachedThreadPool.execute(new Runnable() {
????????public void run() {
????????????try {
????????????????Thread.sleep(1000);
????????????} catch (Exception e) {
????????????}
????????????System.out.println("i:" + index);
????????}
????});
}
(3)newScheduledThreadPool
????????創(chuàng)建一個(gè)定長(zhǎng)線程池诉字,支持定時(shí)及周期性任務(wù)執(zhí)行懦尝。
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
newScheduledThreadPool.schedule(new Runnable() {
????public void run() {
????????System.out.println("delay 3 seconds");
????}
}, 3, TimeUnit.SECONDS);
(4)newSingleThreadExecutor
????????創(chuàng)建一個(gè)單線程化的線程池知纷,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行陵霉。
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
????final int index = i;
????newSingleThreadExecutor.execute(new Runnable() {
????????@Override
????????public void run() {
????????????System.out.println("index:" + index);
????????????try {
????????????????Thread.sleep(200);
????????????} catch (Exception e) {
????????????}
????????}
????});
}
3.8.4.2 阿里推薦:TheadPoolExecutor
(1)Executors框架的弊端
圖3-5 Executors弊端
(2)TheadPoolExecutor構(gòu)造方法
圖3-6 ThreadPoolExecutor構(gòu)造方法
(3)構(gòu)造參數(shù)分析
/**
* ?給定的初始參數(shù)創(chuàng)建?個(gè)新的ThreadPoolExecutor琅轧。
*/
public ThreadPoolExecutor(
int corePoolSize, //核心線程數(shù),最小可以同時(shí)運(yùn)行的線程數(shù)量
int maximumPoolSize, // 隊(duì)列滿容時(shí)踊挠,可以同時(shí)運(yùn)行的最大線程數(shù)
long keepAliveTime, // 核心線程外的線程乍桂,等待銷毀的時(shí)間
TimeUnit unit, // keepAliveTime的單位
BlockingQueue<Runnable> workQueue, //超出核心線程數(shù)后,存放任務(wù)的隊(duì)列
ThreadFactory threadFactory, // executor創(chuàng)建新線程使用
RejectedExecutionHandler handler // 飽和策略
){
????//... ....
}
· 飽和策略:當(dāng)前同時(shí)運(yùn)行線程數(shù)量達(dá)到最大線程數(shù)量maximumPoolSize效床,并且隊(duì)列滿容時(shí)睹酌,對(duì)線程的淘汰策略。
·ThreadPoolExecutor.AbortPolicy (默認(rèn)飽和策略)
拋出RejectedExecutionException異常拒絕新任務(wù)
· ThreadPoolExecutor.CallerRunsPolicy
調(diào)用執(zhí)行自己的線程運(yùn)行任務(wù)剩檀。不丟棄任何一個(gè)任務(wù)請(qǐng)求憋沿。
· ThreadPoolExecutor.DiscardPolicy
不處理新任務(wù),直接丟棄
· ThreadPoolExecutor.DiscardOldestPolicy
丟棄最早的未處理的任務(wù)請(qǐng)求
public class ThreadPoolExecutorDemo {
????private static final int CORE_POOL_SIZE = 5;
????private static final int MAX_POOL_SIZE = 10;
????private static final int QUEUE_CAPACITY = 100;
????private static final Long KEEP_ALIVE_TIME = 1L;
????public static void main(String[] args) {
????????//使?阿?巴巴推薦的創(chuàng)建線程池的?式
????????//通過ThreadPoolExecutor構(gòu)造函數(shù)?定義參數(shù)創(chuàng)建
????ThreadPoolExecutor executor = new ThreadPoolExecutor(
????????????CORE_POOL_SIZE,
????????????MAX_POOL_SIZE,
????????????KEEP_ALIVE_TIME,
????????????TimeUnit.SECONDS,
????????????new ArrayBlockingQueue<>(QUEUE_CAPACITY),
????????????new ThreadPoolExecutor.CallerRunsPolicy());
????????????for (int i = 0; i < 10; i++) {
????????????????//創(chuàng)建WorkerThread對(duì)象(WorkerThread類實(shí)現(xiàn)了Runnable 接?)
????????????????Runnable worker = new MyRunnable("" + i);
????????????????//執(zhí)?Runnable
?executor.execute(worker);
????????????}
????//終?線程池
????????????executor.shutdown();
????????????while (!executor.isTerminated()) {
????????????}
????????????System.out.println("Finished all threads");
????}
}
(4)原理分析
3.9 悲觀鎖與樂觀鎖
3.9.1 悲觀鎖
(1)概念
總是假設(shè)最壞的情況谨朝,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改卤妒,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖(共享資源每次只給一個(gè)線程使用字币,其它線程阻塞则披,用完后再把資源轉(zhuǎn)讓給其它線程)
(2)常見悲觀鎖
? ? ? ? synchronized和ReentrantLock等獨(dú)占鎖是悲觀鎖思想的典型實(shí)現(xiàn)。
3.9.2 樂觀鎖
3.9.2.1 概念
????總是假設(shè)最好的情況洗出,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改士复,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù)翩活,可以使用版本號(hào)機(jī)制和CAS算法實(shí)現(xiàn)阱洪。
3.9.2.2 常見應(yīng)用場(chǎng)景
(1)樂觀鎖適用于多讀的應(yīng)用類型,這樣可提高吞吐量菠镇。
(2)java.util.concurrent.atomic中的原子變量類是樂觀鎖的CAS實(shí)現(xiàn)的冗荸。
3.9.2.3 版本號(hào)機(jī)制
????一般是在數(shù)據(jù)表中加上一個(gè)數(shù)據(jù)版本號(hào)version字段,表示數(shù)據(jù)被修改的次數(shù)利耍,當(dāng)數(shù)據(jù)被修改時(shí)蚌本,version值會(huì)加一。
????當(dāng)線程A要更新數(shù)據(jù)值時(shí)隘梨,在讀取數(shù)據(jù)的同時(shí)也會(huì)讀取version值程癌,在提交更新時(shí),若剛才讀取到的version值為當(dāng)前數(shù)據(jù)庫中的version值相等時(shí)才更新轴猎,否則重試更新操作嵌莉,直到更新成功。
3.9.2.4 CAS算法
(1)概念
即compare and swap(比較與交換)捻脖,是一種有名的無鎖算法锐峭。無鎖編程中鼠,即不使用鎖的情況下實(shí)現(xiàn)多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實(shí)現(xiàn)變量的同步只祠,所以也叫非阻塞同步(Non-blocking Synchronization)
(2)原理
? ? · 三個(gè)操作數(shù):需要讀寫的內(nèi)存值 V兜蠕,進(jìn)行比較的值 A,擬寫入的新值 B抛寝。
· 當(dāng)且僅當(dāng) V 的值等于 A時(shí)熊杨,CAS通過原子方式用新值B來更新V的值,否則不會(huì)執(zhí)行任何操作(比較和替換是一個(gè)原子操作)盗舰。一般情況下是一個(gè)自旋操作蹋笼,即不斷的重試晤锹。(結(jié)合JMM內(nèi)存模型理解)
(3)CAS算法缺點(diǎn)
? ? · ABA問題
如果一個(gè)變量V初次讀取的時(shí)候是A值宋渔,并且在準(zhǔn)備賦值的時(shí)候檢查到它仍然是A值毁靶,那我們就能說明它的值沒有被其他線程修改過了嗎?很明顯是不能的蛮位,因?yàn)樵谶@段時(shí)間它的值可能被改為其他值较沪,然后又改回A,那CAS操作就會(huì)誤認(rèn)為它從來沒有被修改過失仁。這個(gè)問題被稱為CAS操作的"ABA"問題尸曼。
解決方法:在變量前面添加版本號(hào),每次變量更新的時(shí)候都將版本號(hào)加1萄焦,比如juc的原子包中的AtomicStampedReference類控轿。
?· 循環(huán)時(shí)間長(zhǎng)開銷大
自旋CAS(也就是不成功就一直循環(huán)執(zhí)行直到成功)如果長(zhǎng)時(shí)間不成功,會(huì)給CPU帶來非常大的執(zhí)行開銷拂封。
?· 只能保證一個(gè)共享變量的原子操作
CAS 只對(duì)單個(gè)共享變量有效茬射,當(dāng)操作涉及跨多個(gè)共享變量時(shí) CAS 無效。
從 JDK 1.5開始冒签,提供了AtomicReference類來保證引用對(duì)象之間的原子性在抛,你可以把多個(gè)變量放在一個(gè)對(duì)象里來進(jìn)行 CAS 操作.所以我們可以使用鎖或者利用AtomicReference類把多個(gè)共享變量合并成一個(gè)共享變量來操作。
3.10 AQS
3.10.1 概述
? ??AQS 的全稱為(AbstractQueuedSynchronizer)萧恕,這個(gè)類在 java.util.concurrent.locks 包下面霜定。
AQS 是一個(gè)用來構(gòu)建鎖和同步器的框架,使用 AQS 能簡(jiǎn)單且高效地構(gòu)造出應(yīng)用廣泛的大量的同步器廊鸥,比如我們提到的 ReentrantLock,Semaphore辖所,其他的諸如 ReentrantReadWriteLock惰说,SynchronousQueue,F(xiàn)utureTask(jdk1.7) 等等皆是基于 AQS 的缘回。當(dāng)然吆视,我們自己也能利用 AQS 非常輕松容易地構(gòu)造出符合我們自己需求的同步器典挑。
不同的自定義同步器爭(zhēng)用共享資源的方式也不同。自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源 state 的獲取與釋放方式即可啦吧,至于具體線程等待隊(duì)列的維護(hù)(如獲取資源失敗入隊(duì)/喚醒出隊(duì)等)您觉,AQS 已經(jīng)在上層已經(jīng)幫我們實(shí)現(xiàn)好了。
3.10.2 AQS原理
(1)AQS 核心思想
如果被請(qǐng)求的共享資源空閑授滓,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程琳水,并且將共享資源設(shè)置為鎖定狀態(tài)。如果被請(qǐng)求的共享資源被占用般堆,那么就需要一套線程阻塞等待以及被喚醒時(shí)鎖分配的機(jī)制在孝,這個(gè)機(jī)制 AQS 是用 CLH 隊(duì)列鎖實(shí)現(xiàn)的,即將暫時(shí)獲取不到鎖的線程加入到隊(duì)列中淮摔。
? ??CLH(Craig,Landin,and Hagersten)隊(duì)列是一個(gè)虛擬的雙向隊(duì)列(虛擬的雙向隊(duì)列即不存在隊(duì)列實(shí)例私沮,僅存在結(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系)。AQS 是將每條請(qǐng)求共享資源的線程封裝成一個(gè) CLH 鎖隊(duì)列的一個(gè)結(jié)點(diǎn)(Node)來實(shí)現(xiàn)鎖的分配和橙。
(2)源碼分析
????AQS 使用一個(gè) int 成員變量來表示同步狀態(tài)仔燕,通過內(nèi)置的 FIFO 隊(duì)列來完成獲取資源線程的排隊(duì)工作。AQS 使用 CAS 對(duì)該同步狀態(tài)進(jìn)行原子操作實(shí)現(xiàn)對(duì)其值的修改魔招。
private volatile int state;//共享變量晰搀,使用volatile修飾保證線程可見性
狀態(tài)信息通過 protected 類型的getState,setState仆百,compareAndSetState方法進(jìn)行操作厕隧。
//返回同步狀態(tài)的當(dāng)前值
protected final int getState(){
return state;
}
// 設(shè)置同步狀態(tài)的值
protected final void setState(int newState){
state = newState;
}
//原子地(CAS操作)將同步狀態(tài)值設(shè)置為給定值update如果當(dāng)前同步狀態(tài)的值等于expect(期望值)
protected final boolean compareAndSetState(intexpect,intupdate){
return unsafe.compareAndSwapInt(this,stateOffset,expect,update);
}
(3)資源的共享方式
·Exclusive(獨(dú)占)
只有一個(gè)線程能執(zhí)行,如 ReentrantLock俄周。又可分為公平鎖和非公平鎖,ReentrantLock 同時(shí)支持兩種鎖吁讨。
公平鎖:按照線程在隊(duì)列中的排隊(duì)順序,先到者先拿到鎖峦朗。
非公平鎖:當(dāng)線程要獲取鎖時(shí)建丧,先通過兩次 CAS 操作去搶鎖,如果沒搶到波势,當(dāng)前線程再加入到隊(duì)列中等待喚醒翎朱。
·Share(共享)
多個(gè)線程可同時(shí)執(zhí)行,如 Semaphore/CountDownLatch尺铣。Semaphore拴曲、CountDownLatCh、 CyclicBarrier凛忿、ReadWriteLock 我們都會(huì)在后面講到澈灼。
ReentrantReadWriteLock 可以看成是組合式,因?yàn)?ReentrantReadWriteLock 也就是讀寫鎖允許多個(gè)線程同時(shí)對(duì)某一資源進(jìn)行讀。
(4)公平鎖和非公平鎖
概念介紹:見3.3.1節(jié)
源碼分析:深入鎖和并發(fā)集合的核心——AQS(1)_只會(huì)寫B(tài)ug的Java程序員的博客-CSDN博客
3.10.3 Semaphore(信號(hào)量)
? ??Semaphore 只是維持了一個(gè)可獲得許可證的數(shù)量叁熔。 Semaphore 經(jīng)常用于限制獲取某種資源的線程數(shù)量委乌。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
? ??它默認(rèn)構(gòu)造AQS的state為permits。當(dāng)執(zhí)行任務(wù)的線程數(shù)量超出permits,那么多余的線程將會(huì)被放入阻塞隊(duì)列Park,并自旋判斷state是否大于0荣回。只有當(dāng)state大于0的時(shí)候遭贸,阻塞的線程才能繼續(xù)執(zhí)行,此時(shí)先前執(zhí)行任務(wù)的線程繼續(xù)執(zhí)行release方法,release方法使得state的變量會(huì)加1心软,那么自旋的線程便會(huì)判斷成功壕吹。 如此,每次只有最多不超過permits數(shù)量的線程能自旋成功糯累,便限制了執(zhí)行任務(wù)線程的數(shù)量算利。
????執(zhí)行?acquire?方法阻塞,直到有一個(gè)許可證可以獲得然后拿走一個(gè)許可證泳姐;每個(gè)?release?方法增加一個(gè)許可證效拭,這可能會(huì)釋放一個(gè)阻塞的 acquire 方法
public class SemaphoreExample1 {?
// 請(qǐng)求的數(shù)量?
private static final int threadCount = 550;?
public static void main(String[] args) throws InterruptedException {?
// 創(chuàng)建一個(gè)具有固定線程數(shù)量的線程池對(duì)象(如果這里線程池的線程數(shù)量給太少的話你會(huì)發(fā)現(xiàn)執(zhí)行的很慢)?
ExecutorService threadPool = Executors.newFixedThreadPool(300); // 一次只能允許執(zhí)行的線程數(shù)量。?
final Semaphore semaphore = new Semaphore(20);?
for (int i = 0; i < threadCount; i++) {?
final int threadnum = i;?
threadPool.execute(() -> {
// Lambda 表達(dá)式的運(yùn)用?
try {?
semaphore.acquire();// 獲取一個(gè)許可胖秒,所以可運(yùn)行線程數(shù)量為20/1=20? ? ? ? ? ? ? ? ? ? ? ? ? ? ?test(threadnum);?
semaphore.release();// 釋放一個(gè)許可?
} catch (InterruptedException e) {
e.printStackTrace();?
}?
});?
}?
threadPool.shutdown();?
System.out.println("finish");?
}
3.10.4 CountDownLatch(倒計(jì)時(shí)器)
(1)概念
????CountDownLatch允許 count 個(gè)線程阻塞在一個(gè)地方缎患,直至所有線程的任務(wù)都執(zhí)行完畢。
CountDownLatch是共享鎖的一種實(shí)現(xiàn),它默認(rèn)構(gòu)造 AQS 的 state 值為 count阎肝。
當(dāng)線程使用countDown方法時(shí),其實(shí)使用了tryReleaseShared方法以CAS的操作來減少state,直至state為0就代表所有的線程都調(diào)用了countDown方法挤渔。
當(dāng)調(diào)用await方法的時(shí)候,如果state不為0风题,就代表仍然有線程沒有調(diào)用countDown方法判导,那么就把已經(jīng)調(diào)用過countDown的線程都放入阻塞隊(duì)列Park,并自旋CAS判斷state == 0,直至最后一個(gè)線程調(diào)用了countDown沛硅,使得state == 0眼刃,于是阻塞的線程便判斷成功,全部往下執(zhí)行摇肌。
(2)Demo
public class CountDownLatchExample1 {?
// 請(qǐng)求的數(shù)量?
private static final int threadCount = 550;?
public static void main(String[] args) throws InterruptedException {?
// 創(chuàng)建一個(gè)具有固定線程數(shù)量的線程池對(duì)象(如果這里線程池的線程數(shù)量給太少的話你會(huì)發(fā)現(xiàn)執(zhí)行的很慢)?
ExecutorService threadPool = Executors.newFixedThreadPool(300);?
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);?
for (int i = 0; i < threadCount; i++) {?
final int threadnum = i; threadPool.execute(() -> {
// Lambda 表達(dá)式的運(yùn)用?
try {?
test(threadnum);?
} catch (InterruptedException e) {
e.printStackTrace();?
} finally {?
countDownLatch.countDown();// 表示一個(gè)請(qǐng)求已經(jīng)被完成?
}?
});?
}?
countDownLatch.await();?
threadPool.shutdown();?
System.out.println("finish");?
}
}
3.10.5 CyclicBarrier(循環(huán)柵欄)
(1)概念
????CyclicBarrier 的字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier)擂红。它要做的事情是,讓一組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞围小,直到最后一個(gè)線程到達(dá)屏障時(shí)昵骤,屏障才會(huì)開門,所有被屏障攔截的線程才會(huì)繼續(xù)干活肯适。
????CycliBarrier是基于 ReentrantLock(ReentrantLock也屬于AQS同步器)和 Condition 的.
CyclicBarrier 默認(rèn)的構(gòu)造方法是?CyclicBarrier(int parties)变秦,其參數(shù)表示屏障攔截的線程數(shù)量,每個(gè)線程調(diào)用await方法告訴 CyclicBarrier 我已經(jīng)到達(dá)了屏障框舔,然后當(dāng)前線程被阻塞伴栓。其中,parties 就代表了有攔截的線程的數(shù)量,當(dāng)攔截的線程數(shù)量達(dá)到這個(gè)值的時(shí)候就打開柵欄钳垮,讓所有線程通過。
(2)Demo
public class CyclicBarrierExample2 {
// 請(qǐng)求的數(shù)量
private static final int threadCount = 550;
// 需要同步的線程數(shù)量
private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
Thread.sleep(1000); threadPool.execute(() -> {
try {
test(threadNum);
} catch (InterruptedException e) {?
e.printStackTrace();
} catch (BrokenBarrierException e) {?
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
System.out.println("threadnum:" + threadnum + "is ready");
try {
/**等待60秒额港,保證子線程完全執(zhí)行結(jié)束*/
?cyclicBarrier.await(60, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println("-----CyclicBarrierException------");
}
System.out.println("threadnum:" + threadnum + "is finish");
}
}
(3)對(duì)比CountDownLatch
? ? ·?CountDownLatch 是計(jì)數(shù)器饺窿,只能使用一次,而 CyclicBarrier 的計(jì)數(shù)器提供 reset 功能移斩,可以多次使用肚医。
? ? ·?CountDownLatch 是計(jì)數(shù)器,線程完成一個(gè)記錄一個(gè)向瓷,只不過計(jì)數(shù)不是遞增而是遞減肠套,而 CyclicBarrier 更像是一個(gè)閥門,需要所有線程都到達(dá)猖任,閥門才能打開你稚,然后繼續(xù)執(zhí)行。對(duì)于 CountDownLatch 來說朱躺,重點(diǎn)是“一個(gè)線程(多個(gè)線程)等待”刁赖,而其他的 N 個(gè)線程在完成“某件事情”之后,可以終止长搀,也可以等待宇弛。而對(duì)于 CyclicBarrier,重點(diǎn)是多個(gè)線程源请,在任意一個(gè)線程沒有完成枪芒,所有的線程都必須等待。
4 JVM
4.1 Javac編譯原理
4.1.1 概述
(1)作用:將Java的源代碼轉(zhuǎn)化為class字節(jié)碼的谁尸。(編譯器)
(2)將Java語言規(guī)范轉(zhuǎn)化為Java虛擬機(jī)規(guī)范舅踪。
4.1.2 解析步驟
(1)讀取源代碼
? ? ? ? 按字節(jié)讀取,一個(gè)字節(jié)一個(gè)字節(jié)讀進(jìn)來症汹。
(2)詞法分析
? ? ? ? 找出Java中定義的語法關(guān)鍵詞硫朦,如if、else等背镇,找出這些Token流咬展。
(3)語法分析
? ? ? ? 對(duì)Token流進(jìn)行語法分析,形成一個(gè)符合Java語言規(guī)范的抽象語法樹瞒斩。
(4)語義分析
? ? ? ? 形成一個(gè)注解過后的抽象語法樹破婆。
(5)字節(jié)碼生成器
? ? ? ? · 調(diào)用javac.jvm.Gen類遍歷語法樹,生成最終的Java字節(jié)碼
? ? ? ? · JVM的所有操作都是基于棧的
? ? ? ? · 將字節(jié)碼輸出到.class文件中胸囱。
4.2 class文件結(jié)構(gòu)
(1)class文件頭
? ? ? ? ?第一行:標(biāo)識(shí)符祷舀,表示這個(gè)文件為標(biāo)準(zhǔn)的class文件
? ? ? ? ?第二和第三行:Java最小版本和最大版本范圍。
(2)常量池
(3)類信息
? ? ? ? 如final類、接口裳扯、抽象類等
(4)Fields和Methods定義
(5)類屬性描述
? ? · 可以使用javap命令生成class的結(jié)構(gòu)信息到文件抛丽。 javap -verbose Message > result.txt (Message為Java類名,Message.java)
4.3 ClassLoader類加載器
4.3.1 作用
(1)將class文件加載到JVM中
(2)審查每個(gè)類應(yīng)該由誰加載饰豺,它是一種父優(yōu)先的等級(jí)加載機(jī)制
(3)將Class字節(jié)碼重新解析成JVM統(tǒng)一要求的對(duì)象格式
4.3.2 類結(jié)構(gòu)(方法分析)
? ? ClassLoader是抽象類亿鲜,其中的常用接口方法如下:
(1)defineClass(byte[], int, int)
? ? ? ? 將byte流解析成JVM能夠識(shí)別的Class對(duì)象。
(2)findClass(String)
直接覆蓋ClassLoader父類的findClass方法實(shí)現(xiàn)類的加載規(guī)則冤吨。
(3)resolveClass(Class<?>)
? ? ? ? 鏈接(Link)蒿柳,結(jié)合JVM運(yùn)行時(shí)環(huán)境,準(zhǔn)備執(zhí)行該類或接口漩蟆。
(4)loadClass(String)
在運(yùn)行時(shí)加載指定類垒探,獲取類的Class對(duì)象。
? ? ? ? Class<?> class = this.getClass().getClassLoader().loadClass(String)
(5)getResourceAsStream(xmlPath)?
? ??????獲取當(dāng)前classpath
(6)getResource(..)
? ??????獲取當(dāng)前classpath
4.3.3 等級(jí)加載機(jī)制
4.3.3.1 JVM平臺(tái)提供三層ClassLoader
? ? ? ? · Bootstrap ClassLoader 啟動(dòng)類加載器怠李,加載JVM自身工作需要的類
? ? ? ? · Extension ClassLoader 擴(kuò)展類加載器圾叼,加載java.ext.dirs目錄下的類
? ? ? ? · Application ClassLoader 應(yīng)用程序類加載器,加載classpath下的類
4.3.3.2 雙親委派模型
(1)概念
????在類加載的時(shí)候扔仓,系統(tǒng)會(huì)首先判斷當(dāng)前類是否被加載過褐奥。已經(jīng)被加載的類會(huì)直接返回,否則才會(huì)嘗試加載翘簇。
加載的時(shí)候撬码,首先會(huì)把該請(qǐng)求委派該父類加載器的?loadClass()?處理,因此所有的請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器?BootstrapClassLoader?中版保。當(dāng)父類加載器無法處理時(shí)呜笑,才由自己來處理。當(dāng)父類加載器為null時(shí)彻犁,會(huì)使用啟動(dòng)類加載器?BootstrapClassLoader?作為父類加載器叫胁。
(2)每個(gè)類加載都有一個(gè)父類加載器
AppClassLoader的父類加載器為ExtClassLoader?ExtClassLoader的父類加載器為null,null并不代表ExtClassLoader沒有父類加載器汞幢,而是?BootstrapClassLoader驼鹅。
????????類加載器之間的“父子”關(guān)系也不是通過繼承來體現(xiàn)的,是由“優(yōu)先級(jí)”來決定
(3)源碼分析
? ??????雙親委派模型的實(shí)現(xiàn)代碼非常簡(jiǎn)單森篷,邏輯非常清晰输钩,都集中在ClassLoader的loadClass()方法中。
private final ClassLoader parent;?
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先仲智,檢查請(qǐng)求的類是否已經(jīng)被加載過
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
?//父加載器不為空买乃,調(diào)用父加載器loadClass()方法處理
c = parent.loadClass(name, false);
} else {
?//父加載器為空,使用啟動(dòng)類加載器 BootstrapClassLoader 加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//拋出異常說明父類加載器無法完成加載請(qǐng)求
}
if (c == null) {
long t1 = System.nanoTime();
????//自己嘗試加載
c = findClass(name);
// this is the defining class loader; record the stats ????????????????sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); ????????????????sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); ????????????????sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}?
return c;
}
}
(4)優(yōu)點(diǎn)
? ? ? ? ·?雙親委派模型保證了Java程序的穩(wěn)定運(yùn)行钓辆,可以避免類的重復(fù)加載(JVM 區(qū)分不同類的方式不僅僅根據(jù)類名剪验,相同的類文件被不同的類加載器加載產(chǎn)生的是兩個(gè)不同的類)
? ? ? ? ·?保證了 Java 的核心 API 不被篡改
(5)如何不適用雙親委派模型
??自定義加載器的話肴焊,需要繼承?ClassLoader?。
????????如果我們不想打破雙親委派模型功戚,就重寫?ClassLoader?類中的?findClass()?方法即可娶眷,無法被父類加載器加載的類最終會(huì)通過這個(gè)方法被加載。
????????但是疫铜,如果想打破雙親委派模型則需要重寫?loadClass()?方法
4.3.4 加載class文件過程
4.3.4.1 加載
? ?· 通過全類名獲取定義此類的二進(jìn)制字節(jié)流
? ?· 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
? ?· 在內(nèi)存中生成一個(gè)代表該類的 Class 對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問入口
? ? .class文件 ---》 findClass() ---》 defineClass()
4.3.4.2 鏈接(Link)
(1)驗(yàn)證
(2)準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段茂浮,這些內(nèi)存都將在方法區(qū)中分配。
? ? ????·這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static)壳咕,而不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在 Java 堆中顽馋。
? ? ? ? · 這里所設(shè)置的初始值"通常情況"下是數(shù)據(jù)類型默認(rèn)的零值(如0谓厘、0L、null寸谜、false等)竟稳,比如我們定義了public static int value=111?,那么 value 變量在準(zhǔn)備階段的初始值就是 0 而不是111(初始化階段才會(huì)賦值)熊痴。特殊情況:比如給 value 變量加上了 fianl 關(guān)鍵字public static final int value=111?他爸,那么準(zhǔn)備階段 value 的值就被賦值為 111。
(3)解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程果善。解析動(dòng)作主要針對(duì)類或接口诊笤、字段、類方法巾陕、接口方法讨跟、方法類型、方法句柄和調(diào)用限定符7類符號(hào)引用進(jìn)行鄙煤。
符號(hào)引用就是一組符號(hào)來描述目標(biāo)晾匠,可以是任何字面量。直接引用就是直接指向目標(biāo)的指針梯刚、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄凉馆。
4.3.4.3 初始化
? ??????初始化是類加載的最后一步,也是真正執(zhí)行類中定義的 Java 程序代碼(字節(jié)碼)亡资,初始化階段是執(zhí)行初始化方法?<clinit> ()方法的過程澜共。
對(duì)于初始化階段,虛擬機(jī)嚴(yán)格規(guī)范了有且只有5種情況下沟于,必須對(duì)類進(jìn)行初始化(只有主動(dòng)去使用類才會(huì)初始化類):
(1)當(dāng)遇到 new 咳胃、 getstatic、putstatic或invokestatic 這4條直接碼指令時(shí)旷太,比如 new 一個(gè)類展懈,讀取一個(gè)靜態(tài)字段(未被 final 修飾)销睁、或調(diào)用一個(gè)類的靜態(tài)方法時(shí)。
(2)使用?java.lang.reflect?包的方法對(duì)類進(jìn)行反射調(diào)用時(shí)如Class.forname("..."),newInstance()等等存崖。 冻记,如果類沒初始化,需要觸發(fā)其初始化来惧。
(3)初始化一個(gè)類冗栗,如果其父類還未初始化,則先觸發(fā)該父類的初始化供搀。
(4)當(dāng)虛擬機(jī)啟動(dòng)時(shí)隅居,用戶需要定義一個(gè)要執(zhí)行的主類 (包含 main 方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)類葛虐。
(5)MethodHandle和VarHandle可以看作是輕量級(jí)的反射調(diào)用機(jī)制胎源,而要想使用這2個(gè)調(diào)用, 就必須先使用findStaticVarHandle來初始化要調(diào)用的類屿脐。
4.3.5 常見加載類錯(cuò)誤
(1)ClassNotFoundException
? ? ? ? ? ? 發(fā)生在顯示加載類時(shí)
(2)NoClassDefFoundError
? ? ? ? ? ? 發(fā)生在隱式加載類時(shí)
(3)UnsatisfiedLinkError
? ? ? ? ? ? 解析native方法時(shí)
(4)ClassCastException
(5)ExceptionInInitializerError
4.4 內(nèi)存
(1)分類
? ? ? ? · 物理內(nèi)存 RAM涕蚤,調(diào)用操作系統(tǒng)接口訪問。
? ? ? ? · 虛擬內(nèi)存的诵,物理內(nèi)存臨時(shí)存儲(chǔ)在磁盤文件
(2)劃分
? ? ? ? · 內(nèi)核空間万栅,操作系統(tǒng)程序邏輯
? ? ? ? · 用戶空間,每一次系統(tǒng)調(diào)用西疤,都存在兩個(gè)空間的內(nèi)存切換
4.5 Java內(nèi)存區(qū)域
? ? ? ? 還有很多叫法烦粒,如JVM內(nèi)存結(jié)構(gòu),運(yùn)行時(shí)數(shù)據(jù)區(qū)域瘪阁。(一定要與Java內(nèi)存模型JMM分開)撒遣。? ? ? ??
(1)程序計(jì)數(shù)器(PC寄存器)
? ? · 字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制管跺,如:順序執(zhí)行义黎、選擇、循環(huán)豁跑、異常處理廉涕。
? ? · 在多線程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置艇拍,從而當(dāng)線程被切換回來的時(shí)候能夠知道該線程上次運(yùn)行到哪兒了狐蜕。
(2)Java虛擬機(jī)棧
Java 虛擬機(jī)棧是由一個(gè)個(gè)棧幀組成,每一次函數(shù)調(diào)用都會(huì)有一個(gè)對(duì)應(yīng)的棧幀被壓入 Java 棧卸夕,每一個(gè)函數(shù)調(diào)用結(jié)束后层释,都會(huì)有一個(gè)棧幀被彈出。而每個(gè)棧幀中都擁有:局部變量表快集、操作數(shù)棧贡羔、動(dòng)態(tài)鏈接廉白、方法出口信息。
局部變量表主要存放了編譯期可知的各種數(shù)據(jù)類型(boolean乖寒、byte猴蹂、char、short楣嘁、int磅轻、float、long逐虚、double)聋溜、對(duì)象引用(reference 類型,它不同于對(duì)象本身叭爱,可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔樓诨椋部赡苁侵赶蛞粋€(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置)。
(3)本地方法棧
????本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)涤伐。
(4)堆
Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊,Java 堆是所有線程共享的一塊內(nèi)存區(qū)域缨称,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建凝果。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)組都在這里分配內(nèi)存睦尽。
Java 堆是垃圾收集器管理的主要區(qū)域器净,因此也被稱作GC 堆(Garbage Collected Heap).從垃圾回收的角度,由于現(xiàn)在收集器基本都采用分代垃圾收集算法当凡,所以 Java 堆還可以細(xì)分為:新生代和老年代:再細(xì)致一點(diǎn)有:Eden 空間山害、From Survivor、To Survivor 空間等沿量。進(jìn)一步劃分的目的是更好地回收內(nèi)存浪慌,或者更快地分配內(nèi)存。
(5)方法區(qū)
用于存儲(chǔ)已被虛擬機(jī)加載的類信息朴则、常量权纤、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)乌妒。雖然Java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分汹想,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與 Java 堆區(qū)分開來撤蚊。
方法區(qū)和永久代的關(guān)系很像 Java 中接口和類的關(guān)系古掏,類實(shí)現(xiàn)了接口,而永久代就是 HotSpot 虛擬機(jī)對(duì)虛擬機(jī)規(guī)范中方法區(qū)的一種實(shí)現(xiàn)方式侦啸。也就是說槽唾,永久代是 HotSpot 的概念丧枪,方法區(qū)是 Java 虛擬機(jī)規(guī)范中的定義,是一種規(guī)范夏漱,而永久代是一種實(shí)現(xiàn)豪诲,一個(gè)是標(biāo)準(zhǔn)一個(gè)是實(shí)現(xiàn)矮嫉,其他的虛擬機(jī)實(shí)現(xiàn)并沒有永久代這一說法
4.6 Java對(duì)象創(chuàng)建過程
4.6.1 類加載檢查
????虛擬機(jī)遇到一條 new 指令時(shí)粥惧,首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過啰劲、解析和初始化過葵蒂。如果沒有交播,那必須先執(zhí)行相應(yīng)的類加載過程。
4.6.2 分配內(nèi)存
? ??對(duì)象所需的內(nèi)存大小在類加載完成后便可確定践付,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來秦士。
? ? 共有兩種內(nèi)存分配方式,選擇兩種方式中的哪一種永高,取決于 Java 堆內(nèi)存是否規(guī)整隧土。而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標(biāo)記-清除"命爬,還是"標(biāo)記-整理"(也稱作"標(biāo)記-壓縮")曹傀,值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的饲宛。
(1)指針碰撞
? ? ? ? · 使用場(chǎng)合:堆內(nèi)存規(guī)整
? ? ? ? · 原理:用過的內(nèi)存全部整合到一邊皆愉,沒有用過的內(nèi)存放在另一邊,中間有一個(gè)分界值指針艇抠,只需要向著沒用過的內(nèi)存方向?qū)⒃撝羔樢苿?dòng)對(duì)象內(nèi)存大小位置即可幕庐。
? ? ? ? · GC收集器:Serial、ParNew
(2)空閑列表
? ? ? ? · 使用場(chǎng)合:堆內(nèi)存不規(guī)整
? ? ? ? · 原理:虛擬機(jī)會(huì)維護(hù)一個(gè)列表家淤,該列表中會(huì)記錄那些內(nèi)存塊是可用的异剥,在分配的時(shí)候,找一塊足夠大的內(nèi)存塊來劃分給對(duì)象實(shí)例媒鼓,最后更新列表記錄届吁。? ?
? ? ? ? · GC收集器:CMS
? ? 在創(chuàng)建對(duì)象過程中,需要注意線程安全問題绿鸣,采用下列兩種方法保證線程安全:
(1)CAS+失敗重試:?CAS 是樂觀鎖的一種實(shí)現(xiàn)方式疚沐。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作潮模,如果因?yàn)闆_突失敗就重試亮蛔,直到成功為止。虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性擎厢。
(2)TLAB:?為每一個(gè)線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存究流,JVM 在給線程中的對(duì)象分配內(nèi)存時(shí)辣吃,首先在 TLAB 分配,當(dāng)對(duì)象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時(shí)芬探,再采用上述的 CAS 進(jìn)行內(nèi)存分配
4.6.3 初始化零值
? ??虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭)神得,這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值偷仿。
4.6.4 設(shè)置對(duì)象頭
初始化零值完成之后哩簿,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例酝静、如何才能找到類的元數(shù)據(jù)信息节榜、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息别智。這些信息存放在對(duì)象頭中宗苍。另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同薄榛,如是否啟用偏向鎖等讳窟,對(duì)象頭會(huì)有不同的設(shè)置方式。
4.6.5 init方法
? ??執(zhí)行 new 指令之后會(huì)接著執(zhí)行?<init>?方法敞恋,把對(duì)象按照程序員的意愿進(jìn)行初始化挪钓,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來。
4.7 Java內(nèi)存分配和回收
????Java顯式的內(nèi)存申請(qǐng)有靜態(tài)內(nèi)存分配和動(dòng)態(tài)內(nèi)存分配兩種耳舅。
4.7.1 靜態(tài)內(nèi)存
(1)靜態(tài)內(nèi)存分配,指Java被編譯時(shí)就已經(jīng)能夠確定需要的內(nèi)存空間倚评,當(dāng)加載到內(nèi)存時(shí)浦徊,一次性分配。
(2)基本數(shù)據(jù)類型天梧、對(duì)象的引用盔性。在棧上分配
(3)回收:在代碼運(yùn)行結(jié)束時(shí)回收。
4.7.2 動(dòng)態(tài)內(nèi)存
(1)動(dòng)態(tài)內(nèi)存分配呢岗,指在程序執(zhí)行時(shí)才知道要分配的存儲(chǔ)空間大小冕香,而不是在編譯器就確定大小。
(2)new等指令在堆上創(chuàng)建
(3)回收:檢測(cè)垃圾回收
? ? ? ? · 引用計(jì)數(shù)法:引用計(jì)數(shù)為0時(shí)后豫,等待回收
? ? ? ? · 可達(dá)性分析:為不可達(dá)集合時(shí)悉尾,等待回收
4.8 垃圾收集算法
4.8.1 標(biāo)記-清除算法
????首先標(biāo)記出所有不需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有沒有被標(biāo)記的對(duì)象挫酿。
會(huì)產(chǎn)生大量不連續(xù)的碎片构眯。
4.8.2 復(fù)制算法
? ??它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊早龟。當(dāng)這一塊的內(nèi)存使用完后惫霸,就將還存活的對(duì)象復(fù)制到另一塊去猫缭,然后再把使用的空間一次清理掉。這樣就使每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收壹店。
4.8.3 標(biāo)記-整理算法
根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法猜丹,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象回收硅卢,而是讓所有存活的對(duì)象向一端移動(dòng)射窒,然后直接清理掉端邊界以外的內(nèi)存。
4.8.4 分代收集算法
當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法老赤,這種算法沒有什么新的思想轮洋,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊。一般將 java 堆分為新生代和老年代抬旺,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法弊予。
在新生代中,每次收集都會(huì)有大量對(duì)象死去开财,所以可以選擇復(fù)制算法汉柒,只需要付出少量對(duì)象的復(fù)制成本就可以完成每次垃圾收集。
老年代的對(duì)象存活幾率是比較高的责鳍,而且沒有額外的空間對(duì)它進(jìn)行分配擔(dān)保碾褂,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。
(1)堆內(nèi)存分配策略
? ? ? ? · 對(duì)象優(yōu)先在eden區(qū)分配
? ? ? ? · 大對(duì)象直接進(jìn)入老年代
? ? ? ? · 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
(2)GC步驟
? ??????大部分情況历葛,對(duì)象都會(huì)首先在 Eden 區(qū)域分配正塌,在一次新生代垃圾回收后,如果對(duì)象還存活恤溶,則會(huì)進(jìn)入 s0 或者 s1乓诽,并且對(duì)象的年齡還會(huì)加 1(Eden 區(qū)->Survivor 區(qū)后對(duì)象的初始年齡變?yōu)?1),當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲)咒程,就會(huì)被晉升到老年代中鸠天。對(duì)象晉升到老年代的年齡閾值,可以通過參數(shù)?-XX:MaxTenuringThreshold?來設(shè)置帐姻。
經(jīng)過這次GC后稠集,Eden區(qū)和"From"區(qū)已經(jīng)被清空。這個(gè)時(shí)候饥瓷,"From"和"To"會(huì)交換他們的角色剥纷,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"呢铆。不管怎樣筷畦,都會(huì)保證名為To的Survivor區(qū)域是空的。Minor GC會(huì)一直重復(fù)這樣的過程,直到“To”區(qū)被填滿鳖宾,"To"區(qū)被填滿之后吼砂,會(huì)將所有對(duì)象移動(dòng)到老年代中。
(3)GC分類
? ? · 部分收集 (Partial GC):
? ? ? ? · 新生代收集(Minor GC / Young GC):只對(duì)新生代進(jìn)行垃圾收集鼎文;
? ? ? ? · 老年代收集(Major GC / Old GC):只對(duì)老年代進(jìn)行垃圾收集渔肩。需要注意的是 Major GC 在有的語境中也用于指代整堆收集;
? ? ? ? · 混合收集(Mixed GC):對(duì)整個(gè)新生代和部分老年代進(jìn)行垃圾收集拇惋。
? ? · 整堆收集 (Full GC):收集整個(gè) Java 堆和方法區(qū)周偎。關(guān)于系統(tǒng)頻繁Full GC的排查:排查CPU100%(FUll GC導(dǎo)致)、GC執(zhí)行步驟和排查頻繁FULL GC
·常見問題1:頻繁FULL GC的原因:
? ? ·代碼中一次獲取了大量的對(duì)象撑帖,導(dǎo)致內(nèi)存溢出蓉坎,此時(shí)可以通過 Eclipse 的 Mat 工具查看內(nèi)存中有哪些對(duì)象比較多。
? ? ·內(nèi)存占用不高胡嘿,但是 Full GC 次數(shù)還是比較多蛉艾,此時(shí)可能是顯示的 System.gc() 調(diào)用導(dǎo)致 GC 次數(shù)過多,這可以通過添加 -XX:+DisableExplicitGC 來禁用 JVM 對(duì)顯示 GC 的響應(yīng)衷敌。
其他排查FGC問題的實(shí)踐指南:
1. 清楚從程序角度勿侯,有哪些原因?qū)е翭GC?
? ? · 大對(duì)象:系統(tǒng)一次性加載了過多數(shù)據(jù)到內(nèi)存中(比如SQL查詢未做分頁)缴罗,導(dǎo)致大對(duì)象進(jìn)入了老年代助琐。
? ? · 內(nèi)存泄漏:頻繁創(chuàng)建了大量對(duì)象,但是無法被回收(比如IO對(duì)象使用完后未調(diào)用close方法釋放資源)面氓,先引發(fā)FGC兵钮,最后導(dǎo)致OOM.
? ? · 程序頻繁生成一些長(zhǎng)生命周期的對(duì)象,當(dāng)這些對(duì)象的存活年齡超過分代年齡時(shí)便會(huì)進(jìn)入老年代舌界,最后引發(fā)FGC. (即本文中的案例)
? ? · 程序BUG導(dǎo)致動(dòng)態(tài)生成了很多新類矢空,使得 Metaspace 不斷被占用,先引發(fā)FGC禀横,最后導(dǎo)致OOM.
? ? · 代碼中顯式調(diào)用了gc方法,包括自己的代碼甚至框架中的代碼粥血。
? ? · JVM參數(shù)設(shè)置問題:包括總內(nèi)存大小柏锄、新生代和老年代的大小、Eden區(qū)和S區(qū)的大小复亏、元空間大小趾娃、垃圾回收算法等等。
2. 清楚排查問題時(shí)能使用哪些工具
? ? · 公司的監(jiān)控系統(tǒng):大部分公司都會(huì)有缔御,可全方位監(jiān)控JVM的各項(xiàng)指標(biāo)抬闷。
? ? · JDK的自帶工具,包括jmap、jstat等常用命令:# 查看堆內(nèi)存各區(qū)域的使用率以及GC情況jstat -gcutil -h20 pid 1000# 查看堆內(nèi)存中的存活對(duì)象笤成,并按空間排序jmap -histo pid | head -n20# dump堆內(nèi)存文件jmap -dump:format=b,file=heap pid
? ? · 可視化的堆內(nèi)存分析工具:JVisualVM评架、MAT等
3. 排查指南
? ? · 查看監(jiān)控,以了解出現(xiàn)問題的時(shí)間點(diǎn)以及當(dāng)前FGC的頻率(可對(duì)比正常情況看頻率是否正常)
? ? · 了解該時(shí)間點(diǎn)之前有沒有程序上線炕泳、基礎(chǔ)組件升級(jí)等情況纵诞。
? ? · 了解JVM的參數(shù)設(shè)置,包括:堆空間各個(gè)區(qū)域的大小設(shè)置培遵,新生代和老年代分別采用了哪些垃圾收集器浙芙,然后分析JVM參數(shù)設(shè)置是否合理。
? ? · 再對(duì)步驟1中列出的可能原因做排除法籽腕,其中元空間被打滿嗡呼、內(nèi)存泄漏、代碼顯式調(diào)用gc方法比較容易排查皇耗。
? ? · 針對(duì)大對(duì)象或者長(zhǎng)生命周期對(duì)象導(dǎo)致的FGC南窗,可通過 jmap -histo 命令并結(jié)合dump堆內(nèi)存文件作進(jìn)一步分析,需要先定位到可疑對(duì)象廊宪。
? ? · 通過可疑對(duì)象定位到具體代碼再次分析矾瘾,這時(shí)候要結(jié)合GC原理和JVM參數(shù)設(shè)置,弄清楚可疑對(duì)象是否滿足了進(jìn)入到老年代的條件才能下結(jié)論箭启。
·常見問題2:線上可能出現(xiàn)的導(dǎo)致系統(tǒng)緩慢的情況及處理:
簡(jiǎn)要的說壕翩,我們進(jìn)行線上日志分析時(shí),主要可以分為如下步驟:
①通過 top 命令查看 CPU 情況傅寡,如果 CPU 比較高放妈,則通過 top -Hp?命令查看當(dāng)前進(jìn)程的各個(gè)線程運(yùn)行情況。
????找出 CPU 過高的線程之后荐操,將其線程 id 轉(zhuǎn)換為十六進(jìn)制的表現(xiàn)形式芜抒,然后在 jstack 日志中查看該線程主要在進(jìn)行的工作。
這里又分為兩種情況:
? ? · 如果是正常的用戶線程托启,則通過該線程的堆棧信息查看其具體是在哪處用戶代碼處運(yùn)行比較消耗 CPU宅倒。
? ? · 如果該線程是 VM Thread,則通過 jstat -gcutil?命令監(jiān)控當(dāng)前系統(tǒng)的 GC 狀況屯耸。
? ? ? 然后通過 jmap dump:format=b,file=?導(dǎo)出系統(tǒng)當(dāng)前的內(nèi)存數(shù)據(jù)拐迁。
? ? ? 導(dǎo)出之后將內(nèi)存情況放到 Eclipse 的 Mat 工具中進(jìn)行分析即可得出內(nèi)存中主要是什么對(duì)象比較消耗內(nèi)存,進(jìn)而可以處理相關(guān)代碼疗绣。
②如果通過 top 命令看到 CPU 并不高线召,并且系統(tǒng)內(nèi)存占用率也比較低。此時(shí)就可以考慮是否是由于另外三種情況導(dǎo)致的問題多矮。
具體的可以根據(jù)具體情況分析:
? ? · 如果是接口調(diào)用比較耗時(shí)缓淹,并且是不定時(shí)出現(xiàn),則可以通過壓測(cè)的方式加大阻塞點(diǎn)出現(xiàn)的頻率,從而通過 jstack 查看堆棧信息讯壶,找到阻塞點(diǎn)料仗。
? ? · 如果是某個(gè)功能突然出現(xiàn)停滯的狀況,這種情況也無法復(fù)現(xiàn)鹏溯,此時(shí)可以通過多次導(dǎo)出 jstack 日志的方式對(duì)比哪些用戶線程是一直都處于等待狀態(tài)罢维,這些線程就是可能存在問題的線程。
? ? · 如果通過 jstack 可以查看到死鎖狀態(tài)丙挽,則可以檢查產(chǎn)生死鎖的兩個(gè)線程的具體阻塞點(diǎn)肺孵,從而處理相應(yīng)的問題。
4.9 常見垃圾收集器
4.9.1 Serial Collector
Serial(串行)收集器收集器是最基本颜阐、歷史最悠久的垃圾收集器了平窘。大家看名字就知道這個(gè)收集器是一個(gè)單線程收集器了。它的“單線程”的意義不僅僅意味著它只會(huì)使用一條垃圾收集線程去完成垃圾收集工作凳怨,更重要的是它在進(jìn)行垃圾收集工作的時(shí)候必須暫停其他所有的工作線程("Stop The World")瑰艘,直到它收集結(jié)束。
優(yōu)點(diǎn):它簡(jiǎn)單而高效(與其他收集器的單線程相比)
? ? 缺點(diǎn):用戶體驗(yàn)差肤舞,有停頓時(shí)間
? ? 新生代采用復(fù)制算法紫新,老年代采用標(biāo)記-整理算法
4.9.2 Parallel Collector
? ??ParNew 收集器其實(shí)就是 Serial 收集器的多線程版本,除了使用多線程進(jìn)行垃圾收集外李剖,其余行為(控制參數(shù)芒率、收集算法、回收策略等等)和 Serial 收集器完全一樣篙顺。
? ??它是許多運(yùn)行在 Server 模式下的虛擬機(jī)的首要選擇偶芍。
? ??新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法
? ? 并行德玫,不是并發(fā)匪蟀,用戶使用仍然有停頓。
4.9.3 CMS
(1)CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器宰僧。它非常符合在注重用戶體驗(yàn)的應(yīng)用上使用材彪。CMS(Concurrent Mark Sweep)收集器是 HotSpot 虛擬機(jī)第一款真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作琴儿。
(2)步驟
CMS是“標(biāo)記-清除”算法實(shí)現(xiàn)的段化。
· 初始標(biāo)記:暫停所有的其他線程,并記錄下直接與 root 相連的對(duì)象凤类,速度很快 ;
· 并發(fā)標(biāo)記:同時(shí)開啟 GC 和用戶線程普气,用一個(gè)閉包結(jié)構(gòu)去記錄可達(dá)對(duì)象谜疤。但在這個(gè)階段結(jié)束,這個(gè)閉包結(jié)構(gòu)并不能保證包含當(dāng)前所有的可達(dá)對(duì)象。因?yàn)橛脩艟€程可能會(huì)不斷的更新引用域夷磕,所以 GC 線程無法保證可達(dá)性分析的實(shí)時(shí)性履肃。所以這個(gè)算法里會(huì)跟蹤記錄這些發(fā)生引用更新的地方。
· 重新標(biāo)記:重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因?yàn)橛脩舫绦蚶^續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄坐桩,這個(gè)階段的停頓時(shí)間一般會(huì)比初始標(biāo)記階段的時(shí)間稍長(zhǎng)尺棋,遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段時(shí)間短
· 并發(fā)清除:開啟用戶線程,同時(shí) GC 線程開始對(duì)未標(biāo)記的區(qū)域做清掃绵跷。
(3)優(yōu)缺點(diǎn)
·優(yōu)點(diǎn):并發(fā)收集膘螟、低停頓。
· 缺點(diǎn):
? ? ·對(duì) CPU 資源敏感碾局;
? ? ·無法處理浮動(dòng)垃圾荆残;
? ? ·它使用的回收算法-“標(biāo)記-清除”算法會(huì)導(dǎo)致收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生。
4.9.4 G1收集器
(1)概念
G1 (Garbage-First) 是一款面向服務(wù)器的垃圾收集器,主要針對(duì)配備多顆處理器及大容量?jī)?nèi)存的機(jī)器. 以極高概率滿足 GC 停頓時(shí)間要求的同時(shí),還具備高吞吐量性能特征.
(2)特點(diǎn)
被視為 JDK1.7 中 HotSpot 虛擬機(jī)的一個(gè)重要進(jìn)化特征净当。它具備一下特點(diǎn):
·并行與并發(fā):G1 能充分利用 CPU内斯、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè) CPU(CPU 或者 CPU 核心)來縮短 Stop-The-World 停頓時(shí)間像啼。部分其他收集器原本需要停頓 Java 線程執(zhí)行的 GC 動(dòng)作俘闯,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行。
·分代收集:雖然 G1 可以不需要其他收集器配合就能獨(dú)立管理整個(gè) GC 堆忽冻,但是還是保留了分代的概念真朗。
·空間整合:與 CMS 的“標(biāo)記--清理”算法不同,G1 從整體來看是基于“標(biāo)記整理”算法實(shí)現(xiàn)的收集器甚颂;從局部上來看是基于“復(fù)制”算法實(shí)現(xiàn)的蜜猾。
·可預(yù)測(cè)的停頓:這是 G1 相對(duì)于 CMS 的另一個(gè)大優(yōu)勢(shì),降低停頓時(shí)間是 G1 和 CMS 共同的關(guān)注點(diǎn)振诬,但 G1 除了追求低停頓外蹭睡,還能建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為 M 毫秒的時(shí)間片段內(nèi)赶么。
(3)運(yùn)行步驟
????G1 收集器的運(yùn)作大致分為以下幾個(gè)步驟:
·初始標(biāo)記
·并發(fā)標(biāo)記
·最終標(biāo)記
·篩選回收
????**G1 收集器在后臺(tái)維護(hù)了一個(gè)優(yōu)先列表肩豁,每次根據(jù)允許的收集時(shí)間,優(yōu)先選擇回收價(jià)值最大的 Region(這也就是它的名字 Garbage-First 的由來)**辫呻。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式清钥,保證了 G1 收集器在有限時(shí)間內(nèi)可以盡可能高的收集效率(把內(nèi)存化整為零)。
4.10 JVM參數(shù)
4.10.1 堆內(nèi)存相關(guān)
4.10.1.1 顯示指定堆內(nèi)存
????-Xms<heapsize>[unit]
????-Xmx<heapsize>[unit]
·heap size表示要初始化內(nèi)存的具體大小放闺。
·unit表示要初始化內(nèi)存的單位祟昭。單位為*“ g”*** (GB) 、*“ m”*(MB)怖侦、**“ k”(KB)篡悟。
-Xms2G -Xmx5G
4.10.1.2 顯示指定新生代內(nèi)存大小
默認(rèn)情況下谜叹,YG 的最小大小為 1310MB,最大大小為無限制搬葬。一共有兩種指定 新生代內(nèi)存(Young Ceneration)大小的方法荷腊。
(1)通過-XX:NewSize和-XX:MaxNewSize指定
????????-XX:NewSize=<youngsize>[unit]
????????-XX:MaxNewSize=<youngsize>[unit]
-XX:NewSize=256m-XX:MaxNewSize=1024m
(2)通過-Xmn<young size>[unit] 指定
-Xmn256m
還可以通過**-XX:NewRatio=**來設(shè)置新生代和老年代內(nèi)存的比值。
4.10.1.3 顯示指定永久代/元空間的大小
(1)JDK1.8之前
-XX:PermSize=N //方法區(qū) (永久代) 初始大小
-XX:MaxPermSize=N //方法區(qū) (永久代) 最大大小,超過這個(gè)值將會(huì)拋出 OutOfMemoryError 異常:java.lang.OutOfMemoryError: PermGen
(2)JDK 1.8
? ??方法區(qū)(HotSpot 的永久代)被徹底移除了(JDK1.7 就已經(jīng)開始了)急凰,取而代之是元空間女仰,元空間使用的是直接內(nèi)存。
-XX:MetaspaceSize=N //設(shè)置 Metaspace 的初始(和最小大新招狻)-XX:MaxMetaspaceSize=N //設(shè)置 Metaspace 的最大大小疾忍,如果不指定大小
4.10.2 垃圾收集相關(guān)
JVM具有四種類型的GC實(shí)現(xiàn):
? ? ·串行垃圾收集器
? ? ·并行垃圾收集器
? ? ·CMS垃圾收集器
? ? ?·G1垃圾收集器
可以使用以下參數(shù)聲明這些實(shí)現(xiàn):
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+USeParNewGC
-XX:+UseG1GC
4.10.3 GC調(diào)優(yōu)策略
4.10.3.1 GC 調(diào)優(yōu)原則
????多數(shù)的 Java 應(yīng)用不需要在服務(wù)器上進(jìn)行 GC 優(yōu)化;?
????多數(shù)導(dǎo)致 GC 問題的 Java 應(yīng)用企孩,都不是因?yàn)槲覀儏?shù)設(shè)置錯(cuò)誤锭碳,而是代碼問題;
? ? 在應(yīng)用上線之前勿璃,先考慮將機(jī)器的 JVM 參數(shù)設(shè)置到最優(yōu)(最適合)擒抛;?
????減少創(chuàng)建對(duì)象的數(shù)量;?
????減少使用全局變量和大對(duì)象补疑;?
????GC 優(yōu)化是到最后不得已才采用的手段歧沪;?
????在實(shí)際使用中,分析 GC 情況優(yōu)化代碼比優(yōu)化 GC 參數(shù)要多得多莲组。
4.10.3.2 GC 調(diào)優(yōu)目的
????將轉(zhuǎn)移到老年代的對(duì)象數(shù)量降低到最姓锇;?
????減少 GC 的執(zhí)行時(shí)間锹杈。
4.10.3.3 GC 調(diào)優(yōu)策略
(1)策略 1
將新對(duì)象預(yù)留在新生代撵孤,由于 Full GC 的成本遠(yuǎn)高于 Minor GC,因此盡可能將對(duì)象分配在新生代是明智的做法竭望,實(shí)際項(xiàng)目中根據(jù) GC 日志分析新生代空間大小分配是否合理邪码,適當(dāng)通過“-Xmn”命令調(diào)節(jié)新生代大小,最大限度降低新對(duì)象直接進(jìn)入老年代的情況咬清。
(2)策略 2
大對(duì)象進(jìn)入老年代闭专,雖然大部分情況下,將對(duì)象分配在新生代是合理的旧烧。但是對(duì)于大對(duì)象這種做法卻值得商榷影钉,大對(duì)象如果首次在新生代分配可能會(huì)出現(xiàn)空間不足導(dǎo)致很多年齡不夠的小對(duì)象被分配的老年代,破壞新生代的對(duì)象結(jié)構(gòu)掘剪,可能會(huì)出現(xiàn)頻繁的 full gc平委。因此,對(duì)于大對(duì)象夺谁,可以設(shè)置直接進(jìn)入老年代(當(dāng)然短命的大對(duì)象對(duì)于垃圾回收來說簡(jiǎn)直就是噩夢(mèng))廉赔。-XX:PretenureSizeThreshold 可以設(shè)置直接進(jìn)入老年代的對(duì)象大小愚墓。
(3)策略 3
????合理設(shè)置進(jìn)入老年代對(duì)象的年齡,-XX:MaxTenuringThreshold 設(shè)置對(duì)象進(jìn)入老年代的年齡大小昂勉,減少老年代的內(nèi)存占用,降低 full gc 發(fā)生的頻率扫腺。
(4)策略 4
????設(shè)置穩(wěn)定的堆大小岗照,堆大小設(shè)置有兩個(gè)參數(shù):-Xms 初始化堆大小,-Xmx 最大堆大小笆环。
(5)策略5
注意: 如果滿足下面的指標(biāo)攒至,則一般不需要進(jìn)行 GC 優(yōu)化:
MinorGC 執(zhí)行時(shí)間不到50ms; Minor GC 執(zhí)行不頻繁躁劣,約10秒一次迫吐; Full GC 執(zhí)行時(shí)間不到1s; Full GC 執(zhí)行頻率不算頻繁账忘,不低于10分鐘1次志膀。