文章開篇問一個問題吧,一個java程式,如果其中一個執行緒發生了OOM,那行程中的其他執行緒還能運行嗎?
接下來做實驗,看看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);
}
}
監測服務是否可用(http://localhost:8080/checkHealth 測驗服務正常可用):
/**
* @author :charon
* @date :Created in 2021/5/17 8:49
* @description : 測驗服務是否可用
* @version: 1.0
*/
@RestController
public class CheckHealthController {
@RequestMapping("/checkHealth")
public String stackOverFlowError(){
System.out.println("呼叫服務監測介面-----------------------");
return "服務監測介面回傳";
}
}
1.StackOverflowError(堆疊溢位)
堆疊溢位代表的是:當堆疊的深度超過虛擬機分配給執行緒的堆疊大小時就會出現error,
/**
* @author :charon
* @date :Created in 2021/5/17 8:49
* @description : 測驗java.lang.StackOverflowError: null的錯誤
* @version: 1.0
*/
@RestController
public class StackOverFlowErrorController {
/**
* 遞回呼叫一個方法,使其超過堆疊的最大深度
*/
@RequestMapping("/stackOverFlowError")
public void stackOverFlowError(){
stackOverFlowError();
}
}
使用瀏覽器呼叫堆疊溢位的介面(localhost:8080/stackOverFlowError),發現后臺報了堆疊溢位的錯誤,

呼叫監測程式可用的介面,發現還是可以正常訪問,

2.Java heap space(堆記憶體溢位)
當GC多次的時候新生代和老生代的堆記憶體幾乎用滿了,頻繁觸發Full GC (Ergonomics) ,直到沒有記憶體空間給新生物件了,所以JVM拋出了記憶體溢位錯誤!進而導致程式崩潰,
設定虛擬機引數(-Xms10m -Xmx10m -XX:+PrintGCDetails),如果不設定的話,可能會執行很久,
@RestController
public class JavaHeapSpaceController {
/**
* 使用是回圈創建物件,是堆記憶體溢位
*/
@RequestMapping("/javaHeapSpace")
public void javaHeapSpace(){
String str = "hello world";
while (true){
str += new Random().nextInt(1111111111) + new Random().nextInt(222222222);
/**
* intern()方法:
* (1)當常量池中不存在這個字串的參考,將這個物件的參考加入常量池,回傳這個物件的參考,
* (2)當常量池中存在這個字串的參考,回傳這個物件的參考;
*/
str.intern();
}
}
}

呼叫監測程式可用的介面,發現還是可以正常訪問,

3.direct buffer memory
在寫IO程式(如Netty)的時候,經常使用ByteBuffer來讀取或者寫入資料,這是一種基于通道(channel)和緩沖區(Buffer)的IO方式,他可以使用Native函式庫直接分配對外記憶體,然后通過一個存盤在java堆里面的DirectByteBuffer物件作為這塊記憶體的參考操作,這樣能在在一些場景中顯著提高性能,因為避免了再java堆和Native堆中來回復制資料,
ByteBuffer.allocate(capacity) 這種方式是分配jvm堆記憶體,屬于GC管轄的范圍,由于需要拷貝所以速度較慢
ByteBuffer.allocateDirect(capacity) 這種方式是分配本地記憶體,不屬于GC的管轄范圍,由于不需要記憶體拷貝,所以速度較快
但是如果不斷分配本地記憶體,堆記憶體很少使用,那么JVM就不需要執行GC,DirectByteBuffer物件就不會回收,
這時候堆記憶體充足,但本地記憶體可能已經使用光了,再次嘗試分配本地記憶體,就會出現OutOfMemoryError
設定JVM引數: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
@RestController
public class DirectBufferMemoryController {
@RequestMapping("/directBufferMemory")
public void directBufferMemory(){
System.out.println("初始配置的最大本地記憶體為:"+ (sun.misc.VM.maxDirectMemory()/1024/1024)+"MB");
// 在jvm引數里設定的最大記憶體為5M,
ByteBuffer buffer = ByteBuffer.allocateDirect(6*1024*1024);
}
}
訪問記憶體溢位的介面(http://localhost:8080/directBufferMemory),報錯之后再次訪問服務監測介面,發現還是可以繼續訪問的,

4.GC overhead limit exceeded
GC回收之間過長會拋出這個錯,過長的定義是:超過98%的時間用來做垃圾回收并且只回收了不到2%的堆記憶體,連續多次GC都只回收了不到2%的極端情況下才會拋出,加入不拋出GC overhead limit錯誤,就會發生下列情況:
- GC清理的這么點記憶體很快就會再次被填滿,形成惡性回圈
- CPU使用率一直是100%,而GC沒有任何效果
設定JVM引數: -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,但是垃圾回收前后,新生代和老年代的記憶體差不多,就說明,垃圾回收效果不大,

再次訪問服務監測介面,發現還是可以繼續訪問的,
5.Metaspace
java 8及其以后的版本中使用了MetaSpace代替了永久代,它與永久代最大的區別在于:
? MetaSpace并不在虛擬機記憶體中,而是使用本地記憶體,也就是說,在java8中,Class metadata被存盤在MetaSpace的native Memory中
MetaSpace中存盤了一下資訊:
- 虛擬機加載的類資訊
- 常量池
- 靜態變數
- 即時編譯后的代碼
引數設定:-XX:+PrintGCDetails -XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m
@RestController
public class MetaSpaceController {
static class OomTest{
}
/**
* 模擬MetaSpace溢位,不斷生成類往元空間放,類占據的空間會超過MetaSpace指定的大小
*/
@RequestMapping("/metaSpace")
public void metaSpace(){
int i = 0;
try{
while (true){
i++;
/**
* Enhancer允許為非介面型別創建一個java代理,Enhancer動態創建了給定型別的子類但是攔截了所有的方法,
* 和proxy不一樣的是:不管是介面還是類它都能正常作業,
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OomTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public 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創建的代理類導致的Metaspace的問題,具體地址我也忘記了,,,,,

再次訪問服務監測介面,發現還是可以繼續訪問的,
6.unable to create new thread
在高并發服務時,經常會出現如下錯誤,
導致原因:
- 1.應用程式創建了太多的執行緒,一個應用行程創建的執行緒超過了系統承載極限
- 2.服務器不允許應用程式創建這么多執行緒,linux系統默認允許單個行程可以創建的執行緒數為1024個(如果是普通用戶小于這個值)
解決辦法:
- 1.降低應用程式創建執行緒的數量,分析應用是否真的需要創建這么多執行緒
- 2.對于有的應用確實需要創建這么多的執行緒,可以修改linux服務器配置,擴大linux的默認限制
查看:ulimit -u
修改:vim /etc/security/limits.d/90-nproc.conf
@RestController
public class UnableCreateThreadController {
/**
* 友情提示:千萬別在windows中運行這段代碼,如果不小心和我一樣試了,那就只能強制重啟了
*/
@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用戶測驗的,創建了7409個執行緒,大家測驗的時候最好是使用普通用戶測驗,

最后執行檢測服務的介面,發現程式還是可以繼續訪問的,
小結
其實發生OOM的執行緒一般情況下會死亡,也就是會被終結掉,該執行緒持有的物件占用的heap都會被gc了,釋放記憶體,因為發生OOM之前要進行gc,就算其他執行緒能夠正常作業,也會因為頻繁gc產生較大的影響,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/290864.html
標籤:java
上一篇:java-房屋出租系統(專案)
下一篇:Java常用類小結
