Java Troubleshooting 整理

Troubleshooting 整理

  • kill -3 pid
    • 發送一個SIGQUIT信號給Java應用, 通常會有當前的Thread Dump輸出
    • 假定這個程序在JVM初始化之后沒有別的代碼注冊了新的SIGQUIT的signal handler,那么HotSpot VM在收到SIGQUIT之后會在一個專門的signal handler thread處理。該線程的入口函數為signal_thread_entry()
    • http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/tip/src/share/vm/runtime/os.cpp
    • 打印線程棧的動作由VM_PrintThreads實現,在VM thread上執行。于是問題就來了:如果這個JVM實例已經hang了,那它將無法響應任何外部請求,對它發SIGQUIT當然也得不到處理。所以有時候kill -3看不到線程棧是正常的
    • 至于時效性,所有VM operation都是先被放到一個隊列里,然后由VM thread逐個處理。如果當前該隊列是空的,那kill -3就可以幾乎“實時”執行爬棧動作,否則得等前面的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
        ...
      • 修改限制
        • 臨時 可以使用參數unlimited,取消該限制ulimit -c unlimited
        • 永久 echo "ulimit -c 1024" >> /etc/profile (1024 限制產生的 core 文件的大小不能超過 1024kb)
    • 在一個程序崩潰時,它一般會在指定目錄下生成一個 core 文件。 core 文件僅僅是一個內存映象 ( 同時加上調試信息 ) ,主要是用來調試的。
    • 設置 Core Dump 的核心轉儲文件目錄和命名規則
      • /proc/sys/kernel/core_uses_pid 可以控制產生的 core 文件的文件名中是否添加 pid 作為擴展 ,如果添加則文件內容為 1 ,否則為 0
      • /proc/sys/kernel/core_pattern 可以設置格式化的 core 文件保存位置或文件名 ,比如原來文件內容是 core-%e 可以這樣修改 : echo "/corefile/core-%e-%p-%t" > core_pattern; 將會控制所產生的 core 文件會存放到 /corefile 目錄下,產生的文件名為 core- 命令名 -pid- 時間戳
    • kill -l 查看信號
      • 看到SIGSEGV在其中,一般數組越界或是訪問空指針都會產生這個信號
      • 指示進程進行了一次無效的存儲訪問。名字SEGV表示“段違例(segmentation violation)
    • gdb core調試
    • HSDB R大-HSDB

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

  • 陷阱1
    • -XX:+DisableExplicitGC 與 NIO的direct memory
      • System.gc()的默認效果是引發一次stop-the-world的full GC,對整個GC堆做收集。有幾個參數可以改變默認行為
      • 關鍵點是,用了-XX:+DisableExplicitGC參數后,System.gc()的調用就會變成一個空調用,完全不會觸發任何GC,可以看看native源碼, Runtime.c里面;
      • 為什么要關閉顯示調用呢?
        • 避免濫用System.gc()
        • 防止一些第三方庫濫調用System.gc()
      • NIO的DirectByteBuffer里面有用到System.gc(),用來觸發回收DirectByteBuffer對象,從而間接回收堆外內存(reference handler里調用了Cleaner.cleanup() ),如果System.gc()被關閉了,導致DirectByteBuffer對象回收不了,最后直接內存也回收不了; 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,進而能觸發Cleaner對已死的DirectByteBuffer對象做清理工作。而如果很長一段時間里沒做過GC或者只做了young GC的話則不會在old gen觸發Cleaner的工作,那么就可能讓本來已經死了的、但已經晉升到old gen的DirectByteBuffer關聯的native memory得不到及時釋放
      • 為DirectByteBuffer分配空間過程中會顯式調用System.gc(),以期通過full GC來強迫已經無用的DirectByteBuffer對象釋放掉它們關聯的native memory
      • 這幾個實現特征使得Oracle/Sun JDK 6依賴于System.gc()觸發GC來保證DirectByteMemory的清理工作能及時完成。如果打開了-XX:+DisableExplicitGC,清理工作就可能得不到及時完成,于是就有機會見到direct memory的OOM
      • 教訓是:如果你在使用Oracle/Sun JDK 6,應用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了該參數而且遇到direct memory的OOM,可以嘗試去掉該參數看是否能避開這種OOM。如果擔心System.gc()調用造成full GC頻繁,可以嘗試下面提到 -XX:+ExplicitGCInvokesConcurrent 參數 結合笨神的堆外內存完全解讀

  • 陷阱2
    • -XX:+DisableExplicitGC 與 Remote Method Invocation (RMI) 與 -Dsun.rmi.dgc.{server|client}.gcInterval=
    • 實際上這里在做的是分布式GC。Sun JDK的分布式GC是用純Java實現的,為RMI服務
    • 前面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一樣,這兩個參數也是用來改變System.gc()的默認行為用的;不同的是這兩個參數只能配合CMS使用(-XX:+UseConcMarkSweepGC),而且System.gc()還是會觸發GC的,只不過不是觸發一個完全stop-the-world的full GC,而是一次并發GC周期
    • CMS GC周期中也會做reference processing。所以如果用這兩個參數的其中一個,而不是用-XX:+DisableExplicitGC的話,就避開了由full GC帶來的長GC pause,同時NIO direct memory的OOM也不會那么容易發生

  • 陷阱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時, 觸發gc; gcCauses整理有詳細

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

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容