問(wèn):你有沒(méi)有遇到過(guò)OutOfMemory問(wèn)題?你是怎么來(lái)處理這個(gè)問(wèn)題的?處理 過(guò)程中有哪些收獲?
permgen space、heap space 錯(cuò)誤。
常見(jiàn)的原因
(1)內(nèi)存加載的數(shù)據(jù)量太大:一次性從數(shù)據(jù)庫(kù)取太多數(shù)據(jù);
(2)集合類中有對(duì)對(duì)象的引用,使用后未清空,GC不能進(jìn)行回收;
(3)代碼中存在循環(huán)產(chǎn)生過(guò)多的重復(fù)對(duì)象;
(4)啟動(dòng)參數(shù)堆內(nèi)存值小。
參考文章:
http://outofmemory.cn/c/java-outOfMemoryError/
問(wèn):StackOverflow異常有沒(méi)有遇到過(guò)?一般你猜測(cè)會(huì)在什么情況下被觸發(fā)?如何指定幾個(gè)線程的堆棧?一般你們寫(xiě)多少?
棧內(nèi)存溢出,一般由棧內(nèi)存的局部變量過(guò)爆了,導(dǎo)致內(nèi)存溢出。出現(xiàn)在遞歸方法,參數(shù)個(gè)數(shù)過(guò)多,遞歸過(guò)深,遞歸沒(méi)有出口。
問(wèn):新生代轉(zhuǎn)移到老年代的觸發(fā)條件:
(1)長(zhǎng)期存活的對(duì)象
(2)大對(duì)象直接進(jìn)入老年代
(3)minor gc后,survivor仍然放不下
(4)動(dòng)態(tài)年齡判斷,大于等于某個(gè)年齡的對(duì)象超過(guò)了survivor空間一半 ,大于等于某個(gè)年齡的對(duì)象直接進(jìn)入老年代
問(wèn):java?GC是什么時(shí)候做的?
程序員不能具體控制時(shí)間,系統(tǒng)在不可預(yù)測(cè)的時(shí)間調(diào)用System.gc()函數(shù)的時(shí)候;當(dāng)然可以通過(guò)調(diào)優(yōu),用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制進(jìn)入oldObject的次數(shù),使得oldObject 存儲(chǔ)空間延遲達(dá)到full gc,從而使得計(jì)時(shí)器引發(fā)gc時(shí)間延遲OOM的時(shí)間延遲,以延長(zhǎng)對(duì)象生存期。
也就是GC的觸發(fā)條件,eden 滿了minor gc,升到老年代的對(duì)象大于老年代剩余空間full gc,或者小于時(shí)被HandlePromotionFailure參數(shù)強(qiáng)制full gc;gc與非gc時(shí)間耗時(shí)超過(guò)了GCTimeRatio的限制引發(fā)OOM,調(diào)優(yōu)諸如通過(guò)NewRatio控制新生代老年代比例,通過(guò) MaxTenuringThreshold控制進(jìn)入老年前生存次數(shù)等。
問(wèn):為什么需要自定義類加載器
1)加密:java代碼可以輕易的被反編譯,如果你需要對(duì)你的代碼進(jìn)行加密以防止反編譯,可以先將編譯后的代碼用加密算法加密,類加密后就不能再使用java自帶的類加載器了,這時(shí)候就需要自定義類加載器.
2)從非標(biāo)準(zhǔn)的來(lái)源加載代碼:字節(jié)碼是放在數(shù)據(jù)庫(kù),甚至是云端,就可以自定義類加載器,從指定來(lái)源加載類.
問(wèn):為什么要進(jìn)行垃圾回收
在C++中,對(duì)象所占的內(nèi)存在程序結(jié)束運(yùn)行之前一直被占用,在明確釋放之前不能分配給其它對(duì)象;而在Java中,當(dāng)沒(méi)有對(duì)象引用指向原先分配給某個(gè)對(duì)象 的內(nèi)存時(shí),該內(nèi)存便成為垃圾。 垃圾回收能自動(dòng)釋放內(nèi)存空間,減輕編程的負(fù)擔(dān),JVM的一個(gè)系統(tǒng)級(jí)線程會(huì)自動(dòng)釋放該內(nèi)存塊。垃圾回收意味著程序不再需要的對(duì)象是"無(wú)用信息",這些信息將被丟棄。當(dāng)一個(gè)對(duì) 象不再被引用的時(shí)候,內(nèi)存回收它占領(lǐng)的空間,以便空間被后來(lái)的新對(duì)象使用。事實(shí)上,除了釋放沒(méi)用的對(duì)象,垃圾回收也可以清除內(nèi)存記錄碎片。由于創(chuàng)建對(duì)象和垃圾回收器釋放丟棄對(duì)象所占的內(nèi)存空間,內(nèi)存會(huì)出現(xiàn)碎片。碎片是分配給對(duì)象的內(nèi)存塊之間的空閑內(nèi)存洞。碎片整理將所占用的堆內(nèi)存移到堆的一端,JVM將整理出的內(nèi)存分配給新的對(duì)象。
問(wèn):如何進(jìn)行垃圾回收
Java語(yǔ)言規(guī)范沒(méi)有明確地說(shuō)明JVM使用哪種垃圾回收算法,但是任何一種垃圾回收算法一般要做2件基本的事情:(1)發(fā)現(xiàn)無(wú)用信息對(duì)象;(2)回收被無(wú)用對(duì)象占用的內(nèi)存空間,使該空間可被程序再次使用。
大多數(shù)垃圾回收算法使用了根集(root set)這個(gè)概念;所謂根集就是正在執(zhí)行的Java程序可以訪問(wèn)的引用變量的集合(包括局部變量、參數(shù)、類變量),程序可以使用引用變量訪問(wèn)對(duì)象的屬性和 調(diào)用對(duì)象的方法。垃圾回收首先需要確定從根開(kāi)始哪些是可達(dá)的和哪些是不可達(dá)的,從根集可達(dá)的對(duì)象都是活動(dòng)對(duì)象,它們不能作為垃圾被回收,這也包括從根集間 接可達(dá)的對(duì)象。
問(wèn):觸發(fā)GC(Garbage Collector)的條件:
JVM進(jìn)行次GC的頻率很高,但因?yàn)檫@種GC占用時(shí)間極短,所以對(duì)系統(tǒng)產(chǎn)生的影響不大。更值得關(guān)注的是主GC的觸發(fā)條件,因?yàn)樗鼘?duì)系統(tǒng)影響很明顯。總的來(lái)說(shuō),有兩個(gè)條件會(huì)觸發(fā)主GC:
1)當(dāng)應(yīng)用程序空閑時(shí),即沒(méi)有應(yīng)用線程在運(yùn)行時(shí),GC會(huì)被調(diào)用。因?yàn)镚C在優(yōu)先級(jí)最低的線程中進(jìn)行,所以當(dāng)應(yīng)用忙時(shí),GC線程就不會(huì)被調(diào)用,但以下條件除外。
2)Java堆內(nèi)存不足時(shí),GC會(huì)被調(diào)用。 當(dāng)應(yīng)用線程在運(yùn)行,并在運(yùn)行過(guò)程中創(chuàng)建新對(duì)象,若這時(shí)內(nèi)存空間不足,JVM就會(huì)強(qiáng)制地調(diào)用GC線程,以便回收內(nèi)存用于新的分配。若GC一次之后仍不能滿足 內(nèi)存分配的要求,JVM會(huì)再進(jìn)行兩次GC作進(jìn)一步的嘗試,若仍無(wú)法滿足要求,則 JVM將報(bào)“out of memory”的錯(cuò)誤,Java應(yīng)用將停止。
問(wèn):減少GC開(kāi)銷的措施
不要顯示的調(diào)用System.gc
盡量減少臨時(shí)對(duì)象的使用
對(duì)象不用的時(shí)候最好顯示置空
盡量使用StringBuffer,不實(shí)用String累加字符串(String的特性有關(guān))
能使用基本數(shù)據(jù)類型就不要使用封裝類
盡量減少靜態(tài)對(duì)象變量的使用
問(wèn):你知道哪幾種垃圾收集器,各自的優(yōu)缺點(diǎn),重點(diǎn)講下cms,g1

問(wèn):Minor GC,F(xiàn)ull GC 觸發(fā)條件
Minor GC觸發(fā)條件:當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC。
Full GC觸發(fā)條件:
(1)調(diào)用System.gc時(shí),系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
(2)老年代空間不足
(3)方法去空間不足
(4)通過(guò)Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
(5)由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時(shí),對(duì)象大小大于To Space可用內(nèi)存,則把該對(duì)象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對(duì)象大小
問(wèn):Minor GC、Major GC和Full GC之間的區(qū)別
Minor GC指新生代GC,即發(fā)生在新生代(包括Eden區(qū)和Survivor區(qū))的垃圾回收操作,當(dāng)新生代無(wú)法為新生對(duì)象分配內(nèi)存空間的時(shí)候,會(huì)觸發(fā)Minor GC。因?yàn)樾律写蠖鄶?shù)對(duì)象的生命周期都很短,所以發(fā)生Minor GC的頻率很高,雖然它會(huì)觸發(fā)stop-the-world,但是它的回收速度很快。
Major GC清理Tenured區(qū),用于回收老年代,出現(xiàn)Major GC通常會(huì)出現(xiàn)至少一次Minor GC。
Full GC是針對(duì)整個(gè)新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局范圍的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,發(fā)生Full GC需要看使用了什么垃圾收集器組合,才能解釋是什么樣的垃圾回收
問(wèn):為什么要有Survivor區(qū)
如果沒(méi)有Survivor,Eden區(qū)每進(jìn)行一次Minor GC,存活的對(duì)象就會(huì)被送到老年代。老年代很快被填滿,觸發(fā)Major GC(因?yàn)镸ajor GC一般伴隨著Minor GC,也可以看做觸發(fā)了Full GC)。老年代的內(nèi)存空間遠(yuǎn)大于新生代,進(jìn)行一次Full GC消耗的時(shí)間比Minor GC長(zhǎng)得多。你也許會(huì)問(wèn),執(zhí)行時(shí)間長(zhǎng)有什么壞處?頻發(fā)的Full GC消耗的時(shí)間是非常可觀的,這一點(diǎn)會(huì)影響大型程序的執(zhí)行和響應(yīng)速度,更不要說(shuō)某些連接會(huì)因?yàn)槌瑫r(shí)發(fā)生連接錯(cuò)誤了。
好,那我們來(lái)想想在沒(méi)有Survivor的情況下,有沒(méi)有什么解決辦法,可以避免上述情況:
方案優(yōu)點(diǎn)缺點(diǎn)
增加老年代空間更多存活對(duì)象才能填滿老年代。降低Full GC頻率隨著老年代空間加大,一旦發(fā)生Full GC,執(zhí)行所需要的時(shí)間更長(zhǎng)
減少老年代空間Full GC所需時(shí)間減少老年代很快被存活對(duì)象填滿,F(xiàn)ull GC頻率增加
顯而易見(jiàn),沒(méi)有Survivor的話,上述兩種解決方案都不能從根本上解決問(wèn)題。
我們可以得到第一條結(jié)論:Survivor的存在意義,就是減少被送到老年代的對(duì)象,進(jìn)而減少Full GC的發(fā)生,Survivor的預(yù)篩選保證,只有經(jīng)歷16次Minor GC還能在新生代中存活的對(duì)象,才會(huì)被送到老年代。
問(wèn):為什么要設(shè)置兩個(gè)Survivor區(qū)
設(shè)置兩個(gè)Survivor區(qū)最大的好處就是解決了碎片化,下面我們來(lái)分析一下。
為什么一個(gè)Survivor區(qū)不行?第一部分中,我們知道了必須設(shè)置Survivor區(qū)。假設(shè)現(xiàn)在只有一個(gè)survivor區(qū),我們來(lái)模擬一下流程:剛剛新建的對(duì)象在Eden中,一旦Eden滿了,觸發(fā)一次Minor GC,Eden中的存活對(duì)象就會(huì)被移動(dòng)到Survivor區(qū)。這樣繼續(xù)循環(huán)下去,下一次Eden滿了的時(shí)候,問(wèn)題來(lái)了,此時(shí)進(jìn)行Minor GC,Eden和Survivor各有一些存活對(duì)象,如果此時(shí)把Eden區(qū)的存活對(duì)象硬放到Survivor區(qū),很明顯這兩部分對(duì)象所占有的內(nèi)存是不連續(xù)的,也就導(dǎo)致了內(nèi)存碎片化。我繪制了一幅圖來(lái)表明這個(gè)過(guò)程。其中色塊代表對(duì)象,白色框分別代表Eden區(qū)(大)和Survivor區(qū)(?。?。Eden區(qū)理所當(dāng)然大一些,否則新建對(duì)象很快就導(dǎo)致Eden區(qū)滿,進(jìn)而觸發(fā)Minor GC,有悖于初衷。

碎片化帶來(lái)的風(fēng)險(xiǎn)是極大的,嚴(yán)重影響JAVA程序的性能。堆空間被散布的對(duì)象占據(jù)不連續(xù)的內(nèi)存,最直接的結(jié)果就是,堆中沒(méi)有足夠大的連續(xù)內(nèi)存空間,接下去如果程序需要給一個(gè)內(nèi)存需求很大的對(duì)象分配內(nèi)存。。。畫(huà)面太美不敢看。。。這就好比我們爬山的時(shí)候,背包里所有東西緊挨著放,最后就可能省出一塊完整的空間放相機(jī)。如果每件行李之間隔一點(diǎn)空隙亂放,很可能最后就要一路把相機(jī)掛在脖子上了。
那么,順理成章的,應(yīng)該建立兩塊Survivor區(qū),剛剛新建的對(duì)象在Eden中,經(jīng)歷一次Minor GC,Eden中的存活對(duì)象就會(huì)被移動(dòng)到第一塊survivor space S0,Eden被清空;等Eden區(qū)再滿了,就再觸發(fā)一次Minor GC,Eden和S0中的存活對(duì)象又會(huì)被復(fù)制送入第二塊survivor space S1(這個(gè)過(guò)程非常重要,因?yàn)檫@種復(fù)制算法保證了S1中來(lái)自S0和Eden兩部分的存活對(duì)象占用連續(xù)的內(nèi)存空間,避免了碎片化的發(fā)生)。S0和Eden被清空,然后下一輪S0與S1交換角色,如此循環(huán)往復(fù)。如果對(duì)象的復(fù)制次數(shù)達(dá)到16次,該對(duì)象就會(huì)被送到老年代中
問(wèn):minor gc、major gc、full gc使用了哪種垃圾回收算法
先來(lái)講一下這幾個(gè)算法的特性吧
(1)復(fù)制:1.將內(nèi)存分為兩塊,只用一塊,另一塊用來(lái)講這一塊的對(duì)象復(fù)制;2.不會(huì)產(chǎn)生內(nèi)存碎片
(2)標(biāo)記-清除:1.從引用根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用的對(duì)象;2.遍歷整個(gè)堆,把未標(biāo)記的對(duì)象清除。此算法需要暫停整個(gè)應(yīng)用,同時(shí),會(huì)產(chǎn)生內(nèi)存碎片。
(3)標(biāo)記-整理:1.從根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用對(duì)象;2.遍歷整個(gè)堆,把清除未標(biāo)記對(duì)象并且把存活對(duì)象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標(biāo)記-清除”的碎片問(wèn)題,同時(shí)也避免了“復(fù)制”算法的空間問(wèn)題。
說(shuō)的有點(diǎn)不夠形象,還是用對(duì)比的表格來(lái)展示吧
(4)新生代的Minor GC觸發(fā)條件:Copying算法就是掃描出存活的對(duì)象,并復(fù)制到一塊新的完全未使用的空間中,對(duì)應(yīng)于新生代,就是在Eden和FromSpace或ToSpace之間copy。所有的Minor GC都會(huì)觸發(fā)全世界的暫停(stop-the-world)
(5)老年代的GC(Major GC/Full GC):老年代與新生代不同,老年代對(duì)象存活的時(shí)間比較長(zhǎng)、比較穩(wěn)定,因此采用標(biāo)記(Mark)算法來(lái)進(jìn)行回收,所謂標(biāo)記就是掃描出存活的對(duì)象,然后再進(jìn)行回收未被標(biāo)記的對(duì)象,回收后對(duì)用空出的空間要么進(jìn)行合并、要么標(biāo)記出來(lái)便于下次進(jìn)行分配,總之目的就是要減少內(nèi)存碎片帶來(lái)的效率損耗。
問(wèn):JVM性能調(diào)優(yōu)監(jiān)控工具jps、jstack、jstat、jmap、jinfo使用
jps
查看所有的jvm進(jìn)程,包括進(jìn)程ID,進(jìn)程啟動(dòng)的路徑等等。
jstack
觀察jvm中當(dāng)前所有線程的運(yùn)行情況和線程當(dāng)前狀態(tài)。
系統(tǒng)崩潰了?如果java程序崩潰生成core文件,jstack工具可以用來(lái)獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問(wèn)題。
系統(tǒng)hung住了?jstack工具還可以附屬到正在運(yùn)行的java程序中,看到當(dāng)時(shí)運(yùn)行的java程序的java stack和native stack的信息, 如果現(xiàn)在運(yùn)行的java程序呈現(xiàn)hung的狀態(tài),jstack是非常有用的。
jstat
jstat利用JVM內(nèi)建的指令對(duì)Java應(yīng)用程序的資源和性能進(jìn)行實(shí)時(shí)的命令行的監(jiān)控,包括了對(duì)進(jìn)程的classloader,compiler,gc情況;
特別的,一個(gè)極強(qiáng)的監(jiān)視內(nèi)存的工具,可以用來(lái)監(jiān)視VM內(nèi)存內(nèi)的各種堆和非堆的大小及其內(nèi)存使用量,以及加載類的數(shù)量。
jmap
監(jiān)視進(jìn)程運(yùn)行中的jvm物理內(nèi)存的占用情況,該進(jìn)程內(nèi)存內(nèi),所有對(duì)象的情況,例如產(chǎn)生了哪些對(duì)象,對(duì)象數(shù)量;
系統(tǒng)崩潰了?jmap可以從core文件或進(jìn)程中獲得內(nèi)存的具體匹配情況,包括Heap size, Perm size等等
jinfo
觀察進(jìn)程運(yùn)行環(huán)境參數(shù),包括Java System屬性和JVM命令行參數(shù)
系統(tǒng)崩潰了?jinfo可以從core文件里面知道崩潰的Java應(yīng)用程序的配置信息。
問(wèn):堆內(nèi)存設(shè)置的參數(shù)是什么?
-Xmx設(shè)置堆的最大空間大小
-Xms設(shè)置堆的最小空間大小
問(wèn):JDK 1.8之后Perm Space有哪些變動(dòng)? MetaSpace大小默認(rèn)是有限的么? 還是你們會(huì)通過(guò)什么方式來(lái)指定大小?
JDK 1.8后用元空間替代了 Perm Space;字符串常量存放到堆內(nèi)存中。
MetaSpace大小默認(rèn)沒(méi)有限制,一般根據(jù)系統(tǒng)內(nèi)存的大小。JVM會(huì)動(dòng)態(tài)改變此值。
-XX:MetaspaceSize:分配給類元數(shù)據(jù)空間(以字節(jié)計(jì))的初始大?。∣racle邏輯存儲(chǔ)上的初始高水位,the initial high-water-mark)。此值為估計(jì)值,MetaspaceSize的值設(shè)置的過(guò)大會(huì)延長(zhǎng)垃圾回收時(shí)間。垃圾回收過(guò)后,引起下一次垃圾回收的類元數(shù)據(jù)空間的大小可能會(huì)變大。
-XX:MaxMetaspaceSize:分配給類元數(shù)據(jù)空間的最大值,超過(guò)此值就會(huì)觸發(fā)Full GC,此值默認(rèn)沒(méi)有限制,但應(yīng)取決于系統(tǒng)內(nèi)存的大小。JVM會(huì)動(dòng)態(tài)地改變此值。