高德面试官问我:JVM内存溢出后服务还能运行吗,我一顿操作行云流水
文章開篇問一個問題吧,一個java程序,如果其中一個線程發(fā)生了OOM,那進(jìn)程中的其他線程還能運(yùn)行嗎?
接下來做實(shí)驗(yàn),看看JVM的六種OOM之后程序還能不能訪問。
在這里我用的是一個springboot程序。
/*** @author :charon* @date :Created in 2021/5/17 8:30* @description : 程序啟動類* @version: 1.0*/ @SpringBootApplication public class CharonApplication {public static void main(String[] args) {SpringApplication.run(CharonApplication.class, args);}}監(jiān)測服務(wù)是否可用(http://localhost:8080/checkHealth?測試服務(wù)正常可用):
/*** @author :charon* @date :Created in 2021/5/17 8:49* @description : 測試服務(wù)是否可用* @version: 1.0*/ @RestController public class CheckHealthController {@RequestMapping("/checkHealth")public String stackOverFlowError(){System.out.println("調(diào)用服務(wù)監(jiān)測接口-----------------------");return "服務(wù)監(jiān)測接口返回";} }1.StackOverflowError(棧溢出)
棧溢出代表的是:當(dāng)棧的深度超過虛擬機(jī)分配給線程的棧大小時就會出現(xiàn)error。
/*** @author :charon* @date :Created in 2021/5/17 8:49* @description : 測試java.lang.StackOverflowError: null的錯誤* @version: 1.0*/ @RestController public class StackOverFlowErrorController {/*** 遞歸調(diào)用一個方法,使其超過棧的最大深度*/@RequestMapping("/stackOverFlowError")public void stackOverFlowError(){stackOverFlowError();} }使用瀏覽器調(diào)用棧溢出的接口(localhost:8080/stackOverFlowError),發(fā)現(xiàn)后臺報了棧溢出的錯誤。
調(diào)用監(jiān)測程序可用的接口,發(fā)現(xiàn)還是可以正常訪問。
2.Java heap space(堆內(nèi)存溢出)
當(dāng)GC多次的時候新生代和老生代的堆內(nèi)存幾乎用滿了,頻繁觸發(fā)Full GC (Ergonomics) ,直到?jīng)]有內(nèi)存空間給新生對象了。所以JVM拋出了內(nèi)存溢出錯誤!進(jìn)而導(dǎo)致程序崩潰。
設(shè)置虛擬機(jī)參數(shù)(-Xms10m -Xmx10m -XX:+PrintGCDetails),如果不設(shè)置的話,可能會執(zhí)行很久。
@RestController public class JavaHeapSpaceController {/*** 使用是循環(huán)創(chuàng)建對象,是堆內(nèi)存溢出*/@RequestMapping("/javaHeapSpace")public void javaHeapSpace(){String str = "hello world";while (true){str += new Random().nextInt(1111111111) + new Random().nextInt(222222222);/*** intern()方法:* (1)當(dāng)常量池中不存在這個字符串的引用,將這個對象的引用加入常量池,返回這個對象的引用。* (2)當(dāng)常量池中存在這個字符串的引用,返回這個對象的引用;*/str.intern();}} }調(diào)用監(jiān)測程序可用的接口,發(fā)現(xiàn)還是可以正常訪問。
3.direct buffer memory
在寫IO程序(如Netty)的時候,經(jīng)常使用ByteBuffer來讀取或者寫入數(shù)據(jù),這是一種基于通道(channel)和緩沖區(qū)(Buffer)的IO方式,他可以使用Native函數(shù)庫直接分配對外內(nèi)存,然后通過一個存儲在java堆里面的DirectByteBuffer對象作為這塊內(nèi)存的引用操作,這樣能在在一些場景中顯著提高性能,因?yàn)楸苊饬嗽賘ava堆和Native堆中來回復(fù)制數(shù)據(jù)。
ByteBuffer.allocate(capacity) 這種方式是分配jvm堆內(nèi)存,屬于GC管轄的范圍,由于需要拷貝所以速度較慢
ByteBuffer.allocateDirect(capacity) 這種方式是分配本地內(nèi)存,不屬于GC的管轄范圍,由于不需要內(nèi)存拷貝,所以速度較快
但是如果不斷分配本地內(nèi)存,堆內(nèi)存很少使用,那么JVM就不需要執(zhí)行GC,DirectByteBuffer對象就不會回收,
這時候堆內(nèi)存充足,但本地內(nèi)存可能已經(jīng)使用光了,再次嘗試分配本地內(nèi)存,就會出現(xiàn)OutOfMemoryError
設(shè)置JVM參數(shù): -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
@RestController public class DirectBufferMemoryController {@RequestMapping("/directBufferMemory")public void directBufferMemory(){System.out.println("初始配置的最大本地內(nèi)存為:"+ (sun.misc.VM.maxDirectMemory()/1024/1024)+"MB");// 在jvm參數(shù)里設(shè)置的最大內(nèi)存為5M,ByteBuffer buffer = ByteBuffer.allocateDirect(6*1024*1024);}}訪問內(nèi)存溢出的接口(http://localhost:8080/directBufferMemory),報錯之后再次訪問服務(wù)監(jiān)測接口,發(fā)現(xiàn)還是可以繼續(xù)訪問的。
4.GC overhead limit exceeded
GC回收之間過長會拋出這個錯,過長的定義是:超過98%的時間用來做垃圾回收并且只回收了不到2%的堆內(nèi)存,連續(xù)多次GC都只回收了不到2%的極端情況下才會拋出,加入不拋出GC overhead limit錯誤,就會發(fā)生下列情況:
- GC清理的這么點(diǎn)內(nèi)存很快就會再次被填滿,形成惡性循環(huán)
- CPU使用率一直是100%,而GC沒有任何效果
設(shè)置JVM參數(shù): -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
@RestController public class GcOverHeadController {@RequestMapping("/gcOverHead")public void gcOverHead(){int i = 0;List<String> list = new ArrayList<>();try{while(true){list.add(String.valueOf(++i).intern());}}catch(Throwable e){System.out.println("i的值為:" + i);e.printStackTrace();throw e;}} }如下圖所示,在報錯這個異常之前,在頻繁的Full GC,但是垃圾回收前后,新生代和老年代的內(nèi)存差不多,就說明,垃圾回收效果不大。
再次訪問服務(wù)監(jiān)測接口,發(fā)現(xiàn)還是可以繼續(xù)訪問的。
5.Metaspace
java 8及其以后的版本中使用了MetaSpace代替了永久代,它與永久代最大的區(qū)別在于:
? MetaSpace并不在虛擬機(jī)內(nèi)存中,而是使用本地內(nèi)存,也就是說,在java8中,Class metadata被存儲在MetaSpace的native Memory中
MetaSpace中存儲了一下信息:
- 虛擬機(jī)加載的類信息
- 常量池
- 靜態(tài)變量
- 即時編譯后的代碼
參數(shù)設(shè)置:-XX:+PrintGCDetails -XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m
@RestController public class MetaSpaceController {static class OomTest{}/*** 模擬MetaSpace溢出,不斷生成類往元空間放,類占據(jù)的空間會超過MetaSpace指定的大小*/@RequestMapping("/metaSpace")public void metaSpace(){int i = 0;try{while (true){i++;/*** Enhancer允許為非接口類型創(chuàng)建一個java代理。Enhancer動態(tài)創(chuàng)建了給定類型的子類但是攔截了所有的方法,* 和proxy不一樣的是:不管是接口還是類它都能正常工作。*/Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OomTest.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return methodProxy.invokeSuper(o,objects);}});enhancer.create();}}catch (Throwable e){System.out.println("i的值為:" + i);e.printStackTrace();}} }我記得之前看過一篇公眾號的文章,就是使用Fastjson創(chuàng)建的代理類導(dǎo)致的Metaspace的問題,具體地址我也忘記了。。。。。
再次訪問服務(wù)監(jiān)測接口,發(fā)現(xiàn)還是可以繼續(xù)訪問的。
6.unable to create new thread
在高并發(fā)服務(wù)時,經(jīng)常會出現(xiàn)如下錯誤,
導(dǎo)致原因:
- 1.應(yīng)用程序創(chuàng)建了太多的線程,一個應(yīng)用進(jìn)程創(chuàng)建的線程超過了系統(tǒng)承載極限
- 2.服務(wù)器不允許應(yīng)用程序創(chuàng)建這么多線程,linux系統(tǒng)默認(rèn)允許單個進(jìn)程可以創(chuàng)建的線程數(shù)為1024個(如果是普通用戶小于這個值)
解決辦法:
- 1.降低應(yīng)用程序創(chuàng)建線程的數(shù)量,分析應(yīng)用是否真的需要創(chuàng)建這么多線程
- 2.對于有的應(yīng)用確實(shí)需要創(chuàng)建這么多的線程,可以修改linux服務(wù)器配置,擴(kuò)大linux的默認(rèn)限制
查看:ulimit -u
修改:vim /etc/security/limits.d/90-nproc.conf
@RestController public class UnableCreateThreadController {/*** 友情提示:千萬別在windows中運(yùn)行這段代碼,如果不小心和我一樣試了,那就只能強(qiáng)制重啟了*/@RequestMapping("/unableCreateThread")public void unableCreateThread(){for (int i = 0; ; i++) {System.out.println("i的值為:" + i);new Thread(()->{try{Thread.sleep(1000*1000);} catch (InterruptedException e){e.printStackTrace();}}).start();}} }我這里是使用的root用戶測試的,創(chuàng)建了7409個線程。大家測試的時候最好是使用普通用戶測試。
最后執(zhí)行檢測服務(wù)的接口,發(fā)現(xiàn)程序還是可以繼續(xù)訪問的。
小結(jié)
其實(shí)發(fā)生OOM的線程一般情況下會死亡,也就是會被終結(jié)掉,該線程持有的對象占用的heap都會被gc了,釋放內(nèi)存。因?yàn)榘l(fā)生OOM之前要進(jìn)行g(shù)c,就算其他線程能夠正常工作,也會因?yàn)轭l繁gc產(chǎn)生較大的影響。
總結(jié)
以上是生活随笔為你收集整理的高德面试官问我:JVM内存溢出后服务还能运行吗,我一顿操作行云流水的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: UI翻天覆地!iQOO Neo5S官宣:
- 下一篇: 2021年全球8款手游收入超10亿美元: