做者:陳凱玲java
來源:https://url.cn/5UWhBvBgit
一個spring boot開發的項目,spring boot版本是1.5.7,攜帶的spring版本是4.1.3。開發反饋,忽然在本地啓動不起來了,表象特徵就是在本地IDEA上運行時,進程卡住也不退出,應用啓動時加載相關組件的日誌也不輸出。症狀以下圖:web
問題分析
由於沒有有用的日誌信息,因此不能從日誌這個層面上排查問題。可是像這種沒有輸出日誌的話,通常狀況下,確定是程序內部啓動流程卡在什麼地方了,只能經過打印下當前線程堆棧信息瞭解下。通常狀況下,在服務器環境,咱們會使用java工具包中的jstack 工具來查看:如jstack pid(應用java進程)。spring
可是,在IDEA本地開發的話,IDEA內置了一個工具,能夠直接查看當前應用的線程上線文信息,如:bootstrap
注意下面那個箭頭指向的像照相機同樣的圖標,故圖思意,就是打印當前線程快照的的意思。點擊後,就出現了右邊那些線程上下文信息了,能夠看到有不少的線程,咱們主要關注下main線程,線程狀態確實是waiting的,接着點擊箭頭所指向的main線程,能夠看到以下內容:安全
"main@1" prio=5 tid=0x1 nid=NA waitingjava.lang.Thread.State: WAITINGat sun.misc.Unsafe.park(Unsafe.java:-1)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)at org.springframework.boot.autoconfigure.BackgroundPreinitializer.onApplicationEvent(BackgroundPreinitializer.java:63)at org.springframework.boot.autoconfigure.BackgroundPreinitializer.onApplicationEvent(BackgroundPreinitializer.java:45)at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:158)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)at org.springframework.boot.context.event.EventPublishingRunListener.finished(EventPublishingRunListener.java:115)at org.springframework.boot.SpringApplicationRunListeners.callFinishedListener(SpringApplicationRunListeners.java:79)at org.springframework.boot.SpringApplicationRunListeners.finished(SpringApplicationRunListeners.java:72)at org.springframework.boot.SpringApplication.handleRunFailure(SpringApplication.java:745)at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:134)- locked <0xea6> (a java.util.concurrent.atomic.AtomicBoolean)at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:175)at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:98)at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:64)at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:74)at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54)at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:325)at org.springframework.boot.SpringApplication.run(SpringApplication.java:296)at cn.keking.project.customerManagement.KekingCustomerManagement.main(KekingCustomerManagement.java:36)
服務器
微信
app
ide
能夠看到是經過CountDownLatch.await()阻塞了線程,接着看下面那行,所在代碼塊以下:
private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);@Overridepublic void onApplicationEvent(SpringApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { if (preinitializationStarted.compareAndSet(false, true)) { performPreinitialization(); } } if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) { try { preinitializationComplete.await(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } }}
這是spring boot中的一個安全初始化資源的一個類,代碼所示爲監聽SpringApplicationEvent事件,能夠確定的是,它的邏輯執行到了 preinitializationComplete.await();這裏,致使了線程阻塞了。正常狀況下,spring會觸發ApplicationEnvironmentPreparedEvent事件完成資源的初始化,這裏先不深究Spring爲何這麼作,主要經過程序邏輯看下爲何卡這裏了,在preinitializationComplete.await();所在行打個斷點,看下event對象裏的信息,以下:
原來event是一個Spring上下文初始化失敗的異常事件對象,對象裏包含了具體的異常信息,如箭頭所指,關鍵異常信息如:
NoSuchMethodError:"org.springframework.util.ObjectUtils.unwrapOptional(Ljava/lang/Object;)Ljava/lang/Object;"
假設問題
經過上面的分析,基本定位到Spring boot應用啓動卡住這個表象背後的真實緣由了,並且也定位到了異常信息。
出現NoSuchMethodError異常,是由於調用方法的時候,找不到方法了。通常出如今兩個有關聯的jar包,可是版本對不上了,也就是常說的jar版本依賴衝突。查看了下,ObjectUtils是spring-core包裏的一個類,當前的4.1.3版本確實沒有這個unwrapOptional方法,spring-core-5.x的版本才新增了這個方法。由於以前的依賴是沒有問題,爲何如今spring上下文會調用5.x的版本的方法呢?
因此先假設近期有開發在pom.xml裏添加了新的的依賴,致使了這個問題。
當心求證
有了找問題的方向就好辦了,由於代碼都是git管理維護的,因此查看下pom.xml文件近期的提交記錄便可,查看後,確實發現了近期對pom.xml有改動,添加了一個依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.6.RELEASE</version></dependency>
這裏還涉及到一點Maven依賴優先級的問題,在pom.xml裏直接這樣添加的依賴優先於其餘jar中pom.xml依賴的,也就是說,即便spring boot1.5.7自帶了Spring-context.4.1.3,可是這樣指定後,應用最後依賴的仍是5.1.6的版本。具體的Maven依賴關係,能夠參考個人博文《關於Maven的使用,這些你都瞭解了麼?》。結合以前的分析,八九不離十了就是由於加了這個依賴致使的問題,spring-context.5.1.6配合spring-core.4.1.3確定得出問題啊。直接移除這個依賴,而後啓動系統一切正常,日誌打印了Spring加載上線文的信息。
問題總結
定位這個問題的關鍵在於要了解java中線程堆棧的知識,在沒有足夠異常日誌狀況下經過線程快照排查問題。在定位到問題後,如NoSuchMethodError這樣的異常,須要平時的經驗積累來假設問題的真實緣由,而後在追本溯源驗明問題所在根本緣由。找問題本質必定要這種按部就班的思路。舉例,出現這種問題,若是你直接去搜索引擎搜:「Spring boot應用啓動卡住了」,是搜不出來什麼東西的,可是當你發現了是因爲jar衝突。去搜索引擎搜索:
「NoSuchMethodError:"org.springframework.util.ObjectUtils.unwrapOptional(Ljava/lang/Object;)Ljava/lang/Object;"」。就會有不少的內容,很容易解決問題。
本文分享自微信公衆號 - 肥朝(feichao_java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。