<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                本課時我們主要分析一個案例,那就是一個“高死亡率”報表系統的優化之路。 傳統觀念上的報表系統,可能訪問量不是特別多,點擊一個查詢按鈕,后臺 SQL 語句的執行需要等數秒。如果使用 jstack 來查看執行線程,會發現大多數線程都阻塞在數據庫的 I/O 上。 上面這種是非常傳統的報表。還有一種類似于大屏監控一類的實時報表,這種報表的并發量也是比較可觀的,但由于它的結果集都比較小,所以我們可以像對待一個高并發系統一樣對待它,問題不是很大。 本課時要講的,就是傳統觀念上的報表。除了處理時間比較長以外,報表系統每次處理的結果集,普遍都比較大,這給 JVM 造成了非常大的壓力。 下面我們以一個綜合性的實例,來看一下一個“病入膏肓”的報表系統的優化操作。 有一個報表系統,頻繁發生內存溢出,在高峰期間使用時,還會頻繁的發生拒絕服務,這是不可忍受的。 #### 服務背景 本次要優化的服務是一個 SaaS 服務,使用 Spring Boot 編寫,采用的是 CMS 垃圾回收器。如下圖所示,有些接口會從 MySQL 中獲取數據,有些則從 MongoDB 中獲取數據,涉及的結果集合都比較大。 由于有些結果集的字段不是太全,因此需要對結果集合進行循環,可通過 HttpClient 調用其他服務的接口進行數據填充。也許你會認為某些數據可能會被復用,于是使用 Guava 做了 JVM 內緩存。 大體的服務依賴可以抽象成下面的圖。 ![](https://img.kancloud.cn/e8/11/e811aef85f3d46555bb1a425b98bbc1a_757x525.jpg) 初步排查,JVM 的資源太少。當接口 A 每次進行報表計算時,都要涉及幾百兆的內存,而且在內存里駐留很長時間,同時有些計算非常耗 CPU,特別的“吃”資源。而我們分配給 JVM 的內存只有 3 GB,在多人訪問這些接口的時候,內存就不夠用了,進而發生了 OOM。在這種情況下,即使連最簡單的報表都不能用了。 沒辦法,只有升級機器。把機器配置升級到 4core8g,給 JVM 分配 6GB 的內存,這樣 OOM 問題就消失了。但隨之而來的是頻繁的 GC 問題和超長的 GC 時間,平均 GC 時間竟然有 5 秒多。 #### 初步優化 我們前面算過,6GB 大小的內存,年輕代大約是 2GB,在高峰期,每幾秒鐘則需要進行一次 MinorGC。報表系統和高并發系統不太一樣,它的對象,存活時長大得多,并不能僅僅通過增加年輕代來解決;而且,如果增加了年輕代,那么必然減少了老年代的大小,由于 CMS 的碎片和浮動垃圾問題,我們可用的空間就更少了。雖然服務能夠滿足目前的需求,但還有一些不太確定的風險。 * 第一,了解到程序中有很多緩存數據和靜態統計數據,為了減少 MinorGC 的次數,通過分析 GC 日志打印的對象年齡分布,把 MaxTenuringThreshold 參數調整到了 3(請根據你自己的應用情況設置)。**這個參數是讓年輕代的這些對象,趕緊回到老年代去,不要老呆在年輕代里**。 * 第二,我們的 GC 時間比較長,就一塊開了參數 **CMSScavengeBeforeRemark**,使得在 CMS remark 前,先執行一次 Minor GC 將新生代清掉。同時配合上個參數,其效果還是比較好的,一方面,對象很快晉升到了老年代,另一方面,年輕代的對象在這種情況下是有限的,在整個 MajorGC 中占的時間也有限。 * 第三,由于緩存的使用,有大量的弱引用,拿一次長達 10 秒的 GC 來說。我們發現在 GC 日志里,處理 weak refs 的時間較長,達到了 4.5 秒。 ``` 2020-01-28T12:13:32.876+0800:?526569.947:?[weak?refs?processing,?4.5240649?secs] ``` 所以加入了參數 **ParallelRefProcEnabled** 來并行處理 Reference,以加快處理速度,縮短耗時。 同時還加入了其他一些優化參數,比如通過調整觸發 GC 的參數來進行優化。 ``` -Xmx6g?-Xms6g?-XX:MaxTenuringThreshold=3?-XX:+AlwaysPreTouch?-XX:+Par allelRefProcEnabled?-XX:+CMSScavengeBeforeRemark?-XX:+UseConcMarkSwe epGC?-XX:CMSInitiatingOccupancyFraction=80?-XX:+UseCMSInitiatingOccu pancyOnly??-XX:MetaspaceSize=256M?-XX:MaxMetaspaceSize=256M ``` 優化之后,效果不錯,但并不是特別明顯。經過評估,針對高峰時期的情況進行調研,我們決定再次提升機器性能,改用 8core16g 的機器。但是,這會帶來另外一個問題。 **高性能的機器帶來了非常大的服務吞吐量**,通過 jstat 進行監控,能夠看到年輕代的分配速率明顯提高,但隨之而來的 MinorGC 時長卻變的不可控,有時候會超過 1 秒。累積的請求造成了更加嚴重的后果。 這是由于堆空間明顯加大造成的回收時間加長。為了獲取較小的停頓時間,我們在堆上采用了 G1 垃圾回收器,把它的目標設定在 200ms。G1 是一款非常優秀的垃圾收集器,不僅適合堆內存大的應用,同時也簡化了調優的工作。通過主要的參數初始和最大堆空間、以及最大容忍的 GC 暫停目標,就能得到不錯的性能。所以為了照顧大對象的生成,我們把小堆區的大小修改為 16 M。修改之后,雖然 GC 更加頻繁了一些,但是停頓時間都比較小,應用的運行較為平滑。 ``` -Xmx12g?-Xms12g?-XX:+UseG1GC?-XX:InitiatingHeapOccupancyPercent=45???-XX:MaxGCPauseMillis=200??-XX:G1HeapRegionSize=16m?-XX:MetaspaceSize=256m?-XX:MaxMetaspaceSize=256m ``` 這個時候,任務來了:業務部門發力,預計客戶增長量增長 10 ~ 100 倍,報表系統需要評估其可行性,以便進行資源協調。可問題是,這個“千瘡百孔”的報表系統,稍微一壓測,就宕機,那如何應對十倍百倍的壓力呢? 使用 MAT 分析堆快照,發現很多地方可以通過代碼優化,那些占用內存特別多的對象,都是我們需要優化的。 #### 代碼優化 我們使用擴容硬件的方式,暫時緩解了 JVM 的問題,但是根本問題并沒有觸及到。為了減少內存的占用,肯定要清理無用的信息。通過對代碼的仔細分析,首先要改造的就是 SQL 查詢語句。 很多接口,其實并不需要把數據庫的每個字段都查詢出來,當你在計算和解析的時候,它們會不知不覺地“吃掉”你的內存。所以我們只需要獲取所需的數據就夠了,也就是把 select * 這種方式修改為具體的查詢字段,對于報表系統來說這種優化尤其明顯。 再一個就是 Cache 問題,通過排查代碼,會發現一些命中率特別低,占用內存又特別大的對象,放到了 JVM 內的 Cache 中,造成了無用的浪費。 解決方式,就是把 Guava 的 Cache 引用級別改成弱引用(WeakKeys),盡量去掉無用的應用緩存。對于某些使用特別頻繁的小 key,使用分布式的 Redis 進行改造即可。 為了找到更多影響因子大的問題,我們部署了獨立的環境,然后部署了 JVM 監控。在回放某個問題請求后,觀察 JVM 的響應,通過這種方式,發現了更多的優化可能。 報表系統使用了 POI 組件進行導入導出功能的開發,結果客戶在沒有限制的情況下上傳、下載了條數非常多的文件,直接讓堆內存飆升。為了解決這種情況,我們在導入功能加入了文件大小的限制,強制客戶進行拆分;在下載的時候指定范圍,嚴禁跨度非常大的請求。 在完成代碼改造之后,再把機器配置降級回 4core8g,依然采用 G1 垃圾回收器,再也沒有發生 OOM 的問題了,GC 問題也得到了明顯的緩解。 #### 拒絕服務問題 上面解決的是 JVM 的內存問題,可以看到除了優化 JVM 參數、升級機器配置以外,代碼修改帶來的優化效果更加明顯,但這個報表服務還有一個嚴重的問題。 剛開始我們提到過,由于沒有微服務體系,有些數據需要使用 HttpClient 來獲取進行補全。提供數據的服務有的響應時間可能會很長,也有可能會造成服務整體的阻塞。 ![](https://img.kancloud.cn/f7/c7/f7c7b3d0567a75042d22f068234b65cf_757x188.jpg) 如上圖所示,接口 A 通過 HttpClient 訪問服務 2,響應 100ms 后返回;接口 B 訪問服務 3,耗時 2 秒。HttpClient 本身是有一個最大連接數限制的,如果服務 3 遲遲不返回,就會造成 HttpClient 的連接數達到上限,最上層的 Tomcat 線程也會一直阻塞在這里,進而連響應速度比較快的接口 A 也無法正常提供服務。 這是出現頻率非常高的的一類故障,在工作中你會大概率遇見。概括來講,就是同一服務,由于一個耗時非常長的接口,進而引起了整體的服務不可用。 這個時候,通過 jstack 打印棧信息,會發現大多數竟然阻塞在了接口 A 上,而不是耗時更長的接口 B。這是一種錯覺,其實是因為接口 A 的速度比較快,在問題發生點進入了更多的請求,它們全部都阻塞住了。 證據本身具有非常強的迷惑性。由于這種問題發生的頻率很高,排查起來又比較困難,我這里專門做了一個小工程,用于還原解決這種問題的一個方式,參見 report-demo 工程。 demo 模擬了兩個使用同一個 HttpClient 的接口。如下圖所示,fast 接口用來訪問百度,很快就能返回;slow 接口訪問谷歌,由于眾所周知的原因,會阻塞直到超時,大約 10 s。? ![](https://img.kancloud.cn/1f/28/1f282c8aaf5bc7e7c56a56ca3ca850db_733x282.jpg) 使用 wrk 工具對這兩個接口發起壓測。 ``` wrk?-t10?-c200?-d300s?http://127.0.0.1:8084/slow wrk?-t10?-c200?-d300s?http://127.0.0.1:8084/fast ``` ![](https://img.kancloud.cn/31/72/31720be14549e796bce0fd9649cc1d43_684x277.jpg) 此時訪問一個簡單的接口,耗時竟然能夠達到 20 秒。 ``` time?curl?http://localhost:8084/stat fast648,slow:1curl?http://localhost:8084/stat??0.01s?user?0.01s?system?0%?cpu?20.937?total ``` 使用 jstack 工具 dump 堆棧。首先使用 jps 命令找到進程號,然后把結果重定向到文件(可以參考 10271.jstack 文件)。 過濾一下 nio 關鍵字,可以查看 tomcat 相關的線程,足足有 200 個,這和 Spring Boot 默認的 maxThreads 個數不謀而合。更要命的是,有大多數線程,都處于 BLOCKED 狀態,說明線程等待資源超時。 ``` cat?10271.jstack?|grep?http-nio-80?-A?3 ``` ![](https://img.kancloud.cn/cc/91/cc918bc113eb37f3747d6f81937325ef_793x429.jpg) 使用腳本分析,發現有大量的線程阻塞在 fast 方法上。我們上面也說過,這是一個假象,可能你到了這一步,會心生存疑,以至于無法再向下分析。 ``` $?cat?10271.jstack?|grep?fast?|?wc?-l ?????137 $?cat?10271.jstack?|grep?slow?|?wc?-l ??????63 ``` 分析棧信息,你可能會直接查找 locked 關鍵字,如下圖所示,但是這樣的方法一般沒什么用,我們需要做更多的統計。? ![](https://img.kancloud.cn/9f/86/9f86bb18701c647593c2bd872dadc384_733x158.jpg) 注意下圖中有一個處于 BLOCKED 狀態的線程,它阻塞在對鎖的獲取上(wating to lock)。大體瀏覽一下 DUMP 文件,會發現多處這種狀態的線程,可以使用如下腳本進行統計。? ![](https://img.kancloud.cn/1d/eb/1debc76f660d9deb25f12b626fac7a48_928x365.jpg) ``` cat?10271.tdump|?grep?"waiting?to?lock?"?|?awk?'{print?$5}'?|?sort?|?uniq?-c?|?sort?-k1?-r ??26?<0x0000000782e1b590> ??18?<0x0000000787b00448> ??16?<0x0000000787b38128> ??10?<0x0000000787b14558> ???8?<0x0000000787b25060> ???4?<0x0000000787b2da18> ???4?<0x0000000787b00020> ???2?<0x0000000787b6e8e8> ???2?<0x0000000787b03328> ???2?<0x0000000782e8a660> ???1?<0x0000000787b6ab18> ???1?<0x0000000787b2ae00> ???1?<0x0000000787b0d6c0> ???1?<0x0000000787b073b8> ???1?<0x0000000782fbcdf8> ???1?<0x0000000782e11200> ???1?<0x0000000782dfdae0> ``` 我們找到給 0x0000000782e1b590 上鎖的執行棧,可以發現全部卡在了 HttpClient 的讀操作上。在實際場景中,可以看下排行比較靠前的幾個鎖地址,找一下共性。? ![](https://img.kancloud.cn/c3/e8/c3e8dc6dec5fe384146e19708b1b0627_945x485.jpg) 返回頭去再看一下代碼。我們發現 HttpClient 是共用了一個連接池,當連接數超過 100 的時候,就會阻塞等待。它的連接超時時間是 10 秒,這和 slow 接口的耗時不相上下。? ``` private?final?static?HttpConnectionManager?httpConnectionManager?=?new?SimpleHttpConnectionManager(true); ????static?{ ????????HttpConnectionManagerParams?params?=?new?HttpConnectionManagerParams(); ????????params.setMaxTotalConnections(100); ????????params.setConnectionTimeout(1000?*?10); ????????params.setSoTimeout(defaultTimeout); ????????httpConnectionManager.setParams(params); ``` slow 接口和 fast 接口同時在爭搶這些連接,讓它時刻處在飽滿的狀態,進而讓 tomcat 的線程等待、占滿,造成服務不可用。 問題找到了,解決方式就簡單多了。我們希望 slow 接口在阻塞的時候,并不影響 fast 接口的運行。這就可以對某一類接口進行限流,或者對不重要的接口進行熔斷處理,這里不再深入講解(具體可參考 Spring Boot 的限流熔斷處理)。 現實情況是,對于一個運行的系統,我們并不知道是 slow 接口慢還是 fast 接口慢,這就需要加入一些額外的日志信息進行排查。當然,如果有一個監控系統能夠看到這些數據是再好不過了。 項目中的 HttpClientUtil2 文件,是改造后的一個版本。除了調大了連接數,它還使用了多線程版本的連接管理器(MultiThreadedHttpConnectionManager),這個管理器根據請求的 host 進行劃分,每個 host 的最大連接數不超過 20。還提供了 getConnectionsInPool 函數,用于查看當前連接池的統計信息。采用這些輔助的手段,可以快速找到問題服務,這是典型的情況。由于其他應用的服務水平低而引起的連鎖反應,一般的做法是熔斷、限流等,在此不多做介紹了。 #### jstack 產生的信息 為了觀測一些狀態,我上傳了幾個 Java 類,你可以實際運行一下,然后使用 jstack 來看一下它的狀態。 ``` waiting on condition ``` 示例參見 SleepDemo.java。 ``` public?class?SleepDemo?{ ????public?static?void?main(String[]?args)?{ ????????new?Thread(()->{ ????????????try?{ ????????????????Thread.sleep(Integer.MAX_VALUE); ????????????}?catch?(InterruptedException?e)?{ ????????????????e.printStackTrace(); ????????????} ????????},"sleep-demo").start(); ????} } ``` 這個狀態出現在線程等待某個條件的發生,來把自己喚醒,或者調用了 sleep 函數,常見的情況就是等待網絡讀寫,或者等待數據 I/O。如果發現大多數線程都處于這種狀態,證明后面的資源遇到了瓶頸。 此時線程狀態大致分為以下兩種: * java.lang.Thread.State: WAITING (parking):一直等待條件發生; * java.lang.Thread.State: TIMED_WAITING (parking 或 sleeping):定時的,即使條件不觸發,也將定時喚醒。 ``` "sleep-demo"?#12?prio=5?os_prio=31?cpu=0.23ms?elapsed=87.49s?tid=0x00007fc7a7965000?nid=0x6003?waiting?on?condition??[0x000070000756d000] ???java.lang.Thread.State:?TIMED_WAITING?(sleeping) ????at?java.lang.Thread.sleep(java.base@13.0.1/Native?Method) ????at?SleepDemo.lambda$main$0(SleepDemo.java:5) ????at?SleepDemo$$Lambda$16/0x0000000800b45040.run(Unknown?Source) ????at?java.lang.Thread.run(java.base@13.0.1/Thread.java:830) ``` 值的注意的是,Java 中的可重入鎖,也會讓線程進入這種狀態,但通常帶有 parking 字樣,parking 指線程處于掛起中,要注意區別。代碼可參見 LockDemo.java: ``` import?java.util.concurrent.locks.Lock; import?java.util.concurrent.locks.ReentrantLock; public?class?LockDemo?{ ????public?static?void?main(String[]?args)?{ ????????Lock?lock?=?new?ReentrantLock(); ????????lock.lock(); ????????new?Thread(()?->?{ ????????????try?{ ????????????????lock.lock(); ????????????}?finally?{ ????????????????lock.unlock(); ????????????} ????????},?"lock-demo").start(); ????} ``` 堆棧代碼如下: ``` "lock-demo"?#12?prio=5?os_prio=31?cpu=0.78ms?elapsed=14.62s?tid=0x00007ffc0b949000?nid=0x9f03?waiting?on?condition??[0x0000700005826000] ???java.lang.Thread.State:?WAITING?(parking) ????at?jdk.internal.misc.Unsafe.park(java.base@13.0.1/Native?Method) ????-?parking?to?wait?for??<0x0000000787cf0dd8>?(a?java.util.concurrent.locks.ReentrantLock$NonfairSync) ????at?java.util.concurrent.locks.LockSupport.park(java.base@13.0.1/LockSupport.java:194) ????at?java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(java.base@13.0.1/AbstractQueuedSynchronizer.java:885) ????at?java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(java.base@13.0.1/AbstractQueuedSynchronizer.java:917) ????at?java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@13.0.1/AbstractQueuedSynchronizer.java:1240) ????at?java.util.concurrent.locks.ReentrantLock.lock(java.base@13.0.1/ReentrantLock.java:267) ????at?LockDemo.lambda$main$0(LockDemo.java:11) ????at?LockDemo$$Lambda$14/0x0000000800b44840.run(Unknown?Source) ????at?java.lang.Thread.run(java.base@13.0.1/Thread.java:830) waiting for monitor entry ``` 我們上面提到的 HttpClient 例子,就是大部分處于這種狀態,線程都是 BLOCKED 的。這意味著它們都在等待進入一個臨界區,需要重點關注。 ``` "http-nio-8084-exec-120"?#143?daemon?prio=5?os_prio=31?cpu=122.86ms?elapsed=317.88s?tid=0x00007fedd8381000?nid=0x1af03?waiting?for?monitor?entry??[0x00007000150e1000] ???java.lang.Thread.State:?BLOCKED?(on?object?monitor) ????at?java.io.BufferedInputStream.read(java.base@13.0.1/BufferedInputStream.java:263) ????-?waiting?to?lock?<0x0000000782e1b590>?(a?java.io.BufferedInputStream) ????at?org.apache.commons.httpclient.HttpParser.readRawLine(HttpParser.java:78) ????at?org.apache.commons.httpclient.HttpParser.readLine(HttpParser.java:106) ????at?org.apache.commons.httpclient.HttpConnection.readLine(HttpConnection.java:1116) ????at?org.apache.commons.httpclient.HttpMethodBase.readStatusLine(HttpMethodBase.java:1973) ????at?org.apache.commons.httpclient.HttpMethodBase.readResponse(HttpMethodBase.java:1735) in Object.wait() ``` 示例代碼參見 WaitDemo.java: ``` public?class?WaitDemo?{ ????public?static?void?main(String[]?args)?throws?Exception?{ ????????Object?o?=?new?Object(); ????????new?Thread(()?->?{ ????????????try?{ ????????????????synchronized?(o)?{ ????????????????????o.wait(); ????????????????} ????????????}?catch?(InterruptedException?e)?{ ????????????????e.printStackTrace(); ????????????} ????????},?"wait-demo").start(); ????????Thread.sleep(1000); ????????synchronized?(o)?{ ????????????o.wait(); ????????} ????} ``` 說明在獲得了監視器之后,又調用了 java.lang.Object.wait() 方法。 關于這部分的原理,可以參見一張經典的圖。每個監視器(Monitor)在某個時刻,只能被一個線程擁有,該線程就是“Active Thread”,而其他線程都是“Waiting Thread”,分別在兩個隊列“Entry Set”和“Wait Set”里面等候。在“Entry Set”中等待的線程狀態是“Waiting for monitor entry”,而在“Wait Set”中等待的線程狀態是“in Object.wait()”。 ![](https://img.kancloud.cn/3e/58/3e58d329a8e569aac26599fbaa58be02_500x313.jpg) ``` "wait-demo"?#12?prio=5?os_prio=31?cpu=0.14ms?elapsed=12.58s?tid=0x00007fb66609e000?nid=0x6103?in?Object.wait()??[0x000070000f2bd000] ???java.lang.Thread.State:?WAITING?(on?object?monitor) ????at?java.lang.Object.wait(java.base@13.0.1/Native?Method) ????-?waiting?on?<0x0000000787b48300>?(a?java.lang.Object) ????at?java.lang.Object.wait(java.base@13.0.1/Object.java:326) ????at?WaitDemo.lambda$main$0(WaitDemo.java:7) ????-?locked?<0x0000000787b48300>?(a?java.lang.Object) ????at?WaitDemo$$Lambda$14/0x0000000800b44840.run(Unknown?Source) ????at?java.lang.Thread.run(java.base@13.0.1/Thread.java:830) ``` 死鎖 代碼參見 DeadLock.java: ``` public?class?DeadLockDemo?{ ????public?static?void?main(String[]?args)?{ ????????Object?object1?=?new?Object(); ????????Object?object2?=?new?Object(); ????????Thread?t1?=?new?Thread(()?->?{ ????????????synchronized?(object1)?{ ????????????????try?{ ????????????????????Thread.sleep(200); ????????????????}?catch?(InterruptedException?e)?{ ????????????????????e.printStackTrace(); ????????????????} ????????????????synchronized?(object2)?{ ????????????????} ????????????} ????????},?"deadlock-demo-1"); ????????t1.start(); ????????Thread?t2?=?new?Thread(()?->?{ ????????????synchronized?(object2)?{ ????????????????synchronized?(object1)?{ ????????????????} ????????????} ????????},?"deadlock-demo-2"); ????????t2.start(); ????} } ``` 死鎖屬于比較嚴重的一種情況,jstack 會以明顯的信息進行提示。 ``` Found?one?Java-level?deadlock: ============================= "deadlock-demo-1": ??waiting?to?lock?monitor?0x00007fe5e406f500?(object?0x0000000787cecd78,?a?java.lang.Object), ??which?is?held?by?"deadlock-demo-2" "deadlock-demo-2": ??waiting?to?lock?monitor?0x00007fe5e406d500?(object?0x0000000787cecd68,?a?java.lang.Object), ??which?is?held?by?"deadlock-demo-1" Java?stack?information?for?the?threads?listed?above: =================================================== "deadlock-demo-1": ????at?DeadLockDemo.lambda$main$0(DeadLockDemo.java:13) ????-?waiting?to?lock?<0x0000000787cecd78>?(a?java.lang.Object) ????-?locked?<0x0000000787cecd68>?(a?java.lang.Object) ????at?DeadLockDemo$$Lambda$14/0x0000000800b44c40.run(Unknown?Source) ????at?java.lang.Thread.run(java.base@13.0.1/Thread.java:830) "deadlock-demo-2": ????at?DeadLockDemo.lambda$main$1(DeadLockDemo.java:21) ????-?waiting?to?lock?<0x0000000787cecd68>?(a?java.lang.Object) ????-?locked?<0x0000000787cecd78>?(a?java.lang.Object) ????at?DeadLockDemo$$Lambda$16/0x0000000800b45040.run(Unknown?Source) ????at?java.lang.Thread.run(java.base@13.0.1/Thread.java:830) Found?1?deadlock ``` 當然,關于線程的 dump,也有一些線上分析工具可以使用。下圖是 fastthread 的一個分析結果,但也需要你先了解這些情況發生的意義。 ![](https://img.kancloud.cn/b2/68/b268ebc14340f99aeb8b6b9315a7f8ba_1120x597.jpg) #### 小結 本課時主要介紹了一個處處有問題的報表系統,并逐步解決了它的 OOM 問題,同時定位到了拒絕服務的原因。 在研發資源不足的時候,我們簡單粗暴的進行了硬件升級,并切換到了更加優秀的 G1 垃圾回收器,還通過代碼手段進行了問題的根本解決: * 縮減查詢的字段,減少常駐內存的數據; * 去掉不必要的、命中率低的堆內緩存,改為分布式緩存; * 從產品層面限制了單次請求對內存的無限制使用。 在這個過程中,使用 MAT 分析堆數據進行問題代碼定位,幫了大忙。代碼優化的手段是最有效的,改造完畢后,可以節省更多的硬件資源。事實上,使用了 G1 垃圾回收器之后,那些亂七八糟的調優參數越來越少用了。 接下來,我們使用 jstack 分析了一個出現頻率非常非常高的問題,主要是不同速度的接口在同一應用中的資源競爭問題,我們發現一些成熟的微服務框架,都會對這些資源進行限制和隔離。 最后,以 4 個簡單的示例,展示了 jstack 輸出內容的一些意義。代碼都在 git 倉庫里,你可以實際操作一下,希望對你有所幫助。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看