最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

面試必殺題:當(dāng)發(fā)生OOM時(shí),進(jìn)程還能處理請(qǐng)求嗎

2023-07-16 22:52 作者:火星上的彩虹美不美  | 我要投稿



Java 的優(yōu)勢(shì)有什么

面試官一上來,直接進(jìn)入主題:你覺得在內(nèi)存管理上,Java 有什么優(yōu)勢(shì)?

我:小菜一碟。相比于 C 語言的手動(dòng)釋放內(nèi)存,Java 的優(yōu)勢(shì)在于內(nèi)存的自動(dòng)管理,依賴于垃圾回收機(jī)制,它能自動(dòng)識(shí)別和清理不再使用的內(nèi)存資源,消除了手動(dòng)釋放內(nèi)存的繁瑣過程,大大簡(jiǎn)化了開發(fā)人員的工作量。

什么是 OOM

面試官:那你知道什么是 OOM 嗎?

我:這個(gè)我在線上也碰到過好多次了,Java 的 OOM 通常指的是內(nèi)存溢出(Out of Memory)異常。在 Java 應(yīng)用程序中,每個(gè)對(duì)象都需要在內(nèi)存中分配一定的空間。當(dāng)應(yīng)用程序需要分配更多內(nèi)存空間來創(chuàng)建對(duì)象時(shí),但可分配內(nèi)存卻不足以滿足需求時(shí),就會(huì)拋出 OOM 異常。

什么情況會(huì)產(chǎn)生 OOM

面試官:好小子,線上的事故代碼不會(huì)都是你寫的吧,那你說說有什么情況會(huì)導(dǎo)致 OOM?

我:比如說經(jīng)常發(fā)生的堆內(nèi)存溢出, 在創(chuàng)建對(duì)象時(shí),絕大多數(shù)情況占用的都是 JVM 的堆內(nèi)存,當(dāng)堆內(nèi)存不足以分配時(shí),則會(huì)拋出OOM異常。

java.lang.OutOfMemoryError: Java heap space

堆內(nèi)存溢出的具體場(chǎng)景

面試官:你這個(gè)太抽象了,能不能具體點(diǎn)?

我:emm,常見導(dǎo)致內(nèi)存溢出的情況有這么幾種:

  1. 對(duì)象生命周期過長(zhǎng):如果某個(gè)對(duì)象的生命周期過長(zhǎng),而且該對(duì)象占用的內(nèi)存很大,那么在不斷創(chuàng)建新對(duì)象的過程中,堆內(nèi)存會(huì)被耗盡,從而導(dǎo)致內(nèi)存溢出。這種情況一般出現(xiàn)在用集合當(dāng)緩存,卻忽略了緩存的淘汰機(jī)制。

  2. 無限遞歸:遞歸調(diào)用中缺少退出條件或遞歸深度過大,會(huì)導(dǎo)致空間耗盡,引發(fā)溢出錯(cuò)誤。往往在測(cè)試環(huán)境就會(huì)發(fā)現(xiàn)該問題,不會(huì)暴露在生產(chǎn)環(huán)境

  3. 大數(shù)據(jù)集合:在處理大量數(shù)據(jù)時(shí),如果沒有正確管理內(nèi)存,例如加載過大的文件、查詢結(jié)果集過大等,會(huì)導(dǎo)致內(nèi)存溢出。

  4. JVM配置不當(dāng):如果JVM的內(nèi)存參數(shù)配置不合理,例如堆內(nèi)存設(shè)置過小,無法滿足應(yīng)用程序的內(nèi)存需求,也會(huì)導(dǎo)致內(nèi)存溢出。

下面的這個(gè)例子就是無限循環(huán)導(dǎo)致內(nèi)存溢出。

csharp復(fù)制代碼List<Integer> list = new ArrayList<>(); while (true) { ?? ?list.add(1); }

什么是內(nèi)存泄漏

面試官:你知道在我們的程序里,有可能會(huì)出現(xiàn)內(nèi)存泄漏,你對(duì)它了解嗎?

我:對(duì)的,和內(nèi)存溢出的情況不同,還有一種特殊場(chǎng)景,叫做內(nèi)存泄漏(本質(zhì)上還是內(nèi)存溢出,只不過是錯(cuò)誤的內(nèi)存溢出),指的是程序在運(yùn)行過程中無法釋放不再使用的內(nèi)存,導(dǎo)致內(nèi)存占用不斷增加,最終耗盡系統(tǒng)資源,這種情況就被稱為內(nèi)存泄漏。

這一次,我提前搶答了, 常見導(dǎo)致內(nèi)存泄漏的情況包括:

  1. 對(duì)象的引用未被正確釋放:如果在使用完一個(gè)對(duì)象后,忘記將其引用置為 null 或者從數(shù)據(jù)結(jié)構(gòu)中移除,那么該對(duì)象將無法被垃圾回收,導(dǎo)致內(nèi)存泄漏。比如 ThreadLocal。

  2. 長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用:如果一個(gè)長(zhǎng)生命周期的對(duì)象持有了一個(gè)短生命周期對(duì)象的引用,即使短生命周期對(duì)象不再使用,由于長(zhǎng)生命周期對(duì)象的引用仍然存在,短生命周期對(duì)象也無法被垃圾回收,從而造成內(nèi)存泄漏。

  3. 過度使用第三方庫:某些第三方庫可能存在內(nèi)存泄漏或者資源未正確釋放的問題,如果使用不當(dāng)或者沒有適當(dāng)?shù)毓芾磉@些庫,可能會(huì)導(dǎo)致內(nèi)存溢出。

  4. 集合類使用不當(dāng):在使用集合類時(shí),如果沒有正確地清理元素,當(dāng)集合不再需要時(shí),集合中的對(duì)象也不會(huì)被釋放,導(dǎo)致內(nèi)存泄漏。

  5. 資源未正確釋放:如果程序使用了諸如文件、數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接等資源,在不再需要這些資源時(shí)沒有正確釋放,會(huì)導(dǎo)致資源泄漏,最終導(dǎo)致內(nèi)存泄漏。

下面的這個(gè)例子就是長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用, 導(dǎo)致內(nèi)存泄漏。

csharp復(fù)制代碼List<Integer> list2 = new ArrayList<>(); ?@GetMapping("/headOOM2") public String headOOM2() throws InterruptedException { ?? ?while (true) { ?? ? ? ?list2.add(1); ?? ?} }


還有其他情況嗎

面試官:你說的都是堆的內(nèi)存溢出,還有其他情況嗎?

遞歸調(diào)用導(dǎo)致棧溢出

當(dāng)遞歸調(diào)用的層級(jí)過深,??臻g無法容納更多的方法調(diào)用信息時(shí),會(huì)引發(fā) StackOverflowError 異常,這也是一種 OOM 異常。例如,以下示例中的無限遞歸調(diào)用會(huì)導(dǎo)致棧溢出。

typescript復(fù)制代碼public class OOMExample { ?? ?public static void recursiveMethod() { ?? ? ? ?recursiveMethod(); ?? ?} ? ?? ?public static void main(String[] args) { ?? ? ? ?recursiveMethod(); ?? ?} }

元空間(Metaspace)耗盡

元空間是 Java 8 及以后版本中用來存儲(chǔ)類元數(shù)據(jù)的區(qū)域。它取代了早期版本中的永久代(PermGen)。元空間主要用于存儲(chǔ)類的結(jié)構(gòu)信息、方法信息、靜態(tài)變量以及編譯后的代碼等。

當(dāng)程序加載和定義大量類、動(dòng)態(tài)生成類、使用反射頻繁操作類等情況下,可能會(huì)導(dǎo)致元空間耗盡。常見導(dǎo)致元空間耗盡的情況包括:

  1. 類加載過多:如果應(yīng)用程序動(dòng)態(tài)加載大量的類或者使用動(dòng)態(tài)生成類的方式,會(huì)導(dǎo)致元空間的使用量增加。如果無法及時(shí)卸載這些類,元空間可能會(huì)耗盡。

  2. 字符串常量過多:Java中的字符串常量會(huì)被存儲(chǔ)在元空間中。如果應(yīng)用程序中使用了大量的字符串常量,尤其是較長(zhǎng)的字符串,可能會(huì)導(dǎo)致元空間的耗盡。

  3. 頻繁使用反射:反射操作需要大量的元數(shù)據(jù)信息,會(huì)占用較多的元空間。如果應(yīng)用程序頻繁使用反射進(jìn)行類的操作,可能會(huì)導(dǎo)致元空間耗盡。

  4. 大量動(dòng)態(tài)代理:動(dòng)態(tài)代理是一種使用反射創(chuàng)建代理對(duì)象的技術(shù)。如果應(yīng)用程序大量使用動(dòng)態(tài)代理,將會(huì)生成大量的代理類,占用較多的元空間。

  5. 未正確限制元空間大小:默認(rèn)情況下,元空間的大小是不受限制的,它會(huì)根據(jù)需要?jiǎng)討B(tài)擴(kuò)展。如果沒有正確設(shè)置元空間的大小限制,或者限制過小,可能會(huì)導(dǎo)致元空間耗盡。

下面的這個(gè)例子就是類加載過多導(dǎo)致的內(nèi)存泄漏。

typescript復(fù)制代碼public class OOMExample { ?? ?public static void main(String[] args) { ?? ? ? ?while (true) { ?? ? ? ? ? ?ClassLoader classLoader = new CustomClassLoader(); ?? ? ? ? ? ?classLoader.loadClass("com.example.LargeClass"); ?? ? ? ?} ?? ?} }

終極問題

面試官滿意的點(diǎn)了點(diǎn)頭,小伙子你知道的還挺多,那我再問你一個(gè)問題哈:”當(dāng) Java 線程在處理請(qǐng)求時(shí),拋出了 OOM 異常,整個(gè)進(jìn)程還能處理請(qǐng)求嗎?

當(dāng)我正準(zhǔn)備脫口而出的時(shí)候,面試官:“這個(gè)問題考察的內(nèi)容還是挺多的,不是簡(jiǎn)單的是與否的問題。我建議你先整理一下思路?!?/p>

看到面試官的眼神,我就知道這道題有貓膩。思考了一會(huì),我給出了答案?!拔疫€是認(rèn)為OOM 并不會(huì)導(dǎo)致整個(gè)進(jìn)程掛掉”

面試官:你是怎么理解的,OOM 是不是意味著內(nèi)存不夠了。既然內(nèi)存不夠了,進(jìn)程還能處理請(qǐng)求嗎?

我:內(nèi)存不夠了還可以通過垃圾回收釋放內(nèi)存。

面試官:難道 OOM 不就是因?yàn)?GC 后,發(fā)現(xiàn)內(nèi)存不足才會(huì)拋出的異常,這時(shí)候是不是可以理解為 GC 不了了。所以是:內(nèi)存不夠->GC后還不夠-> OOM 這個(gè)流程。

我:此處經(jīng)典國(guó)罵,當(dāng)然我只能在內(nèi)心想想。

這么一套組合拳下來,我徹底懵了。結(jié)果不出意外的掛了,面試官最后送我下樓的時(shí)候,仿佛在和我說:”我也不想這樣,只能怪 HC 太少“

實(shí)戰(zhàn)

回到家,我馬上去進(jìn)行了代碼實(shí)戰(zhàn),用來測(cè)試 OOM。

環(huán)境是:OpenJdk 11 -Xms100m -Xmx100m -XX:+PrintGCDetails

堆內(nèi)存溢出

  1. 首先我們創(chuàng)建一個(gè)方法,調(diào)用它,每隔一秒不停的循環(huán)打印控制臺(tái)信息,它的主要作用是模擬其他線程處理請(qǐng)求

csharp復(fù)制代碼@GetMapping("/writeInfo") public String writeInfo() throws InterruptedException { ?? ?while (true) { ?? ? ? ?Thread.sleep(1000); ?? ? ? ?System.out.println("正在輸出信息"); ?? ?} }

  1. 接著再創(chuàng)建一個(gè)死循環(huán)往 List 中放入對(duì)象的方法,它的主要作用是模擬導(dǎo)致OOM的那個(gè)線程

csharp復(fù)制代碼@GetMapping("/headOOM") public String headOOM() throws InterruptedException { ?? ?List<Integer> list = new ArrayList<>(); ?? ?while (true) { ?? ? ? ?list.add(1); ?? ?} }

  1. 最終結(jié)果是headOOM拋出了 OOM 異常,但是控制臺(tái)還在不停的打印。【這邊截圖太大了,就不貼出來了】

  1. 這就是答案嗎?其實(shí)不是,在第一步中,僅僅是在控制臺(tái)打印出了日志,并沒有創(chuàng)建明確的對(duì)象。將它稍微改動(dòng)下,加一行,每次打印前先創(chuàng)建 10M 的對(duì)象

csharp復(fù)制代碼public String writeInfo() throws InterruptedException { ?? ?while (true) { ?? ? ? ?Thread.sleep(1000); ?? ? ? ?Byte[] bytes = new Byte[1024 * 1024 * 10]; ?? ? ? ?System.out.println("正在輸出信息"); ?? ?} }

結(jié)果依舊會(huì)繼續(xù)打印。看到這里有些人可能會(huì)說,答案確實(shí)是"還能繼續(xù)執(zhí)行",我只能說你是 Too Young Too Simple 。往下看

堆內(nèi)存泄漏

  1. 老規(guī)矩,還是上面的方法

csharp復(fù)制代碼public String writeInfo() throws InterruptedException { ?? ?while (true) { ?? ? ? ?Thread.sleep(1000); ?? ? ? ?Byte[] bytes = new Byte[1024 * 1024 * 10]; ?? ? ? ?System.out.println("正在輸出信息"); ?? ?} }

  1. 創(chuàng)建一個(gè)內(nèi)存泄漏的方法,list2 作用域是在類對(duì)象級(jí)別,從而產(chǎn)生內(nèi)存泄漏

csharp復(fù)制代碼List<Integer> list2 = new ArrayList<>(); @GetMapping("/headOOM2") public String headOOM2() throws InterruptedException { ?? ?while (true) { ?? ? ? ?list2.add(1); ?? ?} }

  1. 然后繼續(xù)執(zhí)行,結(jié)果首先是headOOM2這個(gè)方法對(duì)應(yīng)的線程拋出 OOM。

  1. 接著是 WriteInfo這個(gè)方法對(duì)應(yīng)的線程拋出OOM,所以我猜測(cè)現(xiàn)在整個(gè)進(jìn)程基本都不能處理請(qǐng)求了。

  1. 為了印證這個(gè)猜測(cè),再去調(diào)用下 writeInfo這個(gè)方法,直接拋出 OOM 異常。說明我們的猜測(cè)是對(duì)的。

  1. 這時(shí)候你如果把那個(gè) 10M 改成1M,writeInfo 這個(gè)方法就又能執(zhí)行下去了,不信的話就去試試看吧。

這說明內(nèi)存泄漏的情況,其他線程能否繼續(xù)執(zhí)行下去,取決于這些線程的執(zhí)行邏輯是否會(huì)占用大量?jī)?nèi)存。

不發(fā)生內(nèi)存泄漏的情況下,為什么頻繁創(chuàng)建對(duì)象會(huì)導(dǎo)致OOM,GC 不是會(huì)把對(duì)象給回收嗎

最后再回答下這個(gè)問題:

  1. 堆內(nèi)存限制:Java程序的堆內(nèi)存有一定的大小限制,如果頻繁創(chuàng)建對(duì)象并且無法及時(shí)回收,堆空間可能會(huì)被耗盡。雖然垃圾回收器會(huì)盡力回收不再使用的對(duì)象,但如果對(duì)象創(chuàng)建的速度超過垃圾回收器的回收速度,就會(huì)導(dǎo)致堆內(nèi)存不足而發(fā)生 OOM。

  2. 垃圾回收的開銷:盡管垃圾回收器會(huì)回收不再使用的對(duì)象,但垃圾回收本身也是需要消耗時(shí)間和計(jì)算資源的。如果頻繁創(chuàng)建大量的臨時(shí)對(duì)象,垃圾回收器需要花費(fèi)更多的時(shí)間來回收這些對(duì)象,導(dǎo)致應(yīng)用程序的執(zhí)行效率下降。

  3. 內(nèi)存碎片化:頻繁創(chuàng)建和銷毀對(duì)象會(huì)導(dǎo)致內(nèi)存空間的碎片化。當(dāng)內(nèi)存中存在大量碎片化的空閑內(nèi)存塊時(shí),即使總的空閑內(nèi)存足夠,但可能無法找到連續(xù)的大塊內(nèi)存來分配給新對(duì)象。這種情況下,即使垃圾回收器回收了部分對(duì)象,仍然無法分配足夠的內(nèi)存給新創(chuàng)建的對(duì)象,從而導(dǎo)致OOM。 所以你可以從GC日志上發(fā)現(xiàn),發(fā)生OOM時(shí),你的堆大小沒有到達(dá)你的閾值。

不知道到這,你看懂了沒有。

總結(jié)

首先,我們鋪墊了什么是 OOM,以及 OOM 發(fā)生的場(chǎng)景,包括內(nèi)存溢出、內(nèi)存泄漏,從而得出了這個(gè)問題:當(dāng) Java 線程在處理請(qǐng)求時(shí),拋出了 OOM 異常,整個(gè)進(jìn)程還能處理請(qǐng)求嗎?

接著通過代碼實(shí)戰(zhàn),模擬了內(nèi)存溢出和內(nèi)存泄漏兩個(gè)場(chǎng)景,暫時(shí)性的得出了結(jié)論:

  1. 內(nèi)存溢出的情況,當(dāng) GC 的速度跟不上內(nèi)存的分配時(shí),會(huì)發(fā)生 OOM, 從而將那個(gè)線程 Kill 掉,在這種情況下,進(jìn)程一般還能繼續(xù)處理請(qǐng)求。

  2. 內(nèi)存泄漏的情況,由于這些內(nèi)存不能被回收掉,會(huì)發(fā)生OOM,從而將那個(gè)線程 Kill 掉,防止繼續(xù)創(chuàng)建不能被回收的對(duì)象,此時(shí)有些不占用內(nèi)存的線程可能將繼續(xù)執(zhí)行,而那些會(huì)占用大量?jī)?nèi)存的線程可能將無法執(zhí)行,最壞的情況可能是進(jìn)程直接掛掉。


面試必殺題:當(dāng)發(fā)生OOM時(shí),進(jìn)程還能處理請(qǐng)求嗎的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
晋城| 航空| 腾冲县| 大石桥市| 吴江市| 南投市| 冀州市| 旬阳县| 博客| 偏关县| 星子县| 麻江县| 莫力| 嘉义市| 商水县| 合山市| 信丰县| 荣成市| 枣庄市| 泰安市| 鹰潭市| 孙吴县| 拜泉县| 平遥县| 襄汾县| 兴隆县| 本溪| 元氏县| 工布江达县| 改则县| 南雄市| 乐安县| 普兰店市| 安达市| 乌兰察布市| 茂名市| 永和县| 台东县| 博爱县| 女性| 荆州市|