在實際生產(chǎn)中偶爾會遇到oom,導(dǎo)致服務(wù)器的宕機,如果使用了堆外內(nèi)存也有可能導(dǎo)致k8s的pod宕機這是另一個問題了,發(fā)生oom的原因就是內(nèi)存沒有足夠的空間分配對象,在oom之前會發(fā)生多次的fullgc,依然不能釋放空間則就會使jvm停止,
那發(fā)生oom一定會導(dǎo)致服務(wù)器宕機嗎,不是的,oom其實也是一個異常,類型是OutOfMemoryError,是可以trycatch的,如果發(fā)生異常其實就是退出當(dāng)前線程.
spring的main線程負(fù)責(zé)啟動,初始化容器,監(jiān)聽器等功能,真正處理業(yè)務(wù)都是非main線程,比如tomcat創(chuàng)建的線程處理業(yè)務(wù),當(dāng)發(fā)生oom的時候,其實只是拋出一個異常,退出當(dāng)前線程,如果是main線程那可能jvm直接退出了
如果大對象變量是線程內(nèi)部變量,當(dāng)線程退出的時候,大對象會被GC掉的,并不會導(dǎo)致整個jvm宕機,如果大對象是線程共享的,這個就很可能導(dǎo)致jvm宕機,因為大對象不回因為線程的退出而被GC掉,GCROOT可達(dá)
示例
public class OomTest {
public static List<byte[]> list = new ArrayList<>();
@RequestMapping("hello")
public String oom(@RequestBody Map<String, String> map) throws InterruptedException{
return null != map ? map.get("nihao") : "參數(shù)為null";
}
byte[] bytes = new byte[new Random().nextInt(1024*1024)];
public static void main(String[] args) throws InterruptedException{
//如果list放在主線程中,是不會GC掉的,因為被main線程棧引用
//List<OomTest> list = new ArrayList<>();
new Thread(()->{
//線程里的對象,當(dāng)線程因為oom退出的時候,list會被gc掉
List<OomTest> list = new ArrayList<>();
while(true){
//這里trycatch是可以被捕獲的 OutOfMemoryError異常,如果在catch中將list.clear(),當(dāng)前線程也不回退出
list.add(new OomTest());
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("非主線程");
}
}).start();
//主線程依然會運行
while(true){
try{
Thread.sleep(30);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("主線程");
}
}
}
實際生產(chǎn)中基本都是成員變量導(dǎo)致不能被回收,從而導(dǎo)致jvm發(fā)生宕機.


觀察jvisualvm工具,中間顏色較深的是老年代,第一張圖是還沒有發(fā)生oom,老年代還呈現(xiàn)上升趨勢,第二張圖是發(fā)生了oom,當(dāng)線程退出的時候,老年代的文件會被gc掉,因為gcroot不可達(dá),文件大部分被回收了,如果list放在線程外面就不回被回收,因為被main線程引用.
什么是線程退出呢,其實就是線程執(zhí)行結(jié)束了,上面提到了當(dāng)線程發(fā)生了異常被trycatch,線程就執(zhí)行結(jié)束了,因為catch里方法執(zhí)行完,下面就沒有要執(zhí)行的方法了,就退出了唄.線程池的線程是在while(true)中,獲取阻塞隊列的任務(wù)(runnable),如果沒有任務(wù)就阻塞等待任務(wù)向隊列offer,如果線程數(shù)大于最大線程數(shù)量,退出while循環(huán),線程就結(jié)束了,線程對于java就是一個普通的對象,是會被回收的,真正調(diào)用start方法的時候,才會在內(nèi)核創(chuàng)建與之關(guān)聯(lián)的線程,由cup去執(zhí)行,start會調(diào)用start0方法,本地方法.