在實際生產中偶爾會遇到oom,導致服務器的宕機,如果使用了堆外內存也有可能導致k8s的pod宕機這是另一個問題了,發(fā)生oom的原因就是內存沒有足夠的空間分配對象,在oom之前會發(fā)生多次的fullgc,依然不能釋放空間則就會使jvm停止,
那發(fā)生oom一定會導致服務器宕機嗎,不是的,oom其實也是一個異常,類型是OutOfMemoryError,是可以trycatch的,如果發(fā)生異常其實就是退出當前線程.
spring的main線程負責啟動,初始化容器,監(jiān)聽器等功能,真正處理業(yè)務都是非main線程,比如tomcat創(chuàng)建的線程處理業(yè)務,當發(fā)生oom的時候,其實只是拋出一個異常,退出當前線程,如果是main線程那可能jvm直接退出了
如果大對象變量是線程內部變量,當線程退出的時候,大對象會被GC掉的,并不會導致整個jvm宕機,如果大對象是線程共享的,這個就很可能導致jvm宕機,因為大對象不回因為線程的退出而被GC掉,GCROOT可達
示例
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") : "參數為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(()->{
//線程里的對象,當線程因為oom退出的時候,list會被gc掉
List<OomTest> list = new ArrayList<>();
while(true){
//這里trycatch是可以被捕獲的 OutOfMemoryError異常,如果在catch中將list.clear(),當前線程也不回退出
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("主線程");
}
}
}
實際生產中基本都是成員變量導致不能被回收,從而導致jvm發(fā)生宕機.


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