? 一年不管是讀書還是練習(xí)婆翔,還是看各種博客,反正是學(xué)到了很多東西掏婶。從JVM的內(nèi)存布局啃奴、gc、classloader气堕、JIT纺腊、內(nèi)存模型畔咧、優(yōu)化調(diào)整到j(luò)ava的并發(fā)處理、排序算法的整理揖膜、java一些源碼的分析誓沸,到個(gè)人github的使用和git的運(yùn)用,再到數(shù)據(jù)庫的一些優(yōu)化壹粟,進(jìn)入到mysql的一些原理初步了解拜隧,再到nosql的了解,如redis趁仙,了解了一些概念洪添,組件化和微服務(wù)、虛擬化基本知識(shí)雀费、docker的管理和玩耍,還有看了余華大大的幾本小說干奢,感悟了人生,總之收獲頗多.
JVM
java虛擬機(jī)作為真正運(yùn)行java系統(tǒng)以及處理class文件的一個(gè)平臺(tái)盏袄,在java學(xué)習(xí)中(當(dāng)然不止是java忿峻,該虛擬機(jī)已經(jīng)大量的被其他語言所使用)是至關(guān)重要的,深入的了解它辕羽,不管是編碼實(shí)現(xiàn)逛尚,解決疑難雜癥,還是技術(shù)圈的相互寒暄都是很有用處的刁愿。從以下幾個(gè)方面概述下今年都得到了什么绰寞。
內(nèi)存布局
- PC(程序計(jì)數(shù)器),存放當(dāng)前運(yùn)行指令偏移量的一塊內(nèi)存,是線程獨(dú)享的.
- stack(棧)铣口,這玩意也是線程獨(dú)享的滤钱,每條線程運(yùn)行時(shí)會(huì)為其分配一個(gè)內(nèi)存棧,棧中存放的數(shù)據(jù)片(這里暫且叫片吧)叫棧幀枷踏,棧幀包括本地變量表菩暗,操作數(shù)棧,返回信息表旭蠕,符號(hào)引用等信息停团。線程每進(jìn)入一個(gè)方法會(huì)創(chuàng)建一個(gè)棧幀置于棧頂,執(zhí)行完會(huì)出棧.
- 本地變量表掏熬,操作數(shù)棧佑稠,返回信息表,符號(hào)引用在編譯時(shí)確定的旗芬,只不過在運(yùn)行時(shí)將符號(hào)引用轉(zhuǎn)為直接引用舌胶。
- 本地變量表的存儲(chǔ)單元為slot(32字節(jié)),除了double,long占用兩個(gè)slot疮丛,其他都是一個(gè)slot幔嫂,其中實(shí)例方法的第一個(gè)slot存放的是this.
- 本地變量表辆它,操作數(shù)棧大小在編譯時(shí)就能確定,所以在運(yùn)行時(shí)履恩,分配多大的內(nèi)存是固定的.
- heap(堆),這塊是空間最大的(基本上是)锰茉,是線程共享的,所有對(duì)象實(shí)例都存在這個(gè)地方(說到這里,插下嘴切心,棧中slot(非基本類型)在運(yùn)行時(shí)存放的對(duì)象實(shí)例的引用飒筑,即在堆中的內(nèi)存地址).
- 對(duì)象實(shí)例是存儲(chǔ)對(duì)象數(shù)據(jù)的真正內(nèi)存區(qū)域結(jié)構(gòu),由對(duì)象頭和實(shí)例數(shù)據(jù)組成.
- 對(duì)象頭包括兩部分信息,前32字節(jié)存儲(chǔ)的是對(duì)象的鎖绽昏,gc等信息协屡,后一部分存儲(chǔ)的對(duì)象類型在方法區(qū)中標(biāo)識(shí)該對(duì)象類型的地址引用.如果是數(shù)組類型的,還需要在頭中存放長度信息全谤。
- 在jdk1.7開始肤晓,原本放在方法區(qū)中的常量池移到了該區(qū)域。
- 對(duì)象實(shí)例是存儲(chǔ)對(duì)象數(shù)據(jù)的真正內(nèi)存區(qū)域結(jié)構(gòu),由對(duì)象頭和實(shí)例數(shù)據(jù)組成.
- 方法區(qū). 方法區(qū)是線程共享的认然,存放的是類的信息材原,類加載時(shí)將類信息存放到該區(qū)域.還存放一些常量信息。
- 常量池是方法區(qū)的一部分季眷,存放了一些運(yùn)行時(shí)不變的信息,在類加載后存放類的信息(常靜態(tài)量卷胯,屬性子刮、方法相關(guān)信息)。
- jdk1.6之前在方法區(qū)有
pergen mem
作為常量池常量窑睁。
GC
GC挺峡,即垃圾回收,主要針對(duì)的是堆區(qū)担钮。由于大多數(shù)對(duì)象是使用完就無用橱赠,會(huì)被回收的,所以依據(jù)次規(guī)律JVM將內(nèi)存又分代存儲(chǔ),分為年輕代箫津、老年代狭姨、持久代。年輕代和老年代在堆區(qū)苏遥,持久代在方法區(qū).
- 對(duì)象優(yōu)先分配在年輕代饼拍,年輕代采用停止復(fù)制算法進(jìn)行內(nèi)存的回收。
- 年輕代又分為
Eden
田炭,surivor from
,surivor to
三塊區(qū)間.其中默認(rèn)大小Eden/surivor=8/1. - 每次新生代中可用的內(nèi)存空間為整個(gè)新生代容量的90%师抄,有10%的內(nèi)存是被浪費(fèi)的。
- 每一次gc操作會(huì)將Eden去存活的對(duì)象和from存活的對(duì)象復(fù)制到to區(qū)教硫。
- 由于采用的復(fù)制算法叨吮,所以不會(huì)產(chǎn)生內(nèi)存碎片辆布。
- 由于采用停止復(fù)制算法,所以在gc時(shí)茶鉴,JVM會(huì)有暫時(shí)的停頓锋玲,即
stop the world
。
- 年輕代又分為
- 年輕代存儲(chǔ)不了的對(duì)象蛤铜,超過某一閾值的大對(duì)象嫩絮,在年輕代from-to一定次數(shù)還存活的對(duì)象等會(huì)在老年代存儲(chǔ),老年代采取的是標(biāo)記清除算法。
- 標(biāo)記清除會(huì)產(chǎn)生內(nèi)存碎片围肥,當(dāng)然有些gc算法會(huì)有壓縮機(jī)制剿干。
- JDK1.7以后常量池在此區(qū)域。
- 持久代存儲(chǔ)的是類信息穆刻,靜態(tài)常量置尔。
GC的回收是通過可達(dá)性
判斷來做處理的 ,可達(dá)性俗稱是從GC ROOTS
往下遍歷看是否有路徑到達(dá)該對(duì)象氢伟,如果有榜轿,則為可達(dá),否則不可達(dá).
-
GC ROOTS
主要從一些幾個(gè)點(diǎn)出發(fā):- 線程棧幀的本地變量表
- 類靜態(tài)變量的引用
- 線程所屬變量
- 類加載時(shí)的一系列引用
- JNL 本地和全局參數(shù)
- 還有一種判斷方式交計(jì)數(shù)器朵锣,但此種如果牽扯到相互引用谬盐,即使別的地方?jīng)]有對(duì)此的引用也不會(huì)回收。
GC回收的時(shí)機(jī):
- 對(duì)象優(yōu)先在Eden中分配诚些,當(dāng)Eden中沒有足夠空間時(shí)飞傀,虛擬機(jī)將發(fā)生一次Minor GC,因?yàn)镴ava大多數(shù)對(duì)象都是朝生夕滅,所以Minor GC非常頻繁,而且速度也很快
- Full GC殿漠,發(fā)生在老年代的GC恬惯,當(dāng)老年代沒有足夠的空間時(shí)即發(fā)生Full GC,發(fā)生Full GC一般都會(huì)有一次Minor GC。
- 發(fā)生Minor GC時(shí),虛擬機(jī)會(huì)檢測(cè)之前每次晉升到老年代的平均大小是否大于老年代的剩余空間大小,如果大于颜说,則進(jìn)行一次Full GC,如果小于汰聋,則查看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗脑沿,如果允許,那只會(huì)進(jìn)行一次Minor GC马僻,如果不允許庄拇,則改為進(jìn)行一次Full GC。
參數(shù)設(shè)置
- -xmn 年輕代大小
- -xms 最小堆
- -xmx 最大堆
- -verbose:gc 檢測(cè)內(nèi)存gc信息
System.gc()
其是通知JVM需要進(jìn)行垃圾回收了,但是至于什么時(shí)候措近,這個(gè)是有JVM決定的溶弟。
盡量少用,會(huì)對(duì)JVM的gc正常造成干擾瞭郑。
finalize()
finalize
是Object
類的保護(hù)型方法辜御,任何類都可以重寫它,在回收該類的實(shí)例時(shí)屈张,會(huì)調(diào)用此方法擒权,可以在此方法中將此實(shí)例掛載到別的地方,以達(dá)到對(duì)象復(fù)活
.
安全點(diǎn)和安全區(qū)
為了支持準(zhǔn)確枚舉阁谆,JIT編譯器需要做一些額外的工作碳抄,因?yàn)橹挥蠮IT準(zhǔn)確地知道棧幀信息和寄存器上下文。 當(dāng)JIT編譯一個(gè)方法的時(shí)候场绿, 對(duì)于每一個(gè)指令剖效, 它都保存根引用信息,以防執(zhí)行阻塞在那個(gè)指令上焰盗。
但是對(duì)每一個(gè)指令都保存那些信息太昂貴了璧尸。 它需要大量空間保存那些信息。 這是不必要的熬拒,因?yàn)?只有一小部分指令有機(jī)會(huì)在實(shí)際執(zhí)行時(shí)阻塞爷光。 JIT只需要保存那部分指令的信息就夠了-- 他們就把叫做安全點(diǎn)。安全點(diǎn)意味著對(duì)應(yīng)根枚舉來說澎粟,在該點(diǎn)阻塞是安全的瞎颗。
? 安全區(qū)域是其中引用不會(huì)改變的一段代碼片段,那么在其中任一點(diǎn)進(jìn)行根枚舉都是安全的捌议。 換句話說,安全區(qū)域是安全點(diǎn)的一個(gè)很大的擴(kuò)展引有。
在安全點(diǎn)的設(shè)計(jì)中瓣颅,如果GC觸發(fā)事件發(fā)生了,執(zhí)行函數(shù)通過輪詢進(jìn)行響應(yīng)譬正。它通過設(shè)置一個(gè)準(zhǔn)備好的標(biāo)志(ready flag)來響應(yīng)宫补。 那么GC就可以進(jìn)行根枚舉了。這是一個(gè)握手協(xié)議曾我。
安全區(qū)域也遵循這個(gè)協(xié)議粉怕。執(zhí)行函數(shù)在進(jìn)入安全區(qū)域時(shí)設(shè)置ready flag。在它離開安全區(qū)域以前抒巢,它先檢查GC是否完成了枚舉(或者收集)贫贝,并且不再需要執(zhí)行函數(shù)呆在阻塞狀態(tài)。如果是真的,它就向前執(zhí)行稚晚,離開安全區(qū)域崇堵; 否則,它就像安全點(diǎn)一樣阻塞他自己客燕。
類加載
? Class文件由類裝載器裝載后鸳劳,在JVM中將形成一份描述Class結(jié)構(gòu)的元信息對(duì)象,通過該元信息對(duì)象可以獲知Class的結(jié)構(gòu)信息:如構(gòu)造函數(shù)也搓,屬性和方法等赏廓,Java允許用戶借由這個(gè)Class相關(guān)的元信息對(duì)象間接調(diào)用Class對(duì)象的功能。
虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存傍妒,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)幔摸,轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型拍顷,這就是虛擬機(jī)的類加載機(jī)制抚太。
加載機(jī)制
(1) 裝載:查找和導(dǎo)入Class文件;
(2) 鏈接:把類的二進(jìn)制數(shù)據(jù)合并到JRE中昔案;
(a)校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性尿贫;
(b)準(zhǔn)備:給類的靜態(tài)變量分配存儲(chǔ)空間;
(c)解析:將符號(hào)引用轉(zhuǎn)成直接引用踏揣;
(3) 初始化:對(duì)類的靜態(tài)變量庆亡,靜態(tài)代碼塊執(zhí)行初始化操作
裝載-----》鏈接-----》初始化
鏈接又分為三步:校驗(yàn)--》準(zhǔn)備--》解析
- 裝載:查找和導(dǎo)入Class文件,并在此階段創(chuàng)建一個(gè)對(duì)應(yīng)該class文件的
Class
實(shí)例 - 鏈接:把類的二進(jìn)制數(shù)據(jù)合并到JRE中
- 校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性
- 檢驗(yàn)class字節(jié)流的正確性捞稿,即魔法數(shù)是不是
0xCAFEBB
,jre版本正確與否 - 檢驗(yàn)一些符號(hào)引用的正確性
- 檢驗(yàn)class字節(jié)流的正確性捞稿,即魔法數(shù)是不是
- 準(zhǔn)備:給類的靜態(tài)變量分配存儲(chǔ)空間
- 此時(shí)會(huì)為靜態(tài)變量賦零值又谋,對(duì)于
constant value
會(huì)直接賦變量值.
- 此時(shí)會(huì)為靜態(tài)變量賦零值又谋,對(duì)于
- 解析:將符號(hào)引用轉(zhuǎn)成直接引用
- 校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性
- 初始化:對(duì)類的靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化操作
- 此處編譯器執(zhí)行
<clinic>
初始化方法娱局,此方法按照變量聲明初始化彰亥、靜態(tài)代碼塊編碼的順序組合. - 靜態(tài)變量可以在聲明之前初始化,但是不能作為引用.
- 這里的初始化是類的初始化衰齐,不是對(duì)象的實(shí)例化任斋,對(duì)象實(shí)例化需要執(zhí)行
<init>
方法.
- 此處編譯器執(zhí)行
雙親委派模式
Bootstrap ClassLoader
<---Extension Classloader
<---Application Classloader
<---Custom ClassLoader
通過指定父類加載器,當(dāng)類加載器加載類時(shí)耻涛,會(huì)優(yōu)先查找父類加載器進(jìn)行加載废酷,如果父類加載器沒有加載或加載不了,則使用該類加載器進(jìn)行加載抹缕,下面是一些代碼澈蟆。
loadClass(String name, Boolean resolve) throws ClassNotFoundException{
//首先檢查請(qǐng)求的類是否已經(jīng)被加載過
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){//委派父類加載器加載
c = parent.loadClass(name, false);
}else{//委派啟動(dòng)類加載器加載
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//父類加載器無法完成類加載請(qǐng)求
}
if(c == null){//本身類加載器進(jìn)行類加載
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
如下定義的類加載器,其父類加載器結(jié)果如下.
注意:Ext
的父類加載器為null
卓研,標(biāo)識(shí)為根類加載器Bootstrap
public HowswapCL(String basedir, String[] clazns) {
super(); // 指定系統(tǒng)類加載器為該classloader父類加載器
this.basedir = basedir;
dynaclazns = new HashSet();
loadClassByMe(clazns);
}
HowswapCL@6c908f05
sun.misc.Launcher$AppClassLoader@5b941dc9
sun.misc.Launcher$ExtClassLoader@592fa617
null
這種委派模式保證了java基礎(chǔ)類庫或者一些基本框架只被加載一次趴俘,保證資源的共享,不浪費(fèi)資源。
關(guān)于自定義類加載器
自定義類加載器需要繼承ClassLoader
,通過重寫loadClass(String name)
完成類的加載動(dòng)作哮幢,代碼如上带膀。
一些方法描述:
- loadClass(String name,Boolean resolve) 加載本地或網(wǎng)絡(luò)的class文件或字節(jié)流.
- Class<?> defineClass(String name, byte[] b, int off, int len) 定義一個(gè)對(duì)應(yīng)該字節(jié)碼流的Class對(duì)象.
- resolveClass(Class<?> c) 類加載過程的鏈接過程(當(dāng)然看了好多,loadClass的resolve都為false橙垢,不是加載都需要鏈接的過程么垛叨,不知道為什么?還沒找著相關(guān)資料)
類的卸載
當(dāng)類使用完后想卸載柜某,需要一些條件:
- 該類衍生的對(duì)象不再有引用嗽元。
- 該類對(duì)應(yīng)的Class不再有引用。
- 加載該類的類加載器不再有引用
這樣衍生的問題是喂击,用系統(tǒng)類加載器加載的類在JVM生命周期中是不會(huì)卸載的剂癌。所以支持熱部署的操作需要自己寫類加載器來完成,以達(dá)到類加載器引用的不可達(dá).
public static void main(String[] args) throws Exception {
// 每次都創(chuàng)建出一個(gè)新的類加載器
HowswapCL cl = new HowswapCL("E:/workhome/cl/bin/", new String[] { "Foo" });
Class cls = cl.loadClass("Foo");
Object foo = cls.newInstance();
Method m = foo.getClass().getMethod("sayHello", new Class[] {});
m.invoke(foo, new Object[] {});
//卸載class
m=null;
foo=null;
cls=null;
cl = null;
// 執(zhí)行一次gc垃圾回收
System.gc();
System.out.println("GC over");
}
類加載器衍生的問題
-
類實(shí)例判定的問題---
instanceof
只有被同一類加載器加載的類所衍生的對(duì)象實(shí)例才能判定相等或
instanceof
的判定翰绊,不被同一類加載器加載類實(shí)例的instanceof
始終為false佩谷,類加載是劃分區(qū)域的一個(gè)標(biāo)準(zhǔn)。這也提供了一種隔離手段监嗜。 -
加載時(shí)機(jī)
-
new
,getStatic
,putStatic
操作谐檀。 - 子類的加載引起的父類加載。
- 反射裁奇。
- 元素為對(duì)象的數(shù)組
new
不會(huì)引起元素類的加載桐猬。 - 由于類的靜態(tài)元素放在方法區(qū),所以對(duì)靜態(tài)元素的使用只會(huì)觸發(fā)該靜態(tài)元素所屬類的加載刽肠。
-
JIT
? jvm在執(zhí)行字節(jié)碼時(shí)溃肪,是通過解釋器與編譯器配合工作輸出結(jié)果的.解釋器將字節(jié)碼解釋成平臺(tái)相關(guān)指令,執(zhí)行后輸出結(jié)果音五。編譯器對(duì)于執(zhí)行頻繁的字節(jié)碼直接編譯成平臺(tái)相關(guān)指令code惫撰,并緩存在內(nèi)存中,下次執(zhí)行時(shí)直接調(diào)用躺涝。如果從嚴(yán)格的角度講HotSpot VM沒有使用嚴(yán)格的“JIT編譯”,而其JIT編譯是自適應(yīng)編譯厨钻。
JIT編譯與自適應(yīng)編譯都屬于“動(dòng)態(tài)編譯”(dynamic compilation),或者叫“運(yùn)行時(shí)編譯”的范疇诞挨。特點(diǎn)是在程序運(yùn)行的時(shí)候進(jìn)行編譯,而不是在程序開始運(yùn)行之前就完成了編譯呢蛤;后者也叫做“靜態(tài)編譯”(static compilation)或者AOT編譯(ahead-of-time compilation)惶傻。
JIT編譯與自適應(yīng)編譯相比:
- 前者的編譯時(shí)機(jī)比后者早:第一次執(zhí)行之前 vs 已經(jīng)被執(zhí)行過若干次
- 前者編譯的代碼比后者多:所有執(zhí)行過的代碼 vs 一部分代碼
HotSpot VM是一個(gè)典型的自適應(yīng)動(dòng)態(tài)編譯系統(tǒng),使用解釋器或Client Compiler(C1)來實(shí)現(xiàn)初始執(zhí)行和profile的收集其障,然后把profile信息交給Server Compiler(C2)做優(yōu)化編譯银室。
綜上,Hotspot jvm在運(yùn)行字節(jié)碼時(shí)是通過解釋器與JIT配合的方式來完成字節(jié)碼的執(zhí)行的,在系統(tǒng)啟動(dòng)或者字節(jié)碼剛加載后蜈敢,通過解釋器解釋執(zhí)行辜荠,并通過開啟profile來收集字節(jié)碼(這里是一個(gè)方法或者循環(huán)等的字節(jié)碼)的使用頻率,如果使用頻率超過一定的閾值抓狭,則執(zhí)行JIT優(yōu)化(注意:即使是循環(huán)其優(yōu)化也是按方法為單位進(jìn)行優(yōu)化的)伯病,下次調(diào)用此字節(jié)碼時(shí),直接調(diào)用JIT優(yōu)化后緩存在內(nèi)存中的指令碼否过。
內(nèi)存模型
? 計(jì)算機(jī)存儲(chǔ)模型是cpu每次都操作緩存里的數(shù)據(jù)午笛,該緩存的數(shù)據(jù)都是通過內(nèi)存中而來,而java也有自己的內(nèi)存模型苗桂,如下圖药磺,線程每次操作的數(shù)據(jù)都是工作內(nèi)存中數(shù)據(jù),工作內(nèi)存中數(shù)據(jù)是主內(nèi)存中數(shù)據(jù)的拷貝(當(dāng)然一個(gè)100M的大對(duì)象不可能全部拷貝煤伟,有一種策略癌佩,還需進(jìn)一步學(xué)習(xí)),每次完成后再寫入到主內(nèi)存。
如果硬要與傳統(tǒng)堆棧模型做類比的話便锨,主內(nèi)存相當(dāng)于堆围辙,工作內(nèi)存相當(dāng)于棧,并且牽扯到這種模型的數(shù)據(jù)一般不包括局部變量鸿秆,調(diào)用方法傳入的參數(shù)酌畜,當(dāng)然對(duì)于引用類型,只能保證棧幀中的引用沒有線程同步的影響.
這種內(nèi)存模型引出的問題是數(shù)據(jù)一致性問題卿叽,即線程修改變量引起的變量值的不同步桥胞。
為了解決多線程下數(shù)據(jù)一致性問題,JMM有一些特殊語法處理考婴,來調(diào)用底層指令處理數(shù)據(jù)的同步以保證一致性.
-
volatile
- 能保證數(shù)據(jù)的一致性贩虾,但不能保證數(shù)據(jù)的原子操作
- 能一定程度的防止指令重排序(volatile變量相關(guān)操作指令之前的操作不會(huì)在該指令以下執(zhí)行)
- 當(dāng)其中一線程修改了
volatile
修飾的變量并從工作內(nèi)存寫入到主內(nèi)存后,或通知其他有引用此變量的線程的工作內(nèi)存此變量無效沥阱,下次使用時(shí)會(huì)重新從主內(nèi)存加載到工作內(nèi)存缎罢。
-
synchronized
- 基于方法和代碼塊的同步措施,能保證這個(gè)方法或代碼塊某一時(shí)刻只有一個(gè)線程在操作考杉。
要點(diǎn):
- 對(duì)于實(shí)例方法的同步是通過acc_flags標(biāo)志來實(shí)現(xiàn)的.在實(shí)例對(duì)象頭部存放了對(duì)象的gc,同步監(jiān)視器策精,如果是數(shù)組的話有長度等信息.如果方法設(shè)置了同步標(biāo)志
會(huì)去判斷同步監(jiān)視器的使用情況,來決定是否能夠占有同步鎖. - 對(duì)于同步代碼快崇棠,會(huì)在class文件有
monitorenter
和monitorexit
字節(jié)碼標(biāo)識(shí)對(duì)實(shí)例對(duì)象的同步占有情況咽袜,他倆是一對(duì)的. - 自旋鎖、鎖優(yōu)化枕稀、鎖消除等
- 基于方法和代碼塊的同步措施,能保證這個(gè)方法或代碼塊某一時(shí)刻只有一個(gè)線程在操作考杉。
-
ReentrantLock
- 與
synchronized
有同樣的功能询刹,但是這種方式有可重入性谜嫉。 - 重量級(jí)操作,比較耗時(shí)凹联。
- 與
-
final
- final只是不能重新指向變量的引用沐兰,但不能保證該引用所對(duì)應(yīng)的實(shí)例有其他的引用。
- 如果在一個(gè)線程構(gòu)造了一個(gè)不可變對(duì)象之后(對(duì)象僅包含final字段)蔽挠,你希望保證這個(gè)對(duì)象被其他線程正確的查看住闯,你仍然需要使用同步才行。例如象泵,沒有其他的方式可以保證不可變對(duì)象的引用將被第二個(gè)線程看到寞秃。使用final字段的程序應(yīng)該仔細(xì)的調(diào)試,這需要深入而且仔細(xì)的理解并發(fā)在你的代碼中是如何被管理的偶惠。
優(yōu)化調(diào)整
其實(shí)這里的優(yōu)化調(diào)整概念比較廣春寿,不管是針對(duì)JVM,java code還是數(shù)據(jù)庫相關(guān)忽孽,都是一個(gè)長期的實(shí)踐加琢磨的過程绑改。
- JVM通過調(diào)整參數(shù)或者一些檢測(cè)工具來查看JVM的使用情況,預(yù)見將要出現(xiàn)的問題或者解決已經(jīng)出現(xiàn)的問題兄一。
- jps厘线,jstack,jstat出革,jhat這些命令監(jiān)控工具
- jconsole造壮,visualVm這些圖形化工具
- 來解決內(nèi)存溢出,棧溢出等運(yùn)行時(shí)error
- java code骂束,在編碼時(shí)注意一些容易引起上述問題的操作耳璧,比如資源及時(shí)釋放,鎖的同步釋放問題
- 數(shù)據(jù)庫優(yōu)化展箱,避免大量的循環(huán)單條操作數(shù)據(jù)庫旨枯,盡量批量操作減少多次網(wǎng)絡(luò)傳輸。在sql處理中混驰,盡量采用預(yù)編譯的方式攀隔,減少sql的硬解析。
并發(fā)處理
? 這次算是對(duì)java提供的高并發(fā)處理工具有了進(jìn)一步的了解栖榨。不管是線程池還是基于原子操作的工具類昆汹,還是一些為簡化操作而提供的類似Fork/join
工具等。
-
線程池
-
通過
Executors
提供的靜態(tài)方法創(chuàng)建多種類型的線程池-
newFixedThreadPool
固定線程數(shù) -
newSingleThreadExecutor
固定線程數(shù)為1 -
newCachedThreadPool
可根據(jù)需要?jiǎng)?chuàng)建新線程婴栽,線程可重用满粗,最大為Integer.MAX_VALUE
-
newScheduledThreadPool
創(chuàng)建一個(gè)可以調(diào)度任務(wù)的線程池
-
-
線程池的特點(diǎn)
- 首先如果有新任務(wù)會(huì)創(chuàng)建新線程,直到數(shù)量到達(dá)
corePoolSize
會(huì)將新的任務(wù)緩存到任務(wù)隊(duì)列列面居夹。 - 如果
corePoolSize
這些線程都在使用败潦,并且任務(wù)隊(duì)列已滿,則會(huì)繼續(xù)創(chuàng)建線程直到maxPoolSize
准脂,當(dāng)?shù)竭_(dá)此數(shù)量還解決不了任務(wù)隊(duì)列滿的問題劫扒,會(huì)拋出RejectedExecutionHandler
,當(dāng)然這里的拒絕執(zhí)行異常由自己實(shí)現(xiàn)狸膏。 -
corePoolSize
以內(nèi)的線程會(huì)一直存活沟饥,當(dāng)新任務(wù)進(jìn)入時(shí),且沒有達(dá)到corePoolSize
時(shí)湾戳,會(huì)優(yōu)先選擇創(chuàng)建新的線程贤旷。
- 首先如果有新任務(wù)會(huì)創(chuàng)建新線程,直到數(shù)量到達(dá)
-
任務(wù)處理
-
submit()
可處理一般的Runnable
,也可處理有返回值的任務(wù)callable
砾脑。 -
shutdown()
會(huì)處理已經(jīng)提交的任務(wù)幼驶,但是池不會(huì)接受新的任務(wù)提交。 -
shutdownNow()
會(huì)嘗試著消除所有任務(wù)韧衣。
-
-
-
并發(fā)框架
-
CountDownLatch
-
CountDownLatch
能使所有線程等待盅藻,直到其他線程結(jié)束操作,通過await
做等待操作畅铭,直到countDown
操作使Latch
為0氏淑,則執(zhí)行后續(xù)的操作. - 通過
CountDownLatch(int count)
新建一個(gè)實(shí)例,并需要count
次CountDown
才能喚醒等待的線程硕噩。 -
countDown()
將countDown
次數(shù)減一假残。 -
await()
等待countDown
次數(shù)為0才被喚醒。
-
-
CyclicBarrier
-
CyclicBarrier
是回環(huán)柵欄炉擅。 - 通過
new CyclicBarrier(parties, barrierAction)
生成一個(gè)實(shí)例辉懒,parties
是所有等待被喚醒需要的線程數(shù),barrierAction
是一個(gè)所有線程被喚醒后需要執(zhí)行的柵欄動(dòng)作。 - 通過
await
操作等待在barrier
坑资。 - 當(dāng)所有線程都到達(dá)
barrier
時(shí)耗帕,會(huì)推選任一線程執(zhí)行barrierAction
,并且會(huì)執(zhí)行后續(xù)的操作袱贮。 -
CyclicBarrier
可重復(fù)使用仿便。
-
-
Exchanger
-
Exchanger
主要用來作為兩線程交換數(shù)據(jù)使用。 - 通過
new Exchanger()
實(shí)例化一個(gè)交換器攒巍。 - 當(dāng)線程調(diào)用
exchange
操作時(shí)嗽仪,線程進(jìn)入阻塞狀,當(dāng)其他線程也調(diào)用時(shí)柒莉,將數(shù)據(jù)交換闻坚,執(zhí)行后續(xù)操作。
-
-
Fork/Join
-
Fork/Join
也叫分而治之兢孝,通過將大任務(wù)分成多個(gè)小任務(wù)窿凤,最后再合成仅偎,利用并發(fā)的特性執(zhí)行,效率較高雳殊。 -
Fork/Join
通過使用ForkJoinTask
和ForkJoinPool
來完成.- 其中
ForkJoinTask
提供了以下兩個(gè)子類:- RecursiveAction:用于沒有返回結(jié)果的任務(wù)橘沥。
- RecursiveTask :用于有返回結(jié)果的任務(wù)。
- 其中
-
fork()
來分解,join()
來合成夯秃。 - 這個(gè)工具在
1.7
才加進(jìn)來的座咆。
-
-
Semaphore
-
Semaphore
即信號(hào)量,通過信號(hào)量仓洼,控制對(duì)公共資源的訪問介陶。 - 通過
acquire
等待資源的獲取,release
釋放資源色建。 - 和
notify/await
功能類似哺呜,只是封裝了對(duì)臨界資源的狀態(tài)判斷。
-
-
-
普通線程
- 線程中斷
- 由于對(duì)線程停止有些暴力箕戳,這里通過線程中斷的方式通知線程你應(yīng)該停下了弦牡,至于怎么處理,需要線程自己做處理漂羊。
- 中斷方法
-
interrupt()
通知線程需要中斷驾锰。 -
static boolean interrupted()
用于判斷線程是否收到了中斷信號(hào),并且清除這個(gè)中斷信號(hào)走越。 -
isInterrupted()
判斷線程是否被中斷椭豫,即是否收到中斷信號(hào)。 - 例外:
-
Thread.sleep()
,Thread.await()
時(shí)接收到中斷信號(hào)旨指,會(huì)清除這個(gè)中斷信號(hào)赏酥,即線程下次認(rèn)為自己沒有被中斷。
-
-
-
臨界資源的通知/喚醒
- 通過
object.wait()
,object.notify()
實(shí)現(xiàn)線程的通知喚醒操作谆构。 -
notify
,wait
是針對(duì)對(duì)象級(jí)的操作裸扶,所以在調(diào)用這些方法時(shí),必須使當(dāng)前線程成為對(duì)象監(jiān)視器的所有者搬素。如果沒有同步操作呵晨,則會(huì)報(bào)如下錯(cuò)誤,指明當(dāng)前線程不是對(duì)象監(jiān)視器的所有者熬尺。
- 通過
- 線程中斷
-
原子類工具
-
Atomic**
提供了一些對(duì)基本類型對(duì)象級(jí)的原子操作功能摸屠。-
compareAndSet(int expect, int update)
原子的操作。
-
- 為了解決
ABA
問題粱哼,通過AtomicStampedReference
加修改標(biāo)記戳的方式來解決此問題季二。
-
算法
通過在leetcode
上做題,以及在實(shí)驗(yàn)樓上
做題,并且溫習(xí)了基本的排序算法胯舷,比較鍛煉腦子刻蚯,慢慢遲鈍的小腦袋慢慢開始退銹了,哈哈桑嘶。
第一步從排序算法開始:
選擇排序:每次選擇一個(gè)最值芦倒,直接和目標(biāo)位數(shù)交換。
插入排序:浪費(fèi)空間和時(shí)間不翩,每次將源序列中的元素和目標(biāo)序列中的每一個(gè)元素做比較。
快排:快排以一個(gè)元素作為分界點(diǎn)麻裳,利用分而治之的思想口蝠,通過遞歸各自排好序再合成的思想實(shí)現(xiàn)。
歸并排序:思想也是分而治之的思想津坑,和快排有些類似妙蔗。
冒泡排序:通過相鄰元素間比較,并移動(dòng)大的元素來實(shí)現(xiàn)疆瑰,如果正好有序眉反,則只需要一次。
-
在JDK的源碼中穆役,由于
Collection
最終是以數(shù)組的形成呈現(xiàn)的寸五,其排序是通過Arrays.sort(Object[])
來完成的。public static void sort(Object[] a) { if (LegacyMergeSort.userRequested) legacyMergeSort(a); else ComparableTimSort.sort(a); }
通過上面
legacyMergeSort
是傳統(tǒng)的歸并排序耿币,而在1.7
以后使用的是ComparableTimSort
,其內(nèi)部實(shí)現(xiàn)是Timsort
,這個(gè)還需進(jìn)一步研究梳杏。
第二步當(dāng)然是查找了:
- 順序查找:浪費(fèi)時(shí)間。
- 二分查找:由于這種適合有序序列淹接。
- 樹結(jié)構(gòu):通過將各元素分解在樹的各節(jié)點(diǎn)上十性,快速查找。
第三步是leetcode的練習(xí)了:
通過練習(xí)算法題,對(duì)一些技巧塑悼,遞歸的運(yùn)用有了一定的認(rèn)識(shí)劲适,但是不得不吐槽的是。leetcode
的測(cè)試用例有的不全厢蒜,有的有問題霞势,做了這么多題,印象最深的是正則算法的那道題(RegularExpressionMatching
)斑鸦,技巧運(yùn)用相當(dāng)?shù)拿畎 ?/p>
以后還是要堅(jiān)持支示,腦子還是得磨磨啊鄙才!
源碼分析
基本數(shù)據(jù)類型的引用類型
像Byte
,Integer
,Long
這些類型有cache
機(jī)制颂鸿,也叫享元模式,將-128-127
之間的數(shù)據(jù)緩存常量池中攒庵,像下面這種:
Integer a=1;//Integer a=Integer.valueOf(1);
其實(shí)用到了裝箱嘴纺,裝箱采用了享元模式败晴,源代碼如下:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在涉及到運(yùn)算操作的時(shí)候會(huì)進(jìn)行拆箱操作,如下:
Integer a=Integer.valueOf(1);
Integer b=1;
Long c=2L;
System.out.println(a+b==c);//
上面的a+b==c
相當(dāng)于a.intValue()+b.intValue()==c.longValue()
栽渴。
其他的類似尖坤。
String
String
算是用的最多的一個(gè)引用類型了,是不可變類型的一個(gè)代表闲擦,由于其不可變特性慢味,所以對(duì)它的修改是線程安全的。內(nèi)部是通過final char[]
數(shù)組實(shí)現(xiàn)的墅冷。
-
hashCode()
哈希碼通過對(duì)每個(gè)字符的計(jì)算得出纯路,其算法為
s0*31^(length-1)+s1*31^(length-2)+...+s(n-1)
得出,當(dāng)然這是在初始hash
為0的情況下寞忿。哈希碼主要用在字典表這種運(yùn)用哈希桶的地方驰唬,來唯一確定一個(gè)哈希桶。
-
substring(beginIndex, endIndex)
求子串其實(shí)和源
String
實(shí)例用的同一個(gè)char[]
引用腔彰,只是新的實(shí)例重新指定了offset
和count
叫编。這有一個(gè)問題是如果源串很大,求子串時(shí)霹抛,char數(shù)組引用占用的空間是不釋放的搓逾。split
內(nèi)部使用的求子串的方法,所以類似杯拐,注意點(diǎn)吧恃逻。 -
intern
intern
是將字符串方法常量池的手段,其是一個(gè)native
方法藕施,判斷常量池中有無該字符串值的串寇损,有則指向這個(gè)引用,否則重新生成一個(gè)裳食。-
String s=new String("123")
創(chuàng)建了幾個(gè)對(duì)象矛市?- a:至少在堆中創(chuàng)建一個(gè)對(duì)象,s的引用指向堆中的引用诲祸,另外一個(gè)對(duì)象會(huì)在常量池中判斷有無
123
這個(gè)字符串浊吏,有則不創(chuàng)建,無則創(chuàng)建救氯。
- a:至少在堆中創(chuàng)建一個(gè)對(duì)象,s的引用指向堆中的引用诲祸,另外一個(gè)對(duì)象會(huì)在常量池中判斷有無
-
1.7
之后找田,將常量池從從方法區(qū)移到了堆區(qū),所以下面的代碼在6和7執(zhí)行會(huì)有差別:String a="a"; String b="b"; String c=a+b; System.out.println(c==c.intern());//1.7 true
String a="a"; String b="b"; String c=a+b; System.out.println(c==c.intern());//1.6 false
-
Map
HashMap
是所有哈希字典存儲(chǔ)的基礎(chǔ)着憨,通過哈希桶存儲(chǔ)墩衙,利用hashCode%桶數(shù)量
來分散的將字典元素存儲(chǔ)在不同的鏈表上,即每一個(gè)桶的存儲(chǔ)是通過單鏈表實(shí)現(xiàn)的。
默認(rèn)的數(shù)量為16漆改,負(fù)載因子為0.75心铃,即最多存儲(chǔ)12個(gè)元素后會(huì)發(fā)生擴(kuò)容,擴(kuò)容一般為原來容量的二倍挫剑。
與HashTable
的比較:
-
HashTable
不允許key-value 為null,而HashMap
則可以去扣,由于HashMap
對(duì)這種情況做了特殊處理,而HashTable
通過null key做hash碼的時(shí)候會(huì)報(bào)空指針樊破。//HashMap public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); /** * Offloaded version of put for null keys */ private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0);//即hash碼為0處理 return null; }
```java
//HashTable
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
private int hash(Object k) {
// hashSeed will be zero if alternative hashing is disabled.
return hashSeed ^ k.hashCode();//這里key不能為null愉棱,否則空指針
}
-
HashTable
是線程安全的,由于所有操作都對(duì)字典數(shù)組做了同步處理哲戚,對(duì)這個(gè)優(yōu)化做的最好的是通過分段鎖實(shí)現(xiàn)的ConcurrentHashMap
奔滑。
HashSet的實(shí)現(xiàn)
HashSet
的內(nèi)部是通過HashMap
實(shí)現(xiàn)的,將add
的元素作為key
惫恼,每次new
一個(gè)Object
作為value
,這樣保證了唯一澳盐。
Spring
的AOP
機(jī)制
Spring
的AOP
是通過代理模式實(shí)現(xiàn)的祈纯,對(duì)于實(shí)現(xiàn)了了接口的類,通過實(shí)現(xiàn)InvocationHandler
與Proxy
配合的方式創(chuàng)建代理類叼耙。并實(shí)現(xiàn)功能腕窥;而對(duì)于無接口實(shí)現(xiàn)的則通過CGLIB
第三方框架實(shí)現(xiàn)以上屬于動(dòng)態(tài)代理,在運(yùn)行時(shí)生成筛婉。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {//如果是接口簇爆,則通過JDK代理
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);//否則使用Cglib
}
else {
return new JdkDynamicAopProxy(config);
}
}
一道印象深刻的題,怎么對(duì)HashMap<Integer,Integer>的元素按value的大小排序爽撒,通過實(shí)現(xiàn)
Comparator
重寫compare
方法來完成入蛆。public class Test { public static void main(String[] args) { HashMap<Integer,Integer> map=new HashMap<>(); for(int i=0;i<10;i++){ map.put(i, 10-i); } Set<Entry<Integer, Integer>> set=map.entrySet(); for(Entry<Integer, Integer> entry:set){ System.out.println(entry.getValue()); } System.out.println("=================================="); List<Entry<Integer,Integer>> list=new ArrayList<Entry<Integer,Integer>>(set); Collections.sort(list, new HashMapComp()); for(Entry<Integer, Integer> entry:list){ System.out.println(entry.getValue()); } } } class HashMapComp implements Comparator<Entry<Integer,Integer>>{ @Override public int compare(Entry<Integer, Integer> o1, Entry<Integer, Integer> o2) { return o1.getValue().compareTo(o2.getValue()); } }
下半場(chǎng)
不知不覺已經(jīng)放了這么多天,今天2016最后一天硕勿,有點(diǎn)時(shí)間正好把剩下的再總結(jié)下哨毁,說實(shí)話,今年過的相對(duì)容易源武,沒有前兩年那么忙扼褪,個(gè)人時(shí)間很多,促使自己看了很多書粱栖,學(xué)了很多習(xí)哈哈话浇,_。
-
worker
模型
在重新整理并重構(gòu)項(xiàng)目的任務(wù)處理模塊時(shí)闹究,接觸到了worker
模型幔崖,該模型不干擾主程序功能層次,通過將任務(wù)交給第三方工人(當(dāng)然多任務(wù)的話,就是各包工頭的概念,具體他將任務(wù)配發(fā)給線程池(多個(gè)工人),最終有具體的線程去執(zhí)行)負(fù)責(zé)就ok啦,具體的執(zhí)行和運(yùn)作由Worker幫你完成(當(dāng)然在同步串行性要求高的情況下這還有待商榷)岖瑰,但是就項(xiàng)目的一般處理或者更復(fù)雜的處理這是完全ok的叛买。
上圖是基本的實(shí)現(xiàn)思想。
-
tomcat
的運(yùn)行機(jī)制說實(shí)話蹋订,這一塊還需要花時(shí)間研究下率挣,花了兩天(當(dāng)然加起來估計(jì)不到一上午)看了下結(jié)構(gòu),
初衷露戒?
因?yàn)槲耶?dāng)時(shí)做過一個(gè)小例子椒功,針對(duì)
no web.xml
而實(shí)現(xiàn)web項(xiàng)目,但是一直有個(gè)疑惑智什,tomcat啟動(dòng)過程中在找不到web.xml文件的情況动漾,怎么找到我自己實(shí)現(xiàn)的初始化器?Answer
通過研究源碼荠锭,知道是這么個(gè)過程:
- 在
classpath:/WEB-INF/lib/
下查找jar
(在/META-INF/services/
下有無文件名為javax.servlet.ServletContainerInitializer
的文件旱眯,這是個(gè)約定吧),如果有此文件证九,則解析此文件删豺,將其字符串標(biāo)識(shí)的類作為該Context的初始化器。 - 解析上面的Context的初始化器
@HandlesTypes
注解的values值愧怜,以標(biāo)識(shí)該初始化器需要初始化那些類型(包括該類型的子類)呀页。 - 在
classpath:/WEB-INF/classes/
下查找上面第二步values值所表示的類或者該類的子類,作為需要Set集合傳入由該初始化器加載拥坛。 - 通過反射調(diào)用此初始化器的
onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
啟動(dòng)蓬蝶。
上面這個(gè)過程在
Spring
的spring-web
中已經(jīng)實(shí)現(xiàn):#/META-INF/services/javax.servlet.ServletContainerInitializer內(nèi)容 org.springframework.web.SpringServletContainerInitializer #org.springframework.web.SpringServletContainerInitializer內(nèi)容 @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { } } #我自己實(shí)現(xiàn)的WebApplicationInitializer public class DefaultConfigration implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) throws ServletException { // 配置Spring提供的字符編碼過濾器 javax.servlet.FilterRegistration.Dynamic filter = container.addFilter("encoding", new CharacterEncodingFilter()); // 配置過濾器的過濾路徑 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/"); // 基于注解配置的Spring容器上下文 AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); // 注冊(cè)Spring容器配置類 rootContext.register(AppConfig.class); container.addListener(new ContextLoaderListener(rootContext)); } }
上面使整個(gè)過程,通過自己實(shí)現(xiàn)初始化器,對(duì)
servlet
,filter
,listener
猜惋,初始化上下文
等的加載有了進(jìn)一步的概念丸氛。下面是一張tomcat的實(shí)現(xiàn)基本結(jié)構(gòu)圖:
- 在
具體的實(shí)現(xiàn),還需進(jìn)一步研究著摔,下面是鏈接:
?