最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

springboot中@Async默認(rèn)線程池導(dǎo)致OOM問題

 更新時(shí)間:2020年06月01日 09:46:27   作者:ignorewho  
這篇文章主要介紹了springboot中@Async默認(rèn)線程池導(dǎo)致OOM問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言:

1.最近項(xiàng)目上在測(cè)試人員壓測(cè)過程中發(fā)現(xiàn)了OOM問題,項(xiàng)目使用springboot搭建項(xiàng)目工程,通過查看日志中包含信息:unable to create new native thread

內(nèi)存溢出的三種類型:
1.第一種OutOfMemoryError: PermGen space,發(fā)生這種問題的原意是程序中使用了大量的jar或class
2.第二種OutOfMemoryError: Java heap space,發(fā)生這種問題的原因是java虛擬機(jī)創(chuàng)建的對(duì)象太多
3.第三種OutOfMemoryError:unable to create new native thread,創(chuàng)建線程數(shù)量太多,占用內(nèi)存過大

初步分析:

1.初步懷疑是線程創(chuàng)建太多導(dǎo)致,使用jstack 線程號(hào) > /tmp/oom.log將應(yīng)用的線程信息打印出來。查看oom.log,發(fā)現(xiàn)大量線程處于Runnable狀態(tài),基本可以確認(rèn)是線程創(chuàng)建太多了。

代碼分析:

1.出問題的微服務(wù)是日志寫庫(kù)服務(wù),對(duì)比日志,鎖定在writeLog方法上,wirteLog方法使用spring-@Async注解,寫庫(kù)操作采用的是異步寫入方式。
2.之前沒有對(duì)@Async注解深入研究過,只是知道可以自定義內(nèi)部線程池,經(jīng)查看,日志寫庫(kù)服務(wù)并未自定義異步配置,使用的是spring-@Async默認(rèn)異步配置
3.首先簡(jiǎn)單百度了下,網(wǎng)上提到@Async默認(rèn)異步配置使用的是SimpleAsyncTaskExecutor,該線程池默認(rèn)來一個(gè)任務(wù)創(chuàng)建一個(gè)線程,在壓測(cè)情況下,會(huì)有大量寫庫(kù)請(qǐng)求進(jìn)入日志寫庫(kù)服務(wù),這時(shí)就會(huì)不斷創(chuàng)建大量線程,極有可能壓爆服務(wù)器內(nèi)存。

借此機(jī)會(huì)也學(xué)習(xí)了下SimpleAsyncTaskExecutor源碼,總結(jié)如下:

1.SimpleAsyncTaskExecutor提供了限流機(jī)制,通過concurrencyLimit屬性來控制開關(guān),當(dāng)concurrencyLimit>=0時(shí)開啟限流機(jī)制,默認(rèn)關(guān)閉限流機(jī)制即concurrencyLimit=-1,當(dāng)關(guān)閉情況下,會(huì)不斷創(chuàng)建新的線程來處理任務(wù),核心代碼如下:

public void execute(Runnable task, long startTimeout) {
  Assert.notNull(task, "Runnable must not be null");
  Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
  //判斷是否開啟限流機(jī)制
  if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
   //執(zhí)行前置操作,進(jìn)行限流
   this.concurrencyThrottle.beforeAccess();
   //執(zhí)行完線程任務(wù),會(huì)執(zhí)行后置操作concurrencyThrottle.afterAccess(),配合進(jìn)行限流
   doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
  }
  else {
   doExecute(taskToUse);
  }
}

2.SimpleAsyncTaskExecutor限流實(shí)現(xiàn)

首先任務(wù)進(jìn)來,會(huì)循環(huán)判斷當(dāng)前執(zhí)行線程數(shù)是否超過concurrencyLimit,如果超了,則當(dāng)前線程調(diào)用wait方法,釋放monitor對(duì)象鎖,進(jìn)入等待

protected void beforeAccess() {
	if (this.concurrencyLimit == NO_CONCURRENCY) {
		throw new IllegalStateException(
				"Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
	}
	if (this.concurrencyLimit > 0) {
		boolean debug = logger.isDebugEnabled();
		synchronized (this.monitor) {
			boolean interrupted = false;
			while (this.concurrencyCount >= this.concurrencyLimit) {
				if (interrupted) {
					throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
							"but concurrency limit still does not allow for entering");
				}
				if (debug) {
					logger.debug("Concurrency count " + this.concurrencyCount +
							" has reached limit " + this.concurrencyLimit + " - blocking");
				}
				try {
					this.monitor.wait();
				}
				catch (InterruptedException ex) {
					// Re-interrupt current thread, to allow other threads to react.
					Thread.currentThread().interrupt();
					interrupted = true;
				}
			}
			if (debug) {
				logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
			}
			this.concurrencyCount++;
		}
	}
}

2.SimpleAsyncTaskExecutor限流實(shí)現(xiàn):首先任務(wù)進(jìn)來,會(huì)循環(huán)判斷當(dāng)前執(zhí)行線程數(shù)是否超過concurrencyLimit,如果超了,則當(dāng)前線程調(diào)用wait方法,釋放monitor對(duì)象鎖,進(jìn)入等待狀態(tài)。

protected void beforeAccess() {
	if (this.concurrencyLimit == NO_CONCURRENCY) {
		throw new IllegalStateException(
				"Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
	}
	if (this.concurrencyLimit > 0) {
		boolean debug = logger.isDebugEnabled();
		synchronized (this.monitor) {
			boolean interrupted = false;
			while (this.concurrencyCount >= this.concurrencyLimit) {
				if (interrupted) {
					throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
							"but concurrency limit still does not allow for entering");
				}
				if (debug) {
					logger.debug("Concurrency count " + this.concurrencyCount +
							" has reached limit " + this.concurrencyLimit + " - blocking");
				}
				try {
					this.monitor.wait();
				}
				catch (InterruptedException ex) {
					// Re-interrupt current thread, to allow other threads to react.
					Thread.currentThread().interrupt();
					interrupted = true;
				}
			}
			if (debug) {
				logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
			}
			this.concurrencyCount++;
		}
	}
}

線程任務(wù)執(zhí)行完畢后,當(dāng)前執(zhí)行線程數(shù)會(huì)減一,會(huì)調(diào)用monitor對(duì)象的notify方法,喚醒等待狀態(tài)下的線程,等待狀態(tài)下的線程會(huì)競(jìng)爭(zhēng)monitor鎖,競(jìng)爭(zhēng)到,會(huì)繼續(xù)執(zhí)行線程任務(wù)。

protected void afterAccess() {
	if (this.concurrencyLimit >= 0) {
		synchronized (this.monitor) {
			this.concurrencyCount--;
			if (logger.isDebugEnabled()) {
				logger.debug("Returning from throttle at concurrency count " + this.concurrencyCount);
			}
			this.monitor.notify();
		}
	}
}

雖然看了源碼了解了SimpleAsyncTaskExecutor有限流機(jī)制,實(shí)踐出真知,我們還是測(cè)試下:
一、測(cè)試未開啟限流機(jī)制下,我們啟動(dòng)20個(gè)線程去調(diào)用異步方法,查看Java VisualVM工具如下:


二、測(cè)試開啟限流機(jī)制,開啟限流機(jī)制的代碼如下:

@Configuration
@EnableAsync
public class AsyncCommonConfig extends AsyncConfigurerSupport {
  @Override
  public Executor getAsyncExecutor() {
    SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
    //設(shè)置允許同時(shí)執(zhí)行的線程數(shù)為10
 executor.setConcurrencyLimit(10);
    return executor;
  }
}

同樣,我們啟動(dòng)20個(gè)線程去調(diào)用異步方法,查看Java VisualVM工具如下:

通過上面驗(yàn)證可知:
1.開啟限流情況下,能有效控制應(yīng)用線程數(shù)
2.雖然可以有效控制線程數(shù),但執(zhí)行效率會(huì)降低,會(huì)出現(xiàn)主線程等待,線程競(jìng)爭(zhēng)的情況。
3.限流機(jī)制適用于任務(wù)處理比較快的場(chǎng)景,對(duì)于應(yīng)用處理時(shí)間比較慢的場(chǎng)景并不適用。==

最終解決辦法:
1.自定義線程池,使用LinkedBlockingQueue阻塞隊(duì)列來限定線程池的上限
2.定義拒絕策略,如果隊(duì)列滿了,則拒絕處理該任務(wù),打印日志,代碼如下:

public class AsyncConfig implements AsyncConfigurer{
  private Logger logger = LogManager.getLogger();

  @Value("${thread.pool.corePoolSize:10}")
  private int corePoolSize;

  @Value("${thread.pool.maxPoolSize:20}")
  private int maxPoolSize;

  @Value("${thread.pool.keepAliveSeconds:4}")
  private int keepAliveSeconds;

  @Value("${thread.pool.queueCapacity:512}")
  private int queueCapacity;

  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(corePoolSize);
    executor.setMaxPoolSize(maxPoolSize);
    executor.setKeepAliveSeconds(keepAliveSeconds);
    executor.setQueueCapacity(queueCapacity);
    executor.setRejectedExecutionHandler((Runnable r, ThreadPoolExecutor exe) -> {
        logger.warn("當(dāng)前任務(wù)線程池隊(duì)列已滿.");
    });
    executor.initialize();
    return executor;
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new AsyncUncaughtExceptionHandler() {
      @Override
      public void handleUncaughtException(Throwable ex , Method method , Object... params) {
        logger.error("線程池執(zhí)行任務(wù)發(fā)生未知異常.", ex);
      }
    };
  }
}

到此這篇關(guān)于springboot中@Async默認(rèn)線程池導(dǎo)致OOM問題的文章就介紹到這了,更多相關(guān)springboot @Async線程池導(dǎo)致OOM內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Jmeter JDBC請(qǐng)求常見問題解決方案

    Jmeter JDBC請(qǐng)求常見問題解決方案

    這篇文章主要介紹了Jmeter JDBC請(qǐng)求常見問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Jenkins+maven持續(xù)集成的實(shí)現(xiàn)

    Jenkins+maven持續(xù)集成的實(shí)現(xiàn)

    這篇文章主要介紹了Jenkins+maven持續(xù)集成的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • spring事務(wù)之事務(wù)掛起和事務(wù)恢復(fù)源碼解讀

    spring事務(wù)之事務(wù)掛起和事務(wù)恢復(fù)源碼解讀

    這篇文章主要介紹了spring事務(wù)之事務(wù)掛起和事務(wù)恢復(fù)源碼解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • (starters)springboot-starter整合阿里云datahub方式

    (starters)springboot-starter整合阿里云datahub方式

    這篇文章主要介紹了(starters)springboot-starter整合阿里云datahub方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • IDEA maven上傳速度很慢的解決辦法

    IDEA maven上傳速度很慢的解決辦法

    maven上傳的速度很慢,排除網(wǎng)絡(luò)原因,需要檢查配置,本文主要介紹了IDEA maven上傳速度很慢的解決辦法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-08-08
  • Maven默認(rèn)使用JDK1.5的問題及解決方案

    Maven默認(rèn)使用JDK1.5的問題及解決方案

    這篇文章主要介紹了Maven默認(rèn)使用JDK1.5的問題及解決方案,本文給大家分享兩種方式,通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Springboot 集成 SocketIO的示例代碼

    Springboot 集成 SocketIO的示例代碼

    Socket.IO是實(shí)現(xiàn)瀏覽器與服務(wù)器之間實(shí)時(shí)、雙向和基于事件的通信的工具庫(kù),本文主要介紹了Springboot 集成 SocketIO的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10
  • MyBatis中一對(duì)多的xml配置方式(嵌套查詢/嵌套結(jié)果)

    MyBatis中一對(duì)多的xml配置方式(嵌套查詢/嵌套結(jié)果)

    這篇文章主要介紹了MyBatis中一對(duì)多的xml配置方式(嵌套查詢/嵌套結(jié)果),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Spring中配置數(shù)據(jù)源的幾種方式

    Spring中配置數(shù)據(jù)源的幾種方式

    今天小編就為大家分享一篇關(guān)于Spring中配置數(shù)據(jù)源的幾種方式,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • 如何更好的使用Java8中方法引用詳解

    如何更好的使用Java8中方法引用詳解

    在Java8中,我們可以直接通過方法引用來簡(jiǎn)寫lambda表達(dá)式中已經(jīng)存在的方法,這種特性就叫做方法引用(Method Reference)。下面這篇文章主要給大家介紹了關(guān)于如何更好的使用Java8中方法引用的相關(guān)資料,需要的朋友可以參考下。
    2017-09-09

最新評(píng)論

抚顺市| 育儿| 湖南省| 扶余县| 砚山县| 河间市| 太谷县| 涞源县| 沧源| 克拉玛依市| 略阳县| 宁南县| 宜城市| 东平县| 宁海县| 吉林省| 麻栗坡县| 镇巴县| 含山县| 施秉县| 蚌埠市| 邯郸市| 通渭县| 色达县| 福海县| 黄浦区| 丹巴县| 客服| 宜章县| 合水县| 岱山县| 海盐县| 南靖县| 龙口市| 河南省| 荣成市| 临海市| 胶南市| 太湖县| 兰考县| 扎兰屯市|