<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                本課時我們主要從一個實戰案例入手分析面對突如其來的 GC 問題該如何下手解決。 想要下手解決 GC 問題,我們首先需要掌握下面這三種問題。 * 如何使用 jstat 命令查看 JVM 的 GC 情況? * 面對海量 GC 日志參數,如何快速抓住問題根源? * 你不得不掌握的日志分析工具。 工欲善其事,必先利其器。我們前面課時講到的優化手段,包括代碼優化、擴容、參數優化,甚至我們的估算,都需要一些支撐信息加以判斷。 ![](https://img.kancloud.cn/a7/fb/a7fb61260c69e9deefc44088d303383c_758x362.jpg) 對于 JVM 來說,一種情況是 GC 時間過長,會影響用戶的體驗,這個時候就需要調整某些 JVM 參數、觀察日志。 另外一種情況就比較嚴重了,發生了 OOM,或者操作系統的內存溢出。服務直接宕機,我們要尋找背后的原因。 這時,GC 日志能夠幫我們找到問題的根源。本課時,我們就簡要介紹一下如何輸出這些日志,以及如何使用這些日志的支撐工具解決問題。 #### GC 日志輸出 你可能感受到,最近幾年 Java 的版本更新速度是很快的,JVM 的參數配置其實變化也很大。就拿 GC 日志這一塊來說,Java 9 幾乎是推翻重來。網絡上的一些文章,把這些參數寫的亂七八糟,根本不能投入生產。如果你碰到不能被識別的參數,先確認一下自己的 Java 版本。 在事故出現的時候,通常并不是那么溫柔。你可能在半夜里就能接到報警電話,這是因為很多定時任務都設定在夜深人靜的時候執行。 這個時候,再去看 jstat 已經來不及了,我們需要保留現場。這個便是看門狗的工作,看門狗可以通過設置一些 JVM 參數進行配置。 那在實踐中,要怎么用呢?請看下面命令行。 #### Java 8 我們先看一下 JDK8 中的使用。 ``` #!/bin/sh LOG_DIR="/tmp/logs" JAVA_OPT_LOG="?-verbose:gc" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-XX:+PrintGCDetails" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-XX:+PrintGCDateStamps" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-XX:+PrintGCApplicationStoppedTime" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-XX:+PrintTenuringDistribution" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-Xloggc:${LOG_DIR}/gc_%p.log" JAVA_OPT_OOM="?-XX:+HeapDumpOnOutOfMemoryError?-XX:HeapDumpPath=${LOG_DIR}?-XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log?" JAVA_OPT="${JAVA_OPT_LOG}?${JAVA_OPT_OOM}" JAVA_OPT="${JAVA_OPT}?-XX:-OmitStackTraceInFastThrow" ``` 合成一行。 ``` -verbose:gc?-XX:+PrintGCDetails?-XX:+PrintGCDateStamps? -XX:+PrintGCApplicationStoppedTime?-XX:+PrintTenuringDistribution? -Xloggc:/tmp/logs/gc_%p.log?-XX:+HeapDumpOnOutOfMemoryError? -XX:HeapDumpPath=/tmp/logs?-XX:ErrorFile=/tmp/logs/hs_error_pid%p.log? -XX:-OmitStackTraceInFastThrow ``` 然后我們來解釋一下這些參數: | 參數 | 意義 | | --- | --- | | -verbose:gc | 打印 GC 日志 | | PrintGCDetails | 打印詳細 GC 日志 | | PrintGCDateStamps | 系統時間,更加可讀,PrintGCTimeStamps 是 JVM 啟動時間 | | PrintGCApplicationStoppedTime | 打印 STW 時間 | | PrintTenuringDistribution | 打印對象年齡分布,對調優 MaxTenuringThreshold 參數幫助很大 | | loggc | 將以上 GC 內容輸出到文件中 | 再來看下 OOM 時的參數: | 參數 | 意義 | | --- | --- | | HeapDumpOnOutOfMemoryError | OOM 時 Dump 信息,非常有用 | | HeapDumpPath | Dump 文件保存路徑 | | ErrorFile | 錯誤日志存放路徑 | 注意到我們還設置了一個參數 OmitStackTraceInFastThrow,這是 JVM 用來縮簡日志輸出的。 開啟這個參數之后,如果你多次發生了空指針異常,將會打印以下信息。 ``` java.lang.NullPointerException java.lang.NullPointerException java.lang.NullPointerException java.lang.NullPointerException ``` 在實際生產中,這個參數是默認開啟的,這樣就導致有時候排查問題非常不方便(很多研發對此無能為力),我們這里把它關閉,但這樣它會輸出所有的異常堆棧,日志會多很多。 #### Java 13 再看下 JDK 13 中的使用。 從 Java 9 開始,移除了 40 多個 GC 日志相關的參數。具體參見 JEP 158。所以這部分的日志配置有很大的變化。 我們同樣看一下它的生成腳本。 ``` #!/bin/sh LOG_DIR="/tmp/logs" JAVA_OPT_LOG="?-verbose:gc" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file=${LOG_DIR}/gc_%p.log:tags,uptime,time,level" JAVA_OPT_LOG="${JAVA_OPT_LOG}?-Xlog:safepoint:file=${LOG_DIR}/safepoint_%p.log:tags,uptime,time,level" JAVA_OPT_OOM="?-XX:+HeapDumpOnOutOfMemoryError?-XX:HeapDumpPath=${LOG_DIR}?-XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log?" JAVA_OPT="${JAVA_OPT_LOG}?${JAVA_OPT_OOM}" JAVA_OPT="${JAVA_OPT}?-XX:-OmitStackTraceInFastThrow" echo?$JAVA_OPT ``` 合成一行展示。 ``` -verbose:gc?-Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file =/tmp/logs/gc_%p.log:tags,uptime,time,level?-Xlog:safepoint:file=/tmp /logs/safepoint_%p.log:tags,uptime,time,level?-XX:+HeapDumpOnOutOfMemoryError? -XX:HeapDumpPath=/tmp/logs?-XX:ErrorFile=/tmp/logs/hs_error_pid%p.log? -XX:-OmitStackTraceInFastThrow ``` 可以看到 GC 日志的打印方式,已經完全不一樣,但是比以前的日志參數規整了許多。 我們除了輸出 GC 日志,還輸出了 safepoint 的日志。這個日志對我們分析問題也很重要,那什么叫 safepoint 呢? safepoint 是 JVM 中非常重要的一個概念,指的是可以安全地暫停線程的點。 當發生 GC 時,用戶線程必須全部停下來,才可以進行垃圾回收,這個狀態我們可以認為 JVM 是安全的(safe),整個堆的狀態是穩定的。 ![](https://img.kancloud.cn/f2/8f/f28f81055bf88e187ffd235801fb90ea_757x350.jpg) 如果在 GC 前,有線程遲遲進入不了 safepoint,那么整個 JVM 都在等待這個阻塞的線程,會造成了整體 GC 的時間變長。 所以呢,并不是只有 GC 會掛起 JVM,進入 safepoint 的過程也會。這個概念,如果你有興趣可以自行深挖一下,一般是不會出問題的。 如果面試官問起你在項目中都使用了哪些打印 GC 日志的參數,上面這些信息肯定是不很好記憶。你需要進行以下總結。比如: “我一般在項目中輸出詳細的 GC 日志,并加上可讀性強的 GC 日志的時間戳。特別情況下我還會追加一些反映對象晉升情況和堆詳細信息的日志,用來排查問題。另外,OOM 時自動 Dump 堆棧,我一般也會進行配置”。 #### GC 日志的意義 我們首先看一段日志,然后簡要看一下各個階段的意義。 ![](https://img.kancloud.cn/4b/1b/4b1b07dc1087cf6958eb5526699c8d57_766x482.jpg) * 1 表示 GC 發生的時間,一般使用可讀的方式打印; * 2 表示日志表明是 G1 的“轉移暫停: 混合模式”,停頓了約 223ms; * 3 表明由 8 個 Worker 線程并行執行,消耗了 214ms; * 4 表示 Diff 越小越好,說明每個工作線程的速度都很均勻; * 5 表示外部根區掃描,外部根是堆外區。JNI 引用,JVM 系統目錄,Classloaders 等; * 6 表示更新 RSet 的時間信息; * 7 表示該任務主要是對 CSet 中存活對象進行轉移(復制); * 8 表示花在 GC 之外的工作線程的時間; * 9 表示并行階段的 GC 總時間; * 10 表示其他清理活動; * 11表示收集結果統計; * 12 表示時間花費統計。 可以看到 GC 日志描述了垃圾回收器過程中的幾乎每一個階段。但即使你了解了這些數值的意義,在分析問題時,也會感到吃力,我們一般使用圖形化的分析工具進行分析。 尤其注意的是最后一行日志,需要詳細描述。可以看到 G C花費的時間,竟然有 3 個數值。這個數值你可能在多個地方見過。如果你手頭有 Linux 機器,可以執行以下命令: time?ls?/ ![](https://img.kancloud.cn/fe/32/fe32e24120af7688453a591fcd7b70b0_195x124.jpg) 可以看到一段命令的執行,同樣有三種緯度的時間統計。接下來解釋一下這三個字段的意思。 * real 實際花費的時間,指的是從開始到結束所花費的時間。比如進程在等待 I/O 完成,這個阻塞時間也會被計算在內; * user 指的是進程在用戶態(User Mode)所花費的時間,只統計本進程所使用的時間,注意是指多核; * sys 指的是進程在核心態(Kernel Mode)花費的 CPU 時間量,指的是內核中的系統調用所花費的時間,只統計本進程所使用的時間。 在上面的 GC 日志中,real < user + sys,因為我們使用了多核進行垃圾收集,所以實際發生的時間比 (user + sys) 少很多。在多核機器上,這很常見。 [Times: user=1.64 sys=0.00, real=0.23 secs] 下面是一個串行垃圾收集器收集的 GC 時間的示例。由于串行垃圾收集器始終僅使用一個線程,因此實際使用的時間等于用戶和系統時間的總和: [Times: user=0.29 sys=0.00, real=0.29 secs] 那我們統計 GC 以哪個時間為準呢?一般來說,用戶只關心系統停頓了多少秒,對實際的影響時間非常感興趣。至于背后是怎么實現的,是多核還是單核,是用戶態還是內核態,它們都不關心。所以我們直接使用 real 字段。 #### GC日志可視化 肉眼可見的這些日志信息,讓人非常頭暈,尤其是日志文件特別大的時候。所幸現在有一些在線分析平臺,可以幫助我們分析這個過程。下面我們拿常用的 gceasy 來看一下。 以下是一個使用了 G1 垃圾回收器,堆內存為 6GB 的服務,運行 5 天的 GC 日志。 (1)堆信息 ![](https://img.kancloud.cn/4f/6c/4f6cf4eb70bb15122296bc50776c5564_757x250.jpg) 我們可以從圖中看到堆的使用情況。 (2)關鍵信息 從圖中我們可以看到一些性能的關鍵信息。 吞吐量:98.6%(一般超過 95% 就 ok 了); 最大延遲:230ms,平均延遲:42.8ms; 延遲要看服務的接受程度,比如 SLA 定義 50ms 返回數據,上面的最大延遲就會有一點問題。本服務接近 99% 的停頓在 100ms 以下,可以說算是非常優秀了。 ![](https://img.kancloud.cn/fe/35/fe35947825fc6b898da6410073fce65f_757x313.jpg) 你在看這些信息的時候,一定要結合宿主服務器的監控去看。比如 GC 發生期間,CPU 會突然出現尖鋒,就證明 GC 對 CPU 資源使用的有點多。但多數情況下,如果吞吐量和延遲在可接受的范圍內,這些對 CPU 的超額使用是可以忍受的。 (3)交互式圖表 ![](https://img.kancloud.cn/b7/21/b721815812b356f8c1d66ce798e5d048_758x416.jpg) 可以對有問題的區域進行放大查看,圖中表示垃圾回收后的空間釋放,可以看到效果是比較好的。 (4)G1 的時間耗時 ![](https://img.kancloud.cn/1c/b1/1cb10031ada09fe5c3e652d87947be4b_757x459.jpg) 如圖展示了 GC 的每個階段花費的時間。可以看到平均耗時最長的階段,就是 Concurrent Mark 階段,但由于是并發的,影響并不大。隨著時間的推移,YoungGC 竟然達到了 136485 次。運行 5 天,光花在 GC 上的時間就有 2 個多小時,還是比較可觀的。 (5)其他 ![](https://img.kancloud.cn/01/50/015085439e302cce0b681182fd8d60cf_758x496.jpg) 如圖所示,整個 JVM 創建了 100 多 T 的數據,其中有 2.4TB 被 promoted 到老年代。 另外,還有一些 safepoint 的信息等,你可以自行探索。 那到底什么樣的數據才是有問題的呢?gceasy 提供了幾個案例。比如下面這個就是停頓時間明顯超長的 GC 問題。 ![](https://img.kancloud.cn/82/46/8246c8dff62f3b1a09560df133966297_757x323.jpg) 下面這個是典型的內存泄漏。 ![](https://img.kancloud.cn/6b/ba/6bba2ff8bb48da40dc18474f85cae695_757x374.jpg) 上面這些問題都是非常明顯的。但大多數情況下,問題是偶發的。從基本的衡量指標,就能考量到整體的服務水準。如果這些都沒有問題,就要看曲線的尖峰。 一般來說,任何不平滑的曲線,都是值得懷疑的,那就需要看一下當時的業務情況具體是什么樣子的。是用戶請求突增引起的,還是執行了一個批量的定時任務,再或者查詢了大批量的數據,這要和一些服務的監控一起看才能定位出根本問題。 只靠 GC 來定位問題是比較困難的,我們只需要知道它有問題就可以了。后面,會介紹更多的支持工具進行問題的排解。 為了方便你調試使用,我在 GitHub 上上傳了兩個 GC 日志。其中 gc01.tar.gz 就是我們現在正在看的,解壓后有 200 多兆;另外一個 gc02.tar.gz 是一個堆空間為 1GB 的日志文件,你也可以下載下來體驗一下。 GitHub 地址: https://gitee.com/xjjdog/jvm-lagou-res 另外,GCViewer 這個工具也是常用的,可以下載到本地,以 jar 包的方式運行。 在一些極端情況下,也可以使用腳本簡單過濾一下。比如下面行命令,就是篩選停頓超過 100ms 的 GC 日志和它的行數(G1)。 ``` #?grep?-n?real?gc.log?|?awk?-F"=|?"?'{?if($8>0.1){?print?}}' 1975:?[Times:?user=2.03?sys=0.93,?real=0.75?secs] 2915:?[Times:?user=1.82?sys=0.65,?real=0.64?secs] 16492:?[Times:?user=0.47?sys=0.89,?real=0.35?secs] 16627:?[Times:?user=0.71?sys=0.76,?real=0.39?secs] 16801:?[Times:?user=1.41?sys=0.48,?real=0.49?secs] 17045:?[Times:?user=0.35?sys=1.25,?real=0.41?secs] ``` #### jstat 上面的可視化工具,必須經歷導出、上傳、分析三個階段,這種速度太慢了。有沒有可以實時看堆內存的工具? 你可能會第一時間想到 jstat 命令。第一次接觸這個命令,我也是很迷惑的,主要是輸出的字段太多,不了解什么意義。 但其實了解我們在前幾節課時所講到內存區域劃分和堆劃分之后,再看這些名詞就非常簡單了。 ![](https://img.kancloud.cn/a8/3a/a83ae48fb3e4885140370b0438a3e8d6_757x335.jpg) 我們拿 -gcutil 參數來說明一下。 ``` jstat -gcutil $pid 1000 ``` 只需要提供一個 Java 進程的 ID,然后指定間隔時間(毫秒)就 OK 了。 ``` S0?S1?E?O?M?CCS?YGC?YGCT?FGC?FGCT?GCT 0.00?0.00?72.03?0.35?54.12?55.72?11122?16.019?0?0.000?16.019 0.00?0.00?95.39?0.35?54.12?55.72?11123?16.024?0?0.000?16.024 0.00?0.00?25.32?0.35?54.12?55.72?11125?16.025?0?0.000?16.025 0.00?0.00?37.00?0.35?54.12?55.72?11126?16.028?0?0.000?16.028 0.00?0.00?60.35?0.35?54.12?55.72?11127?16.028?0?0.000?16.028 ``` 可以看到,E 其實是 Eden 的縮寫,S0 對應的是 Surivor0,S1 對應的是 Surivor1,O 代表的是 Old,而 M 代表的是 Metaspace。 YGC 代表的是年輕代的回收次數,YGC T對應的是年輕代的回收耗時。那么 FGC 肯定代表的是 Full GC 的次數。 你在看日志的時候,一定要注意其中的規律。-gcutil 位置的參數可以有很多種。我們最常用的有 gc、gcutil、gccause、gcnew 等,其他的了解一下即可。 * gc: 顯示和 GC 相關的?堆信息; * gcutil: 顯示?垃圾回收信息; * gccause: 顯示垃圾回收?的相關信息(同 -gcutil),同時顯示?最后一次?或?當前?正在發生的垃圾回收的?誘因; * gcnew: 顯示?新生代?信息; * gccapacity: 顯示?各個代?的?容量?以及?使用情況; * gcmetacapacity: 顯示?元空間?metaspace 的大小; * gcnewcapacity: 顯示?新生代大小?和?使用情況; * gcold: 顯示?老年代?和?永久代?的信息; * gcoldcapacity: 顯示?老年代?的大小; * printcompilation: 輸出 JIT?編譯?的方法信息; * class: 顯示?類加載?ClassLoader 的相關信息; * compiler: 顯示 JIT?編譯?的相關信息; 如果 GC 問題特別明顯,通過 jstat 可以快速發現。我們在啟動命令行中加上參數 -t,可以輸出從程序啟動到現在的時間。如果 FGC 和啟動時間的比值太大,就證明系統的吞吐量比較小,GC 花費的時間太多了。另外,如果老年代在 Full GC 之后,沒有明顯的下降,那可能內存已經達到了瓶頸,或者有內存泄漏問題。 下面這行命令,就追加了 GC 時間的增量和 GC 時間比率兩列。 ``` jstat?-gcutil?-t?90542?1000?|?awk?'BEGIN{pre=0}{if(NR>1)?{print?$0?"\t"?($12-pre)?"\t"?$12*100/$1?;?pre=$12?}?else?{?print?$0?"\tGCT_INC\tRate"}?}' ? Timestamp?????????S0?????S1?????E??????O??????M?????CCS????YGC?????YGCT????FGC????FGCT?????GCT????GCT_INC?Rate ???????????18.7???0.00?100.00???6.02???1.45??84.81??76.09??????1????0.002?????0????0.000????0.002?0.002?0.0106952 ???????????19.7???0.00?100.00???6.02???1.45??84.81??76.09??????1????0.002?????0????0.000????0.002?0?0.0101523 ``` #### GC 日志也會搞鬼 順便給你介紹一個實際發生的故障。 你知道 ElasticSearch 的速度是非常快的,我們為了壓榨它的性能,對磁盤的讀寫幾乎是全速的。它在后臺做了很多 Merge 動作,將小塊的索引合并成大塊的索引。還有 TransLog 等預寫動作,都是 I/O 大戶。 使用 iostat -x 1 可以看到具體的 I/O 使用狀況。 問題是,我們有一套 ES 集群,在訪問高峰時,有多個 ES 節點發生了嚴重的 STW 問題。有的節點竟停頓了足足有 7~8 秒。 ?[Times: user=0.42 sys=0.03, real=7.62 secs]? 從日志可以看到在 GC 時用戶態只停頓了 420ms,但真實的停頓時間卻有 7.62 秒。 盤點一下資源,唯一超額利用的可能就是 I/O 資源了(%util 保持在 90 以上),GC 可能在等待 I/O。 通過搜索,發現已經有人出現過這個問題,這里直接說原因和結果。 原因就在于,寫 GC 日志的 write 動作,是統計在 STW 的時間里的。在我們的場景中,由于 ES 的索引數據,和 GC 日志放在了一個磁盤,GC 時寫日志的動作,就和寫數據文件的動作產生了資源爭用。 ![](https://img.kancloud.cn/2d/4b/2d4b19237bf7e2fdb8cebbdba28c9a38_757x184.jpg) 解決方式也是比較容易的,把 ES 的日志文件,單獨放在一塊普通 HDD 磁盤上就可以了。 #### 小結 本課時,我們主要介紹了比較重要的 GC 日志,以及怎么輸出它,并簡要的介紹了一段 G1 日志的意義。對于這些日志的信息,能夠幫助我們理解整個 GC 的過程,專門去記憶它投入和產出并不成正比,可以多看下 G1 垃圾回收器原理方面的東西。 接下來我們介紹了幾個圖形化分析 GC 的工具,這也是現在主流的使用方式,因為動輒幾百 MB 的 GC 日志,是無法肉眼分辨的。如果機器的 I/O 問題很突出,就要考慮把 GC 日志移動到單獨的磁盤。 我們尤其介紹了在線分析工具 gceasy,你也可以下載 gcviewer 的 jar 包本地體驗一下。 最后我們看了一個命令行的 GC 回收工具 jstat,它的格式比較規整,可以重定向到一個日志文件里,后續使用 sed、awk 等工具進行分析。關于相關的兩個命令,可以參考我以前寫的兩篇文章。 《Linux生產環境上,最常用的一套“Sed“技巧》 《Linux生產環境上,最常用的一套“AWK“技巧》 #### 課后問答 * 1、如果幸存區中相同年齡對象大小的和,大于幸存區的一半,大于或等于 age 的對象將會直接進入老年代”應該為小于等于某一年齡的對象大小總和? 答案:感謝提醒,參考代碼share/gc/shared/ageTable.cpp中的compute_tenuring_threshold函數,重新表述如下:從年齡最小的對象開始累加,如果累加的對象大小,大于幸存區的一半,則講當前的對象age將作為新的閾值,年齡大于此閾值的對象直接進入老年代。 * 2、另外“幸存區的一半”最好提一下 TargetSurvivorRatio 這個參數。 答案:值的注意的是。使用“grep -rn -i --color TargetSurvivorRatio .”搜索(jdk13),可以看到這個參數只影響serial和G1收集器,還稍微影響PLAB緩沖區的大小。 * 3、完整的項目代碼么? 答案:https://gitee.com/xjjdog/jvm-lagou-res * 4、枚舉類的內存模型是啥 答案:java類和字節碼,沒有內存模型這個概念。Java的內存模型,指的是JMM,與多線程協作有關。像堆、虛擬機棧這些劃分,也不叫內存模型,叫內存布局。這個千萬別搞混了。 你應該是說enum的字節碼表現形式。可以使用javac E.java && javap -v -p? E看一下輸出。 public enum E{ A, B, C, D } javap輸出: public final class E extends java.lang.Enum<E> 可以看到enum只是一種語法上的便捷方式,繼承的是Enum類。 * 5、“由于常量池,在 Java 7 之后,放到了堆中,我們創建的字符串,將會在堆上分配”。但是您上文也說了,JAVA8開始,metasapce是非堆區域,而且文中也提到了該區域包含的內容是類的信息、常量池、方法數據、方法代碼。那么java字符串常量,就不應該在堆上創建了啊。勞煩您解釋下,添麻煩了,謝謝。 答案:你好,JVM中存在多個常量池。把第一個改成字符串常量池就比較好理解了。 1、字符串常量池,已經移動到堆上(jdk8之前是perm區),也就是執行intern方法后存的地方。 2、類文件常量池,constant_pool,是每個類每個接口所擁有的,第四節字節碼中“#n”的那些都是。這部分數據在方法區,也就是元數據區。而運行時常量池是在類加載后的一個內存區域,它們都在元空間。 * 6、大家都知道,JVM 在運行時,會從操作系統申請大塊的堆內內存,進行數據的存儲。但是,堆外內存也就是申請后操作系統剩余的內存,也會有部分受到 JVM 的控制。比較典型的就是一些 native 關鍵詞修飾的方法,以及對內存的申請和處理 這句話是jvm去申請了一塊操作系統的堆內內存,那圖上怎么jvm申請的內存包括了堆內存和非堆內存,有點疑惑。就是jvm申請的內存其實有這兩個? 答案:可以這樣理解: 操作系統有8G。-Xmx分配了4G(堆內內存),Metaspace使用了256M(堆外內存) 剩下的 8G-4G-256M ,就是操作系統剩下的本地內存。具體有沒有可能變成堆外內存,要看情況。 比如: (1)netty的direct buffer使用了額外的120MB內存,那么現在JVM占用的堆外內存就有 256M+120M (2)使用了jni或者jna,直接申請了內存2GB,那么現在JVM占用的堆外內存就有256M+120M+2GB (3)網絡socket連接等,占用了操作系統的50MB內存 這個時候,留給操作系統的就只剩下了:8GB-4GB-256M-120M-2GB-50M。具體“堆和堆外”一共用了多少,可以top命令,看RSS段。 * 7、被final修飾的成員變量,會被gc嗎,可否細致的講下。 答案:您好,對象是否被GC,和是否是final變量沒有關系。關于final,我們將在19小節的并發編程中詳細介紹。 * 8、想知道類加載器是加載字節碼變成機器碼給執行引擎去執行的,那么類加載器是誰來加載的? 答案:啟動類加載器,就是最上面那一個,是c代碼實現的,沒有繼承classloader類。它就是一段native邏輯,所以沒有加載這種概念。它的實現參考${openjdk}\hotspot\src\share\vm\classfile 目錄下的 classLoader.cpp 與classLoader.hpp * 9、JMM 保證了 read、load、assign、use、store 和 write 六個操作具有原子性,可以認為除了 long 和 double 類型以外,對其他基本數據類型所對應的內存單元的訪問讀寫都是原子的。long和double沒有原子性? 答案:目前大多數機器是64位的,你可以認為是原子的。這是因為,在32位操作系統上對64位的數據的讀寫要分兩步完成,每一步取32位數據。隨著時間推移,這種知識點會越來越冷。 * 10、字節碼的執行流程可以這樣理解嗎?字節碼在Java虛擬機棧中被執行,每一項內容都可以看作是一個棧幀,棧幀的結構包括局部變量表、操作數棧、鏈接、返回地址。這時候就很明了了,棧幀的執行流程就是字節碼的執行流程了。類中變量會被解析到局部變量表,然后對操作數棧進行入棧出棧的操作,在此期間有可能引用到動態或靜態鏈接,最后把計算結果的引用地址返回。不知道這個理解是否正確,麻煩老師指正,謝謝? 答案:正確 * 11、文中說除了基本類型,其他都是在堆上分配的。之前粗略在哪個地方看到過jvm會判斷對象是否存在線程逃逸,如果不存在就直接在棧上分配對象。這種在棧上創建對象的情況是怎樣的呢。 答案:你說的沒錯,這種情況在第6小節已聊到了。不過它是分層編譯的優化手段,所以我們在后面JIT小節還會碰到它。 * 12、“它只是自定義的加載器順序不同,但對于頂層來說,還是一樣的。”這句話是什么意思呢?為什么自己寫的ArrayList不會被加載? 答案:loadClass的邏輯是可以非常靈活的,以下代碼來自tomcat-9.0.30。 // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding Java SE classes. This implements // SRV.10.7.2 String resourceName = binaryNameToPath(name, false); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; 第一步就是嘗試從javabase加載哦,加載不到才走其他邏輯。感興趣可以參照WebappClassLoaderBase.java文件。 * 13、老師您好,這里對于虛擬機棧有點不太理解,原文:這里有一個兩層的棧。第一層是棧幀,對應著方法;第二層是方法的執行,對應著操作數棧;這里是說棧幀是說具體的Java方法,而真正的調用,是在棧幀中里面還建了一個操作數棧對嗎?為什么要這么做呀? 答案:線程方法棧(棧)->棧幀(元素)=>方法級別的操作。 棧幀里的操作數棧(棧)->操作數(元素)=> 字節碼指令級的操作。 主管的功能不同,層次也不同。 * 14、從文章中copy 命令是不能直接運行的,手動敲可以,肉眼對比看不出有什么不同,試后發現是copy的問題是在空格,刪了“空格”,重新輸入空格正常。是故意這樣做,還是有問題?? 答案:可能是顯示問題,很多編輯器編輯之后都這樣
                  <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>

                              哎呀哎呀视频在线观看