Java基礎(chǔ)+集合+多線程+JVM(二)

目錄
? ? 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)丧没。

圖3-1 線程狀態(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ì)列尾部辛润。

AQS實(shí)現(xiàn)的公平鎖

? ? · 非公平鎖(默認(rèn))

? ? ? ? 如果線程在發(fā)出請(qǐng)求的同時(shí)該鎖的狀態(tài)變?yōu)榭捎茫敲催@個(gè)線程將跳過隊(duì)列中所有的等待線程并獲得這個(gè)鎖见秤。嘗試獲取鎖會(huì)進(jìn)行兩次砂竖,兩次都失敗則直接加入CLH隊(duì)列尾部,后面流程和公平鎖一致鹃答。

AQS實(shí)現(xiàn)的非公平搜

(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-2 ThreadLocal原理

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)模型圖

圖3-3 JMM

? ??????當(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)致鎖無法釋放痘绎。

圖3-4 死鎖

(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-7 線程池原理

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-8 CAS算法

(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)鎖的分配和橙。

圖3-9 AQS原理

(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?作為父類加載器叫胁。

圖4-1 雙親委派模型

(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-2 類加載過程

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)證

圖4-3 驗(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分開)撒遣。? ? ? ??

圖4-4 Java內(nèi)存區(qū)域

(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ū)分開來撤蚊。

圖 補(bǔ)充 方法區(qū)劃分
圖 補(bǔ)充 常量池

方法區(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-5 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ī)整的饲宛。

圖4-6 內(nèi)存分配

(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ì)象挫酿。

圖4-7 標(biāo)記-清除

會(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 復(fù)制算法

4.8.3 標(biāo)記-整理算法

根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法猜丹,標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象回收硅卢,而是讓所有存活的對(duì)象向一端移動(dòng)射窒,然后直接清理掉端邊界以外的內(nèi)存。

圖4-9 標(biāo)記-整理算法

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)行垃圾收集

圖4-10 GC堆(含永久代)
圖4-11 GC堆(不含永久代)

(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)步驟

圖4-12 CMS

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次志膀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鳖擒,隨后出現(xiàn)的幾起案子溉浙,更是在濱河造成了極大的恐慌,老刑警劉巖蒋荚,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戳稽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡期升,警方通過查閱死者的電腦和手機(jī)惊奇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來播赁,“玉大人颂郎,你說我怎么就攤上這事⌒新#” “怎么了祖秒?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)舟奠。 經(jīng)常有香客問我竭缝,道長(zhǎng),這世上最難降的妖魔是什么沼瘫? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任抬纸,我火速辦了婚禮,結(jié)果婚禮上耿戚,老公的妹妹穿的比我還像新娘湿故。我一直安慰自己阿趁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布坛猪。 她就那樣靜靜地躺著脖阵,像睡著了一般蚁阳。 火紅的嫁衣襯著肌膚如雪榕茧。 梳的紋絲不亂的頭發(fā)上拜隧,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天伐蒋,我揣著相機(jī)與錄音灾票,去河邊找鬼划栓。 笑死茧彤,一個(gè)胖子當(dāng)著我的面吹牛常侦,可吹牛的內(nèi)容都是我干的洋机。 我是一名探鬼主播坠宴,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼绷旗!你這毒婦竟也來了喜鼓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤衔肢,失蹤者是張志新(化名)和其女友劉穎颠通,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膀懈,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顿锰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了启搂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硼控。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胳赌,靈堂內(nèi)的尸體忽然破棺而出牢撼,到底是詐尸還是另有隱情,我是刑警寧澤疑苫,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布熏版,位于F島的核電站,受9級(jí)特大地震影響捍掺,放射性物質(zhì)發(fā)生泄漏撼短。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一挺勿、第九天 我趴在偏房一處隱蔽的房頂上張望曲横。 院中可真熱鬧,春花似錦、人聲如沸禾嫉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熙参。三九已至艳吠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孽椰,已是汗流浹背讲竿。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弄屡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓鞋诗,卻偏偏與公主長(zhǎng)得像膀捷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子削彬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344