一、線程部分
1、Java 中引用類(lèi)型都有哪些?
(1)強(qiáng)引用。在虛擬機(jī)內(nèi)存不足的情況下,也不會(huì)回收,如果我們把(強(qiáng)引用)對(duì)象置為null,會(huì)大大增加了垃圾回收的頻率。幾乎只要我們給出建議(GC),JVM就會(huì)回收。
Object o = new Object();
Object o1 = o;
(2) 軟引用。如果不顯式的置為null,跟強(qiáng)引用差不多。垃圾回收不會(huì)執(zhí)行。只會(huì)等到內(nèi)存不足的時(shí)候才會(huì)調(diào)用。
(3)弱引用。就算不置為null,發(fā)生垃圾回收時(shí),會(huì)立即被回收。
(4)虛引用。相當(dāng)于null
2、談一談java 線程模型
線程的實(shí)現(xiàn)可以分為兩類(lèi):用戶級(jí)線程(User-LevelThread, ULT)和內(nèi)核級(jí)線程(Kemel-LevelThread, KLT)。用戶線程由用戶代碼支持,內(nèi)核線程由操作系統(tǒng)內(nèi)核支持。
(1)一對(duì)一模型
每個(gè)用戶線程對(duì)應(yīng)一個(gè)內(nèi)核線程。由于每個(gè)用戶線程對(duì)應(yīng)自己的內(nèi)核線程,所以他們互不影響,當(dāng)一個(gè)線程阻塞,也允許另外的線程繼續(xù)執(zhí)行,這是此模型的優(yōu)點(diǎn)。但也存在一個(gè)缺陷,由于一對(duì)一的關(guān)系,有多少用戶線程就代表有多少內(nèi)核線程,由于內(nèi)核線程的開(kāi)銷(xiāo)比較大,一般操作系統(tǒng)會(huì)都有內(nèi)核線程數(shù)量的限制,所以也限制了用戶線程的數(shù)量。
(2)多對(duì)一模型
一個(gè)內(nèi)核線程實(shí)現(xiàn)若干個(gè)用戶線程的并發(fā)功能,線程的管理在用戶空間中進(jìn)行,一般不需要切換到內(nèi)核態(tài),效率較高,而且比起一對(duì)一模型,支持的用戶線程數(shù)量更多。但此模型有個(gè)致命的弱點(diǎn)是如果一個(gè)線程執(zhí)行了阻塞調(diào)用,所有的線程都將阻塞,并且任意時(shí)刻都只能有一個(gè)線程訪問(wèn)內(nèi)核。另外,對(duì)線程的所有操作,都將由用戶自己處理。一般除了不支持多線程操作的系統(tǒng)被迫使用此模型外,在多線程操作系統(tǒng)中不會(huì)使用該模型。
(3)多對(duì)多模型
多對(duì)多模型的提出是為了解決以上兩種模型的缺點(diǎn),多個(gè)用戶線程與多個(gè)內(nèi)核線程映射形成了多路復(fù)用。前面一對(duì)一模型存在受內(nèi)核線程數(shù)量限制的問(wèn)題,多對(duì)一模型解決了這個(gè)問(wèn)題,但它存在一個(gè)線程阻塞所有線程都阻塞的風(fēng)險(xiǎn),而且一個(gè)內(nèi)核線程只能調(diào)用一個(gè)用戶線程導(dǎo)致并發(fā)性不強(qiáng)。看看多對(duì)多模型如何解決這些問(wèn)題,由于多對(duì)一是多對(duì)多的子集,所以多對(duì)多具備多對(duì)一的優(yōu)點(diǎn),線程數(shù)不受限制。除此之外,多個(gè)內(nèi)核線程可處理多個(gè)用戶線程,當(dāng)某個(gè)線程阻塞時(shí),將可以調(diào)度另外一個(gè)線程執(zhí)行,這從另一方面看也是增強(qiáng)了并發(fā)性。
二、JVM
1、JVM內(nèi)存結(jié)構(gòu)說(shuō)一下
(一)運(yùn)行時(shí)數(shù)據(jù)區(qū):JVM在執(zhí)行Java程序過(guò)程中,把所管理的內(nèi)存劃分為不同的區(qū)域。有
(1)程序計(jì)數(shù)器。
記錄當(dāng)前線程正在執(zhí)行字節(jié)碼指令的地址,因?yàn)镃PU的時(shí)間片輪轉(zhuǎn)機(jī)制,當(dāng)CPU切換到其他線程執(zhí)行后再切回當(dāng)前線程根據(jù)程序計(jì)數(shù)器的記錄繼續(xù)執(zhí)行。它是線程私有的,也是唯一一塊不會(huì)發(fā)生OOM的區(qū)域。
(2)虛擬機(jī)棧
棧的特點(diǎn)是先進(jìn)后出(FILO),存儲(chǔ)當(dāng)前線程運(yùn)行方法所需的數(shù)據(jù)、指令、地址。它是線程私有的。里邊有若干棧幀(每個(gè)java方法對(duì)應(yīng)一個(gè)棧幀),棧幀有局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、完成出口(返回地址)組成。局部變量表中存放8大基本數(shù)據(jù)類(lèi)型和對(duì)象的引用;操作數(shù)棧存放方法的執(zhí)行和操作;動(dòng)態(tài)鏈接:是由于多態(tài),靜態(tài)分派、動(dòng)態(tài)分派;
完成出口(返回地址):
把符號(hào)引用轉(zhuǎn)換成具體引用
正常返回,會(huì)調(diào)用程序計(jì)數(shù)器中的地址進(jìn)行返回;
異常返回,會(huì)根據(jù)異常處理表來(lái)確定。
在Java中的解析執(zhí)行是基于棧的,基于棧幀的執(zhí)行主要說(shuō)的是操作數(shù)棧。c語(yǔ)言是基于寄存器的。
Java基于棧:兼容性好,效率低;基于寄存器:寄存器是硬件,所以快一點(diǎn),但移植性差。
可以再講一下方法執(zhí)行的流程。
(3)本地方法棧
保存的是native方法的信息。它是線程私有的。
當(dāng) JVM 創(chuàng)建的線程調(diào)用native方法時(shí),JVM不會(huì)在虛擬機(jī)棧上創(chuàng)建棧幀,只是簡(jiǎn)單的動(dòng)態(tài)鏈接并直接調(diào)用native方法。程序計(jì)數(shù)器也不會(huì)記錄。
(4)方法區(qū)
存放著class類(lèi)信息、靜態(tài)變量、常量、即時(shí)編譯期編譯后的代碼。線程間共享的。
實(shí)現(xiàn)方式 JDK 1.8 以后是元空間,JDK1.7是永久代。元空間可以使用硬件內(nèi)存(堆以外的內(nèi)存),方便擴(kuò)展;永久代是受制于堆內(nèi)存。
(5)堆
存放著幾乎所有的對(duì)象、數(shù)組。線程間共享的。
方法區(qū)和堆為什么劃分為兩個(gè)區(qū)?
堆中存放著幾乎所有的對(duì)象、數(shù)組,這些是頻繁回收的;方法區(qū)中存放的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯期編譯后的代碼回收的難度是相當(dāng)大的。動(dòng)靜分離的思想,便于垃圾回收的高效。
(二)直接內(nèi)存(堆外內(nèi)存)
不是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。
如果使用NIO會(huì)頻繁的使用該區(qū)域,在Java堆內(nèi)使用 DirectByteBuffer 對(duì)象可以直接引用并操作。
不受堆內(nèi)存大小的限制,但受本機(jī)內(nèi)存大小的限制??梢酝ㄟ^(guò)MaxDirectMemorySize來(lái)設(shè)置,默認(rèn)與堆內(nèi)存最大值一樣,所以也會(huì)出現(xiàn)OOM異常。
2、什么情況下內(nèi)存棧溢出?
虛擬機(jī)棧是有深度限制的,如果加載的方法過(guò)多會(huì)發(fā)生 stackoverflowerror 造成溢出;棧內(nèi)存空間不足也會(huì)發(fā)生OOM。
3、描述一下new 一個(gè)對(duì)象的過(guò)程?
首先,<u>檢查加載</u>,若類(lèi)信息沒(méi)有加載進(jìn)方法區(qū),進(jìn)行 <u>類(lèi)加載</u>。若已加載,進(jìn)行<u>分配內(nèi)存</u>,如果內(nèi)存是規(guī)整的,使用 指針碰撞 進(jìn)行內(nèi)存分配,若內(nèi)存不規(guī)整,使用 空閑列表 進(jìn)行內(nèi)存分配。同時(shí)要注意并發(fā)安全問(wèn)題,CAS加失敗重試、本地線程分配緩存(TLAB)。本地線程分配緩存(TLAB)是線程的一塊私有內(nèi)存,在堆的Eden區(qū)中開(kāi)辟一塊區(qū)域來(lái)存放對(duì)象。對(duì)象占據(jù)的內(nèi)存一定是連續(xù)的。之后是<u>內(nèi)存空間初始化</u>,也就是把數(shù)據(jù)初始值置為零值,比如int類(lèi)型的 a = 0; boolean類(lèi)型的 b = false。然后進(jìn)行<u>設(shè)置</u>,設(shè)置對(duì)象頭等信息。最后是<u>對(duì)象初始化</u>,調(diào)用對(duì)象的構(gòu)造函數(shù)等。
TLAB是線程的一塊私有內(nèi)存,它是虛擬機(jī)在堆內(nèi)存的eden劃分出來(lái)的,如果設(shè)置了虛擬機(jī)參數(shù) -XX:UseTLAB,在線程初始化時(shí),同時(shí)也會(huì)申請(qǐng)一塊指定大小的內(nèi)存, 只給當(dāng)前線程使用, 這樣每個(gè)線程都單獨(dú)擁有一個(gè)Buffer,如果需要分配內(nèi)存,就在自己的Buffer上分配,這樣就不存在競(jìng)爭(zhēng)的情況,可以大大提升分配效率,當(dāng)Buffer容量不夠的時(shí)候,再重新從Eden區(qū)域申請(qǐng)一塊繼續(xù)使用。
4、Java對(duì)象會(huì)不會(huì)分配在棧中?
會(huì)分配在棧中。滿足逃逸分析的對(duì)象會(huì)直接分配在棧中。
逃逸分析:方法逃逸、線程逃逸
對(duì)象分配的原則:
- 對(duì)象優(yōu)先在Eden區(qū)分配
- 空間分配擔(dān)保
- 大對(duì)象直接進(jìn)入老年代
- 長(zhǎng)期存活的對(duì)象進(jìn)入老年代
- 動(dòng)態(tài)判斷對(duì)象的年齡
堆中的優(yōu)化技術(shù): 本地線程分配緩沖(TLAB)是線程的一塊私有內(nèi)存.
new出一個(gè)對(duì)象,首先判斷是否滿足棧中分配的條件(逃逸分析),若滿足直接在棧中分配;若不滿足,繼續(xù)判斷是否可以使用 本地線程分配緩沖,若可以在堆的Eden區(qū)單獨(dú)開(kāi)辟出TLAB區(qū)存放,則存放在TLAB區(qū),若不行,則繼續(xù)判斷是否是大對(duì)象,如果是大對(duì)象,直接在老年代分配內(nèi)存,若不是,分配在Eden區(qū)。
5、判斷一個(gè)對(duì)象是否被回收,有些算法,實(shí)際虛擬機(jī)使用的最多的是什么?
(1)引用計(jì)數(shù)法
python中使用的是這種。有引用,計(jì)數(shù)+1,不在引用,計(jì)數(shù) -1,當(dāng)計(jì)數(shù)=0,就回收。存在一個(gè)問(wèn)題,對(duì)象間的相互引用問(wèn)題,需要額外的補(bǔ)償機(jī)制回收相互引用的對(duì)象。
(2)可達(dá)性分析算法(根可達(dá))--- 實(shí)際虛擬機(jī)使用的最多
可以成為 GC Roots 的對(duì)象有:靜態(tài)變量、線程棧變量(虛擬機(jī)棧中的局部變量中的變量)、常量池、JNI指針,還有class、異常NullPointException等、類(lèi)加載器、加鎖的synchronized對(duì)象、jmxBean、臨時(shí)性的等。
6、GC算法有哪些?它們的特點(diǎn)是?
(1)復(fù)制算法,把使用的對(duì)象從一塊空間復(fù)制到另一塊空間。特點(diǎn):實(shí)現(xiàn)簡(jiǎn)單、運(yùn)行高效;內(nèi)存復(fù)制,沒(méi)有內(nèi)存碎片;空間利用率只有一半。
(2)標(biāo)記清除算法。特點(diǎn):執(zhí)行效率不穩(wěn)定,因?yàn)樾枰厥盏膶?duì)象多少不確定;內(nèi)存碎片會(huì)導(dǎo)致提前GC。
(3)標(biāo)記整理算法。特點(diǎn):對(duì)象移動(dòng);引用更新;用戶線程暫停;沒(méi)有內(nèi)存碎片。
7、JVM中一次完成的GC流程是怎樣的?對(duì)象如何晉級(jí)老年代?
8、final、finally、finalize的區(qū)別?
final:
修飾類(lèi),表示類(lèi)不可擴(kuò)展。
修飾方法,一是不可修改,二是效率。
修飾變量,只能賦值一次。
finally:
try catch finally 不管有沒(méi)有異常都會(huì)執(zhí)行,釋放資源
finalize:
Object中的方法
對(duì)象垃圾回收可以拯救一下對(duì)象,不可靠,一般不用
9、String s = new String("abc"); 創(chuàng)建了幾個(gè)對(duì)象?
2個(gè)。
首先,在編譯文件時(shí),"abc"常量字符串會(huì)加入到常量結(jié)構(gòu)中,在類(lèi)加載時(shí),"abc"會(huì)在常量池中創(chuàng)建;
其次,調(diào)用new 時(shí), jvm 命令將調(diào)用String的構(gòu)造函數(shù),同時(shí)引用常量池中的"abc"字符串,在堆中創(chuàng)建一個(gè)對(duì)象;
最后,s引用這個(gè)對(duì)象。
常量池(方法區(qū))分為靜態(tài)常量池、運(yùn)行時(shí)常量池。
靜態(tài)常量池中,存放字面量、符號(hào)引用、類(lèi)信息、方法的信息等
運(yùn)行時(shí)常量池中,存放 類(lèi)加載,加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)、轉(zhuǎn)換成實(shí)體類(lèi),把符號(hào)引用變成直接引用(hash值)