編譯:唐尤華
連結:dzone.com/articles/try-to-avoid-xxusegclogfilerotation
開發者透過 JVM 引數 `-XX:+UseGCLogFileRotation` 實現 GC 日誌輪轉。
像下麵這樣:
```shell
"-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/GCEASY/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M"
```
指定上述引數,當日誌檔案大小增加到 20MB,JVM 會進行 GC 日誌輪轉生成最多5個檔案,副檔名分別為 `gc.log.0`、`gc.log.1`、`gc.log.2`、`gc.log.3` 和 `gc.log.4`。
這種做法會帶來以下問題:
1. 丟失舊的 GC 日誌
如果設定引數 `-XX:NumberOfGCLogFiles=5`,一段時間過後會建立5個 GC 日誌檔案:
- gc.log.0 ← `最老的 GC 日誌內容`
- gc.log.1
- gc.log.2
- gc.log.3
- gc.log.4 ← `最新的 GC 日誌內容`
最新的 GC 日誌寫入 `gc.log.4`,而過去的 GC 日誌內容存入 `gc.log.0`。
使用 `-XX:NumberOfGCLogFiles` 配置時,如果應用不斷產生更多的 GC 日誌,`gc.log.0` 中的舊日誌內容會被刪除,新產生的 GC 事件將寫入 `gc.log.0`。這意味著日誌內容完整性早到破壞,即無法看到所有 GC 事件。
2. GC 日誌混合
假設某個應用建立了5個 GC 日誌檔案:
- gc.log.0
- gc.log.1
- gc.log.2
- gc.log.3
- gc.log.4
接著,如果重啟這個應用,現在新 GC 日誌會寫入 `gc.log.0`。重啟前的日誌資訊儲存在 `gc.log.1`, `gc.log.2`, `gc.log.3`, `gc.log.4` 中。
- gc.log.0 ← 重啟後的 GC 日誌檔案內容
- gc.log.1 ← 重啟前的 GC 日誌檔案內容
- gc.log.2 ← 重啟前的 GC 日誌檔案內容
- gc.log.3 ← 重啟前的 GC 日誌檔案內容
- gc.log.4 ← 重啟前的 GC 日誌檔案內容
因此,重啟後的 GC 新日誌與舊日誌混在一起。要解決這個問題,可以在重啟應用前把所有舊日誌移動到一個獨立的檔案夾中。
3. 將 GC 日誌轉發到中央儲存
採用這種方法,當前寫入的日誌檔案會標記 `.current` 擴充套件,例如當前寫入 `gc.log.3`,會命名為 `gc.log.3.current`。
如果要把 GC 日誌從單個伺服器彙總到中央儲存,大多數 DevOps 工程師會使用 `rsyslog`。然而,正如[這篇部落格][1]討論的那樣,這種命名方式給 `rsyslog` 帶來了巨大挑戰。
[1]:http://www.planetcobalt.net/sdb/forward_gc_logs.shtml
4. 工具
現在,有許多像 [GCeasy][2]、GCViewer 這樣的工具可以用來分析 GC 日誌,支援上傳多個日誌檔案。
[2]:https://gceasy.io/
5. 推薦的解決方案
當 JVM 重啟後可以為 GC 日誌加上時間戳作為字尾,這樣 GC 日誌檔案路徑能夠保持唯一。這樣,新日誌就不會改寫掉舊的 GC 日誌。透過為 GC 日誌檔案指定 `%t` 字尾可以實現,像下麵這樣:
```shell
"-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/GCEASY/gc-%t.log"
```
`%t` 字尾格式生成的時間戳格式為 `YYYY-MM-DD_HH-MM-SS`。因此,生成的日誌檔案名看起來像這樣 `gc-2019-01-29_20-41-47.log`。
這種簡單的方法解決了 `-XX:+UseGCLogFileRotation` 引數的所有缺點。