String對象是如何實現(xiàn)的?
- Java6以及之前的版本,String是對char數(shù)組進(jìn)行了封裝實現(xiàn)的對象,主要有,char數(shù)組,偏移量offset,字符串?dāng)?shù)量count,哈希值
- Java7到8String類中不再有offset和count兩個變量了,String占用的內(nèi)存稍微小些,同時,String.substring方法不再共享char,解決了可能導(dǎo)致內(nèi)存泄漏的問題
- java9開始,char[]字段改為了byte[]字段,維護(hù)了一個新的coder,它是編碼格式的標(biāo)識
編譯器會對,str+str進(jìn)行優(yōu)化,改成StringBuilder,每次+都會new一個新的StringBuilder的對象,所以,建議直接使用StringBuilder來優(yōu)化
String的內(nèi)存分配
- String str = "abc" 代碼編譯時,會在常量池中創(chuàng)建常量abc,運行時返回常量池中的字符串的飲用
- String str = new String("abc"),代碼在加載時會在常量池中創(chuàng)建常量abc,在調(diào)用new時,會在堆中創(chuàng)建String對象,并且引用常量池中字符串對象char[]數(shù)組,并且返回String對象的引用
- public class Test{String test1,String test2}(String 為成員變量),在運行時,創(chuàng)建的String對象會在堆中,不會在常量池中創(chuàng)建
注意:使用intern方法需要注意一點,一定要結(jié)合實際場景,因為常量池實現(xiàn)類似于一個HashTable的實現(xiàn)方式,存儲的數(shù)據(jù)越大,遍歷的時間復(fù)雜度就會提升
正則表達(dá)式
目前實現(xiàn)正則表達(dá)式引擎的方式有兩種,DFA(確定有限狀態(tài)自動機(jī))和NFA(非確定有限狀態(tài)自動機(jī)),對比來看,構(gòu)造DFA自動機(jī)的代價遠(yuǎn)大于NFA自動機(jī),但DFA自動機(jī)執(zhí)行效率高于NFA,NFA自動機(jī)的優(yōu)勢時支持更多的功能,例如,捕獲group,環(huán)視,占有優(yōu)先量詞等高級功能,在編程語言里使用的正則表達(dá)式引擎都是基于NFA實現(xiàn)的
NFA自動機(jī)的回溯,NFA實現(xiàn)會引起大量回溯問題,比如ab{1,3}c,source=abbc,匹配b之后會不斷的回溯來看是否滿足,貪婪模式就是回溯的導(dǎo)火索
貪婪模式就是,如果單獨使用+、?、*或者{minx,max}等量詞,正則表達(dá)式會匹配盡可能多的內(nèi)容
懶惰模式,在這種情況下正則表達(dá)式會盡可能少的重復(fù)匹配字符串,如果匹配成功則會繼續(xù)匹配剩余的字符串,開啟懶惰模式可以在量詞后面拼接?,如"ab{1,3}c",如果匹配的結(jié)果是abc那么一次匹配即可成功
獨占模式,獨占模式和貪婪模式一樣會最大限度的匹配更多的內(nèi)容,不同的是,在獨占模式下,匹配失敗就會結(jié)束匹配,不會發(fā)生回溯的問題,開始獨占模式在字符串后面增加個+,如"ab{1,3}+bc"
正則表達(dá)式的優(yōu)化
- 少用貪婪模式多用獨占模式
- 減少分支的選擇,如"abcd|abef"替換為"ab(cd|ef)"
- 減少捕獲嵌套
I/O
- 傳統(tǒng)I/O和NIO的最大區(qū)別就是傳統(tǒng)I/O是面向流的,NIO是面向buffer的,Buffer可以將文件一次性讀入內(nèi)存再做后續(xù)的處理,而傳統(tǒng)的方式是邊讀文件邊處理數(shù)據(jù),雖然傳統(tǒng)I/O后面也是用了緩存快,如:BufferedInputStream,但是仍不嫩隔閡NIO媲美
- NIO的Buffer除了做緩存快塊優(yōu)化外,還提供了一個可以直接訪問物理內(nèi)存的類,DirectBuffer,普通的Buffer分配的是JVM堆內(nèi)存,而DirectBuffer是直接分配的物理內(nèi)存,
數(shù)據(jù)要輸出到外部設(shè)備,必須先從用戶空間復(fù)制到內(nèi)核空間,再復(fù)制到輸出設(shè)備,而在Java中,在用戶空間中又存在一個拷貝,那就是從java堆內(nèi)存拷貝到臨時的直接內(nèi)存中去,通過臨時的直接內(nèi)存拷貝到內(nèi)存空間中去,此時的直接內(nèi)存和堆內(nèi)存就是屬于用戶空間(為什么Java要通過一個臉是非堆內(nèi)存來復(fù)制數(shù)據(jù)?如果單純使用Java堆內(nèi)存進(jìn)行數(shù)據(jù)拷貝,當(dāng)拷貝的數(shù)據(jù)量比較大的情況,Java堆的GC壓力會比較大,而使用非堆內(nèi)存可以簡低GC壓力),DirectBuffer則是直接將步驟簡化為直接保存數(shù)據(jù)到非堆內(nèi)存中,從而減少一次拷貝
DirectBuffer申請的是非JVM的物理內(nèi)存,所以創(chuàng)建和銷毀的代價很高,DirectBuffer申請的內(nèi)存并不是直接由JVM負(fù)責(zé)垃圾回收,但在DirectBuffer包裝類回收時,會通過JavaReference機(jī)智來釋放蓋內(nèi)存塊
DirectBuffer只優(yōu)化了用戶空間內(nèi)部的拷貝,MappedByteBuffer時通過本地類調(diào)用mmap進(jìn)行文件內(nèi)存映射的,map()系統(tǒng)方法會直接將文件從硬盤拷貝到用戶空間,只進(jìn)行一次數(shù)據(jù)拷貝,從何減少了read方法從硬盤拷貝到內(nèi)核的這一步
零拷貝,DirectBuffer和MappedByteBuffer
偏向鎖
鎖狀態(tài)流轉(zhuǎn)

上下文切換
上下文切換是線程之間切換的時候保存之前線程的狀態(tài),加載新線程的數(shù)據(jù),再垃圾回收的時候就可能導(dǎo)致Stop-the-world,就是線程暫停的行為
Linux可以使用 vmstat命令來查看切換的頻率(vmstat -pid),如果監(jiān)視某個應(yīng)用上下文的切換,就可以使用pidstat命令監(jiān)控指定進(jìn)程的Context Switch上下文切換(pidstat -w -l -p <pid> 1 100)
什么時候會導(dǎo)致上下文切換
- 系統(tǒng)原因,如分配的時間片到了,I/O中斷等
- 程序中調(diào)用slepp,wait,park等導(dǎo)致線程狀態(tài)的改變
所以,在多線程中,鎖其實不是性能的開銷,競爭鎖才是為了提高性能,優(yōu)化點有
- 減少鎖持有的時間,只在關(guān)鍵競爭處增加鎖
- 降低鎖的粒度,如鎖分離,鎖分段(ConcurrentHashMap)
- 樂觀鎖代替競爭鎖
- wait和notify的優(yōu)化,建議使用Lock鎖結(jié)合Condition接口代替
- 合理設(shè)置線程池的大小,避免創(chuàng)建過多的線程,根據(jù)自己的業(yè)務(wù)場景,一般在N+1和2N兩個公式中選擇一個合適的
- 使用協(xié)程實現(xiàn)非阻塞等待
- 減少JAVA垃圾回收
線程模型
實現(xiàn)線程的主要有三種方式:
- 輕量級進(jìn)程和內(nèi)核線程一對一互相映射實現(xiàn)1:1線程模型(JAVA在LINUX下的實現(xiàn))
- 用戶線程和內(nèi)核線程實現(xiàn)N:1線程模型
- 用戶線程和輕量級進(jìn)程混合實現(xiàn)N:M線程模型
1:1線程模型,通過fork函數(shù)創(chuàng)建一個子進(jìn)程來代表一個內(nèi)核中的線程,一個進(jìn)程調(diào)用fork函數(shù)后系統(tǒng)會給新的進(jìn)程分配資源,然后把原進(jìn)程中所有的值都復(fù)制到新的進(jìn)程中,只有少數(shù)的與原來不同,如:PID,由于每次都需要復(fù)制一摸一樣的數(shù)據(jù),LWP進(jìn)行了優(yōu)化使用clone函數(shù)來創(chuàng)建線程,沒有復(fù)制的資源可以通過指針共享給子進(jìn)程
N:1線程模型,由于1:1線程模型是和內(nèi)核一對一映射,所以創(chuàng)建,切換都存在用戶態(tài)和內(nèi)核態(tài)的切換,開銷比較大,同時由于系統(tǒng)資源有限,不能支持創(chuàng)建大量的LWP,但是N:1可以很好的解決這些問題,一個內(nèi)核線程管理映射多個用戶線程,所以用戶線程切換的時候不會產(chǎn)生用戶態(tài)和內(nèi)核態(tài)的切換
N:M線程模型,主要解決了N:1線程模型如果一個線程阻塞,就會導(dǎo)致整個進(jìn)程被阻塞,N:M通過LWP與內(nèi)核線程鏈接,用戶態(tài)的線程數(shù)量和內(nèi)核態(tài)的LWP數(shù)量是N:M的關(guān)系
Java使用了1:1的線程模型,而Go協(xié)程實現(xiàn)了N:M的線程模型,所以Go語言并發(fā)行支持的更好,不需要上下文之間的切換(協(xié)程實現(xiàn)的本質(zhì)就是在程序總實現(xiàn)函數(shù)的調(diào)度)
JAVA虛擬機(jī)
Java8為什么使用元空間替代永久代(方法區(qū)的實現(xiàn))
- 為了融合HotSpot JVM與JRockit VM,因為JRockit沒有永久代
- 永久代經(jīng)常不夠用或發(fā)生內(nèi)存溢出,每次PermGen區(qū)FullGC的時候回收率偏低
類加載執(zhí)行過程

- 類編譯,把.java文件編譯成.class文件
- 類加載,當(dāng)類被創(chuàng)建實例或者被其他對象引用時,虛擬機(jī)在沒有加載過該類的情況下,會通過類加載將字節(jié)碼文件加載到內(nèi)存中,加載完成后class類文件常量池信息以及其他數(shù)據(jù)會被保存到JVM內(nèi)存的方法區(qū)中
- 類連接,驗證:驗證是否符合規(guī)范,準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,初始化值,如int常量初始化0(此時還沒有真正賦值),解析:將符號引用轉(zhuǎn)為直接引用,包括類和接口的全限定名,類引用,方法引用以及成員變量引用等
- 類初始化,執(zhí)行構(gòu)造器的<clinit>方法,為變量賦值
- 即時編譯(JIT),初始化完成后,類在調(diào)用執(zhí)行過程中,執(zhí)行引擎會把字節(jié)碼轉(zhuǎn)成機(jī)器嗎,然后操作系統(tǒng)才能執(zhí)行,字節(jié)碼轉(zhuǎn)機(jī)器碼的過程中虛擬機(jī)還存在一道編譯,那就是即時編譯,HotSpot虛擬機(jī)中內(nèi)置了兩個JIT,分別為C1和C2編譯器,這兩個編譯器過程是不一樣的,C1編譯器是一個簡單快速的編譯器,主要關(guān)注在局部性優(yōu)化(如GUI程序),C2編譯器是為長期運行的服務(wù)器程序做的性能調(diào)優(yōu)