Java Troubleshooting 整理

Troubleshooting 整理

  • kill -3 pid
    • 發(fā)送一個SIGQUIT信號給Java應(yīng)用, 通常會有當(dāng)前的Thread Dump輸出
    • 假定這個程序在JVM初始化之后沒有別的代碼注冊了新的SIGQUIT的signal handler,那么HotSpot VM在收到SIGQUIT之后會在一個專門的signal handler thread處理。該線程的入口函數(shù)為signal_thread_entry()
    • http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/tip/src/share/vm/runtime/os.cpp
    • 打印線程棧的動作由VM_PrintThreads實現(xiàn),在VM thread上執(zhí)行。于是問題就來了:如果這個JVM實例已經(jīng)hang了,那它將無法響應(yīng)任何外部請求,對它發(fā)SIGQUIT當(dāng)然也得不到處理。所以有時候kill -3看不到線程棧是正常的。
    • 至于時效性,所有VM operation都是先被放到一個隊列里,然后由VM thread逐個處理。如果當(dāng)前該隊列是空的,那kill -3就可以幾乎“實時”執(zhí)行爬棧動作,否則得等前面的VM operation先完成,那就會延遲一會兒。

  • Linux core dump
    • concept
      • A core dump is the recorded state of the working memory of a computer program at a specific time, generally when the program has terminated abnormally (crashed). In practice, other key pieces of program state are usually dumped at the same time, including the processor registers, which may include the program counter and stack pointer, memory management information, and other processor and operating system flags and information. The name comes from the once-standard memory technology core memory. Core dumps are often used to diagnose or debug errors in computer programs.
      • On many operating systems, a fatal error in a program automatically triggers a core dump, and by extension the phrase "to dump core" has come to mean, in many cases, any fatal error, regardless of whether a record of the program memory is created.
    • 檢查是否可以core dump
      • ulimit -a (or unlimit -c只查看core file size)
        core file size (blocks, -c) 0
        ...
      • 修改限制
        • 臨時 可以使用參數(shù)unlimited,取消該限制ulimit -c unlimited
        • 永久 echo "ulimit -c 1024" >> /etc/profile (1024 限制產(chǎn)生的 core 文件的大小不能超過 1024kb)
    • 在一個程序崩潰時,它一般會在指定目錄下生成一個 core 文件。 core 文件僅僅是一個內(nèi)存映象 ( 同時加上調(diào)試信息 ) ,主要是用來調(diào)試的。
    • 設(shè)置 Core Dump 的核心轉(zhuǎn)儲文件目錄和命名規(guī)則
      • /proc/sys/kernel/core_uses_pid 可以控制產(chǎn)生的 core 文件的文件名中是否添加 pid 作為擴展 ,如果添加則文件內(nèi)容為 1 ,否則為 0
      • /proc/sys/kernel/core_pattern 可以設(shè)置格式化的 core 文件保存位置或文件名 ,比如原來文件內(nèi)容是 core-%e 可以這樣修改 : echo "/corefile/core-%e-%p-%t" > core_pattern; 將會控制所產(chǎn)生的 core 文件會存放到 /corefile 目錄下,產(chǎn)生的文件名為 core- 命令名 -pid- 時間戳
    • kill -l 查看信號
      • 看到SIGSEGV在其中,一般數(shù)組越界或是訪問空指針都會產(chǎn)生這個信號
      • 指示進程進行了一次無效的存儲訪問。名字SEGV表示“段違例(segmentation violation)
    • gdb core調(diào)試
    • HSDB R大-HSDB

  • JVM調(diào)優(yōu)-標(biāo)準(zhǔn)參數(shù)-的一些陷阱
    • 各參數(shù)的默認值
      • 1.參考HotSpot VM里的各個globals.hpp文件 本帖子列出一些
      • 2.-XX:+PrintCommandLineFlags
        • 顯示出VM初始化完畢后所有跟最初的默認值不同的參數(shù)及它們的值
      • 3.-XX:+PrintFlagsFinal (這個參數(shù)本身只從JDK 6 update 21開始才可以用)
        • 前一個參數(shù)只顯示跟默認值不同的,而這個參數(shù)則可以顯示所有可設(shè)置的參數(shù)及它們的值
        • 可以設(shè)置的參數(shù)默認是不包括diagnostic或experimental系的。要在-XX:+PrintFlagsFinal的輸出里看到這兩種參數(shù)的信息,分別需要顯式指定-XX:+UnlockDiagnosticVMOptions / -XX:+UnlockExperimentalVMOptions
      • 4,-XX:+PrintFlagsInitial
        • 這個參數(shù)顯示在處理參數(shù)之前所有可設(shè)置的參數(shù)及它們的值,然后直接退出程序?!皡?shù)處理”包括許多步驟,例如說檢查參數(shù)之間是否有沖突,通過ergonomics調(diào)整某些參數(shù)的值,之類的
        • 結(jié)合-XX:+PrintFlagsInitial與-XX:+PrintFlagsFinal,對比兩者的差異,就可以知道ergonomics對哪些參數(shù)做了怎樣的調(diào)整
        • $ java -XX:+PrintFlagsInitial | grep UseCompressedOops
          bool UseCompressedOops = false {lp64_product}
          $ java -XX:+PrintFlagsFinal | grep UseCompressedOops
          bool UseCompressedOops = true {lp64_product}
          Oracle JDK從6 update 23開始在64位系統(tǒng)上會默認開啟壓縮指針
      • 5.jinfo -flag 可以用來查看某個參數(shù)的值,也可以用來設(shè)定manageable系參數(shù)的值
      • PrintFlagsInitial->ergonomics->命令指定->PrintFlagsFinal->jinfo

  • 陷阱1
    • -XX:+DisableExplicitGC 與 NIO的direct memory
      • System.gc()的默認效果是引發(fā)一次stop-the-world的full GC,對整個GC堆做收集。有幾個參數(shù)可以改變默認行為
      • 關(guān)鍵點是,用了-XX:+DisableExplicitGC參數(shù)后,System.gc()的調(diào)用就會變成一個空調(diào)用,完全不會觸發(fā)任何GC,可以看看native源碼, Runtime.c里面;
      • 為什么要關(guān)閉顯示調(diào)用呢?
        • 避免濫用System.gc()
        • 防止一些第三方庫濫調(diào)用System.gc()
      • NIO的DirectByteBuffer里面有用到System.gc(),用來觸發(fā)回收DirectByteBuffer對象,從而間接回收堆外內(nèi)存(reference handler里調(diào)用了Cleaner.cleanup() ),如果System.gc()被關(guān)閉了,導(dǎo)致DirectByteBuffer對象回收不了,最后直接內(nèi)存也回收不了; java.lang.OutOfMemoryError: Direct buffer memory
      • public class DisableExplicitGCDemo { public static void main(String[] args) { for (int i = 0; i < 100000; i++) { ByteBuffer.allocateDirect(128); } System.out.println("Done"); } }
      • java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
        Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    • DirectByteBuffer有幾處值得注意的地方
      • DirectByteBuffer沒有finalizer,它的native memory的清理工作是通過sun.misc.Cleaner自動完成的
      • sun.misc.Cleaner是一種基于PhantomReference的清理工具,比普通的finalizer輕量些
      • A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner
      • Oracle/Sun JDK 6中的HotSpot VM只會在old gen GC(full GC/major GC或者concurrent GC都算)的時候才會對old gen中的對象做reference processing,而在young GC/minor GC時只會對young gen里的對象做reference processing; 死在young gen中的DirectByteBuffer對象會在young GC時被處理; 也就是說,做full GC的話會對old gen做reference processing,進而能觸發(fā)Cleaner對已死的DirectByteBuffer對象做清理工作。而如果很長一段時間里沒做過GC或者只做了young GC的話則不會在old gen觸發(fā)Cleaner的工作,那么就可能讓本來已經(jīng)死了的、但已經(jīng)晉升到old gen的DirectByteBuffer關(guān)聯(lián)的native memory得不到及時釋放
      • 為DirectByteBuffer分配空間過程中會顯式調(diào)用System.gc(),以期通過full GC來強迫已經(jīng)無用的DirectByteBuffer對象釋放掉它們關(guān)聯(lián)的native memory
      • 這幾個實現(xiàn)特征使得Oracle/Sun JDK 6依賴于System.gc()觸發(fā)GC來保證DirectByteMemory的清理工作能及時完成。如果打開了-XX:+DisableExplicitGC,清理工作就可能得不到及時完成,于是就有機會見到direct memory的OOM
      • 教訓(xùn)是:如果你在使用Oracle/Sun JDK 6,應(yīng)用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了該參數(shù)而且遇到direct memory的OOM,可以嘗試去掉該參數(shù)看是否能避開這種OOM。如果擔(dān)心System.gc()調(diào)用造成full GC頻繁,可以嘗試下面提到 -XX:+ExplicitGCInvokesConcurrent 參數(shù) 結(jié)合笨神的堆外內(nèi)存完全解讀

  • 陷阱2
    • -XX:+DisableExplicitGC 與 Remote Method Invocation (RMI) 與 -Dsun.rmi.dgc.{server|client}.gcInterval=
    • 實際上這里在做的是分布式GC。Sun JDK的分布式GC是用純Java實現(xiàn)的,為RMI服務(wù)
    • 前面gcCauses整理有整理這個dgc

  • 陷阱3
    • -XX:+ExplicitGCInvokesConcurrent 或 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
    • product(bool, ExplicitGCInvokesConcurrent, false, "A System.gc() request invokes a concurrent collection;" " (effective only when UseConcMarkSweepGC)")
    • product(bool, ExplicitGCInvokesConcurrentAndUnloadsClasses, false, "A System.gc() request invokes a concurrent collection and also unloads classes during such a concurrent gc cycle " "(effective only when UseConcMarkSweepGC)")
    • 跟上面的第一個例子的-XX:+DisableExplicitGC一樣,這兩個參數(shù)也是用來改變System.gc()的默認行為用的;不同的是這兩個參數(shù)只能配合CMS使用(-XX:+UseConcMarkSweepGC),而且System.gc()還是會觸發(fā)GC的,只不過不是觸發(fā)一個完全stop-the-world的full GC,而是一次并發(fā)GC周期
    • CMS GC周期中也會做reference processing。所以如果用這兩個參數(shù)的其中一個,而不是用-XX:+DisableExplicitGC的話,就避開了由full GC帶來的長GC pause,同時NIO direct memory的OOM也不會那么容易發(fā)生

  • 陷阱4
    • -XX:+GCLockerInvokesConcurrent
    • product(bool, GCLockerInvokesConcurrent, false, "The exit of a JNI CS necessitating a scavenge also kicks off a bkgrd concurrent collection")
    • jni critical release時, 觸發(fā)gc; gcCauses整理有詳細

  • 陷阱5
    • MaxDirectMemorySize 與 NIO direct memory 的默認上限
    • product(intx, MaxDirectMemorySize, -1, "Maximum total size of NIO direct-buffer allocations")
    • 但如果不配置它的話,direct memory默認最多能申請多少內(nèi)存呢?這個參數(shù)默認值是-1,顯然不是一個“有效值”。所以真正的默認值肯定是從別的地方來的
    • 當(dāng)MaxDirectMemorySize參數(shù)沒被顯式設(shè)置時它的值就是-1,在Java類庫初始化時maxDirectMemory()被java.lang.System的靜態(tài)構(gòu)造器調(diào)用,走的路徑就是這條;
    • 結(jié)論:MaxDirectMemorySize沒顯式配置的時候,NIO direct memory可申請的空間的上限就是-Xmx減去一個survivor space的預(yù)留大小
    • 這里建議讀讀源碼看看

  • 陷阱6
    • -verbose:gc 與 -XX:+PrintGCDetails
    • 經(jīng)常能看到在推薦的標(biāo)準(zhǔn)參數(shù)里這兩個參數(shù)一起出現(xiàn)。實際上它們有啥關(guān)系?
      在Oracle/Sun JDK 6里,"java"這個啟動程序遇到"-verbosegc"會將其轉(zhuǎn)換為"-verbose:gc",將啟動參數(shù)傳給HotSpot VM后,HotSpot VM遇到"-verbose:gc"則會當(dāng)作"-XX:+PrintGC"來處理。
      也就是說 -verbosegc、-verbose:gc、-XX:+PrintGC 三者的作用是完全一樣的。
      而當(dāng)HotSpot VM遇到 -XX:+PrintGCDetails 參數(shù)時,會順帶把 -XX:+PrintGC 給設(shè)置上。
      也就是說 -XX:+PrintGCDetails 包含 -XX:+PrintGC,進而也就包含 -verbose:gc。
      既然 -verbose:gc 都被包含了,何必在命令行參數(shù)里顯式設(shè)置它呢?

  • -XX:+UseFastEmptyMethods 與 -XX:+UseFastAccessorMethods
    • 這個用的少, 只看下結(jié)論就行了
    • 為了適應(yīng)多層編譯模式,JDK 7里這兩個參數(shù)的默認值就被改為false了

  • -XX:+UseCMSCompactAtFullCollection
    • CMSFullGCsBeforeCompaction
    • CMSParallelRemarkEnabled
    • CMSScavengeBeforeRemark
      • Attempt scavenge before the CMS remark step
      • 如果一個應(yīng)用統(tǒng)計到的young GC時間都比較短而CMS remark的時間比較長,那么可以試試打開這個參數(shù),在做remark之前先做一次young GC。是否能有效縮短remark的時間視應(yīng)用情況而異,所以開這個參數(shù)的話請一定做好測試
  • -Xss 與 -XX:ThreadStackSize
  • -Xmn 與 -XX:NewSize、-XX:MaxNewSize
    • 如果同時設(shè)置了-XX:NewSize與-XX:MaxNewSize遇到“Could not reserve enough space for object heap”錯誤的話,請看看是不是這帖所說的問題。早期JDK 6似乎都受這問題影響,一直到JDK 6 update 14才修復(fù)
  • -Xmn 與 -XX:NewRatio
  • -XX:NewRatio 與 -XX:NewSize、-XX:OldSize
  • jmap -heap看到的參數(shù)值與實際起作用的參數(shù)的關(guān)系?
    • jmap -heap顯示的部分參數(shù)是以MB為單位來顯示的,而MaxNewSize的單位是byte
    • 要注意的是,HotSpot VM有大量可調(diào)節(jié)的參數(shù),并不是所有參數(shù)在某次運行的時候都有效。
  • -XX:MaxTenuringThreshold 的默認值?
    • Oracle/Sun JDK 6中,選擇CMS之外的GC時,MaxTenuringThreshold(以下簡稱MTT)的默認值是15;而選擇了CMS的時候,MTT的默認值是4而不是15。設(shè)定是在 Arguments::set_cms_and_parnew_gc_flags() 里做的
    • 在Sun JDK 6之前(1.4.2、5),選擇CMS的時候MTT的默認值則是0,也就是等于設(shè)定了-XX:+AlwaysTenure——所有eden里的活對象在經(jīng)歷第一次minor GC的時候就會直接晉升到old gen,而survivor space直接就沒用了
    • java -XX:+PrintFlagsFinal | egrep 'MaxTenuringThreshold|UseParallelGC|UseConcMarkSweepGC' (15)
    • java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC | egrep 'MaxTenuringThreshold|UseParallelGC|UseConcMarkSweepGC' (本機是6)
    • 建議這樣查看vm參數(shù)值:java 我們的vm配置參數(shù) -XX:PrintFlagsFinal | grep ''selective parameter (原因在于有的參數(shù)的值,會受到其他參數(shù)影響導(dǎo)致與自己的默認值不一樣!)
  • -XX:+CMSClassUnloadingEnabled
    • CMS remark暫停時間會增加,所以如果類加載并不頻繁、String的intern也沒有大量使用的話,這個參數(shù)還是不開的好
  • -XX:+UseCompressedOops 有益?有害?
    • 我能做的建議是如果在64位Oracle/Sun JDK 6/7上,那個參數(shù)不要顯式設(shè)置
    • 有些庫比較“聰明”,會自行讀取VM參數(shù)來調(diào)整自己的一些參數(shù),例如Berkeley DB Java Edition。但這些庫實現(xiàn)得不好的時候反而會帶來一些麻煩:BDB JE要求顯式指定-XX:+UseCompressedOops才能有效的調(diào)整它的緩存大小。所以在用BDB JE并且Java堆+PermGen大小小于32GB的時候,請顯式指定-XX:+UseCompressedOops吧
  • -XX:+AlwaysPreTouch
    • 會把commit的空間跑循環(huán)賦值為0以達到“pretouch”的目的。開這個參數(shù)會增加VM初始化時的開銷,但后面涉及虛擬內(nèi)存的開銷可能降低
  • -XX:+ParallelRefProcEnabled
    • 這個功能可以加速reference processing,但在JDK6u25和6u26上不要使用,有bug:
      Bug ID 7028845: CMS: 6984287 broke parallel reference processing in CMS
  • -XX:+UseConcMarkSweepGC 與 -XX:+UseAdaptiveSizePolicy
    • 這兩個選項在現(xiàn)有的Oracle/Sun JDK 6和Oracle JDK 7上都不要搭配在一起使用——CMS用的adaptive size policy還沒實現(xiàn)完,用的話可能會crash。
      目前HotSpot VM上只有ParallelScavenge系的GC才可以配合-XX:+UseAdaptiveSizePolicy使用;也就是只有-XX:+UseParallelGC或者-XX:+UseParallelOldGC。Jon Masamitsu在郵件列表上提到過。
      題外話:開著UseAdaptiveSizePolicy的ParallelScavenge會動態(tài)調(diào)整各空間的大小,有可能會造成兩個survivor space的大小被調(diào)整得不一樣大。Jon Masamitsu在這封郵件里解釋了原因。
      追加:JDK9里CMS終于要徹底不支持adaptive size policy了:https://bugs.openjdk.java.net/browse/JDK-8034246
  • -XX:HeapDumpPath 與 -XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError
    • 但很多人都會疑惑:做出來的heap dump存到哪里去了?
      如果不想費神去摸索到底各種環(huán)境被配置成什么樣、“working directory”到底在哪里的話,就在VM啟動參數(shù)里加上 -XX:HeapDumpPath=一個絕對路徑 吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容