性能測(cè)試
環(huán)境部署
$ python -m pip install --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org locustio
$ locusts -V
[2019-02-02 10:17:55,387] BenedictJin.local/INFO/stdout: Locust 0.9.0
[2019-02-02 10:17:55,387] BenedictJin.local/INFO/stdout:
$ locusts -f put_one_point_with_variable_data_types.json --processes 4
[2019-02-02 10:18:53,668] BenedictJin.local/INFO/locust.main: Starting web monitor at *:8089
[2019-02-02 10:18:53,668] BenedictJin.local/INFO/locust.main: Starting Locust 0.9.0
測(cè)試限流
# 需要先打開服務(wù)端的限流
$ source .env
$ locusts -f testcases/limit/limit_tps_with_number.json --processes 4 -H ${base_url_from_env} -L DEBUG
$ locusts -f testcases/limit/limit_tps_with_string.json --processes 4 -H ${base_url_from_env} -L DEBUG
抓包并轉(zhuǎn)為 TestCase
使用 Charles 抓包工具
通過該方法纹坐,可以將現(xiàn)有的測(cè)試用例蛹含,輕松地轉(zhuǎn)為 http_runner 的表現(xiàn)形式决左。具體步驟如下:
a) 安裝 Charles Proxy
# Mac
# https://www.charlesproxy.com/download/
# Linux
$ cat <<EOF > /etc/yum.repos.d/Charles.repo
[charlesproxy]
name=Charles Proxy Repository
baseurl=https://www.charlesproxy.com/packages/yum
gpgkey=https://www.charlesproxy.com/packages/yum/PublicKey
EOF
$ sudo yum install charles-proxy
b) 關(guān)閉 browsermob-proxy
$ pkill -1 -f browsermob-proxy
c) 在 Http Request 中增加 Proxy
request.setConfig(RequestConfig.custom().setProxy(new HttpHost("127.0.0.1", 8888)).build());
// 修改好之后颜启,重新打包編譯即可
使用 browsermobproxy 代理庫
也可以通過編寫 Python 腳本偷俭,來創(chuàng)建 Proxy,并使用 Json 庫將 Proxy 抓包內(nèi)容以 har 的形式保存為文件
import json
import os
import time
import psutil
from browsermobproxy import Server
# pkill -1 -f browsermob-proxy
for proc in psutil.process_iter():
if proc.name() == "browsermob-proxy":
proc.kill()
server = Server(path="/apps/browsermob-proxy-2.1.4/bin/browsermob-proxy",
options={'port': 8880})
server.start()
time.sleep(1)
proxy = server.create_proxy(params={'port': 8881})
time.sleep(1)
with open('testcases.txt', "r+", encoding="utf8") as of:
os.chdir("/code/yuzhouwan-test-cloud")
for line in of.readlines():
line = line.strip('\n')
print(line)
if '.' not in line:
continue
splits = line.split('.')
clazz = splits[0]
method = splits[1]
proxy.new_har("com.yuzhouwan.yuzhouwan.client.%s,%s" % (clazz, method),
options={'captureHeaders': True, 'captureContent': True, 'captureBinaryContent': True})
command = 'java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:' \
'<...>" ' \
'com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 ' \
'com.yuzhouwan.yuzhouwan.client.%s,%s' % (clazz, method)
os.system(command)
json_obj = json.dumps(proxy.har)
file_dir = '/Users/benedictjin/Documents/http_runner/%s/' % clazz
file_name = method
file_extension = '.har'
if not os.path.exists(file_dir):
os.makedirs(file_dir)
fileObject = open(file_dir + file_name + file_extension, 'w')
fileObject.write(json_obj)
fileObject.close()
server.stop()
二次開發(fā)
解決 http_runner 中只能解析部分 parameters 的問題
# vim httprunner/parser.py
parsed_parameters_list = []
for parameter in parameters:
parameter_name, parameter_content = list(parameter.items())[0]
parameter_name_list = parameter_name.split("-")
# 改為
parsed_parameters_list = []
for parameter_name, parameter_content in parameters.items():
parameter_name_list = parameter_name.split("-")
使得 har2case 支持 list 結(jié)構(gòu)的 JSON
詳見:Let _make_validate
method supports the list
structures JSON #18
使得 har2case 支持普通文本的返回類型
# vim har2case/core.py
mime_type = resp_content_dict.get("mimeType")
if mime_type and mime_type.startswith("application/json"):
encoding = resp_content_dict.get("encoding")
if encoding and encoding == "base64":
content = base64.b64decode(text).decode('utf-8')
try:
resp_content_json = json.loads(content)
except JSONDecodeError:
logging.warning("response content can not be loaded as json.")
return
else:
resp_content_json = json.loads(text)
# 改為
mime_type = resp_content_dict.get("mimeType")
if mime_type and mime_type.startswith("application/json"):
encoding = resp_content_dict.get("encoding")
if encoding and encoding == "base64":
text = base64.b64decode(text).decode('utf-8')
try:
resp_content_json = json.loads(text)
except JSONDecodeError:
logging.warning("response content can not be loaded as json.")
return
print("resp_content_json", resp_content_json)
使二次開發(fā)的改動(dòng)生效
$ pip uninstall har2case -y
$ cd /usr/local/har2case/har2case
$ python setup.py install
$ har2case --log-level DEBUG test.har
可視化管理系統(tǒng)
部署
按照部署手冊(cè)即可部署成功缰盏。相關(guān)的涌萤,比如淹遵,MySQL 安裝、RabbitMQ 安裝负溪,都能找到很多資料透揣。這里,主要記錄幾個(gè)可能踩到的坑
可視化管理頁面
配置環(huán)境笙以、增加測(cè)試用例淌实、組合測(cè)試套件等,效果如下圖所示:
運(yùn)行結(jié)果報(bào)告頁面猖腕,效果如下圖所示:
支持權(quán)限管理拆祈,超級(jí)管理員可以配置整個(gè)管理系統(tǒng),效果如下圖所示:
踩到的坑
MySQL 默認(rèn)編碼導(dǎo)致 Django 報(bào)錯(cuò) OperationalError
- 描述
$ python manage.py migrate
# 報(bào)錯(cuò) django.db.utils.OperationalError: (1366, "Incorrect string value for column 'name' at row 1")
- 解決
這個(gè)問題是因?yàn)樾掳惭b的 MySQL 默認(rèn)的編碼不是 UTF-8
導(dǎo)致的倘感,停掉 mysql
實(shí)例配置 my.cnf
再重啟即可放坏。如果停止不掉,可以 pkill mysql
強(qiáng)制停止老玛。之前創(chuàng)建的數(shù)據(jù)庫也需要?jiǎng)h掉重新創(chuàng)建
$ cd /usr/local/mysql/support-files
$ sudo ./mysql.server stop
$ vim my.cnf
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
collation-server=utf8_unicode_ci
init-connect='SET NAMES utf8'
character-set-server=utf8
# 如果 my.cnf 沒有生效淤年,可能需要拷貝一份到 /private/etc 目錄下
$ cd /private/etc
$ sudo cp /usr/local/mysql/support-files/my.cnf .
$ sudo ./mysql.server restart
$ mysql -u root -p
mysql> show variables like '%char%';
+--------------------------+-----------------------------------------------------------+
| Variable_name | Value |
+--------------------------+-----------------------------------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql-5.7.25-macos10.14-x86_64/share/charsets/ |
+--------------------------+-----------------------------------------------------------+
8 rows in set (0.00 sec)
mysql> drop database HttpRunner;
Query OK, 27 rows affected (0.05 sec)
mysql> create database HttpRunner;
Query OK, 1 row affected (0.00 sec)
# 增加 `TEST_CHARSET` 連接配置
$ vim HttpRunnerManager/settings.py
DATABASES = {
'default': {
# ...
'TEST_CHARSET': 'utf-8'
}
}
$ python manage.py migrate
資料
Doc
Github
- HttpRunner / HttpRunner
- Let
_make_validate
method supports thelist
structures JSON #18
Blog
- DebugTalk:探索一個(gè)軟件工程師的無限可能
- selenium.common.exceptions.SessionNotCreatedException: Message: Unable to find a matching set of capabilities with Firefox 46 through Selenium
- httprunner 中 validate 的比較方法總結(jié)
- Mac 下 MySQL 5.7.19 字符編碼設(shè)置為 utf-8 的方法
JVM 相關(guān)
基本概念
堆內(nèi) vs. 堆外
堆內(nèi)內(nèi)存
一般情況下,Java 中分配的非空對(duì)象都是由 Java 虛擬機(jī)的垃圾收集器管理的蜡豹,也稱為堆內(nèi)內(nèi)存(on-heap memory)
堆外內(nèi)存
堆外內(nèi)存(off-heap memory)意味著把內(nèi)存對(duì)象分配在 Java 虛擬機(jī)的堆以外的內(nèi)存麸粮,這些內(nèi)存直接受操作系統(tǒng)管理(而不是虛擬機(jī))
堆外內(nèi)存的優(yōu)勢(shì)
- 對(duì)于大內(nèi)存有良好的伸縮性
- 對(duì)垃圾回收停頓的改善可以明顯感覺到
- 在進(jìn)程間可以共享,減少虛擬機(jī)間的復(fù)制
堆外內(nèi)存的劣勢(shì)
- 數(shù)據(jù)結(jié)構(gòu)變得不那么直觀镜廉,發(fā)生內(nèi)存溢出的時(shí)候弄诲,排查定位會(huì)很麻煩
- 如果數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜,就要對(duì)它進(jìn)行串行化(serialization)娇唯,而串行化本身也會(huì)影響性能
- 可以使用更大的內(nèi)存的同時(shí)齐遵,需要擔(dān)心虛擬內(nèi)存(即硬盤)的速度對(duì)應(yīng)用的影響
示例
/*
-XX:MaxDirectMemorySize=64M 可以控制堆外內(nèi)存大小,默認(rèn)在 VM 靜態(tài)變量 directMemory 為 64M
maxDirectMemory: 67108864
isDirect: true
*/
public static void main(String[] args) throws InterruptedException {
System.out.println("maxDirectMemory: " + VM.maxDirectMemory());
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 64);
Thread.sleep(200);
boolean isDirect = buffer.isDirect();
System.out.println("isDirect: " + isDirect);
if (isDirect) ((DirectBuffer) buffer).cleaner().clean();
else buffer.clear();
Thread.sleep(200);
System.exit(0);
}
參考
- 從 0 到 1 起步 - 跟我進(jìn)入堆外內(nèi)存的奇妙世界
- 堆外內(nèi)存(off-heap)塔插、堆內(nèi)內(nèi)存(on-heap)
- Java 堆外內(nèi)存之突破 JVM 枷鎖
- 堆內(nèi)內(nèi)存還是堆外內(nèi)存梗摇?
指針壓縮
定義
64 位環(huán)境下, 寄存器是 64 位的,對(duì)應(yīng)指針也就成 64 位了想许,也就是 8 字節(jié)伶授。我們知道 4 字節(jié)可以表示 4G,實(shí)際中基本不會(huì)有需要加載這么多對(duì)象的情況伸刃。因此 8 字節(jié)就顯得浪費(fèi)了谎砾,narrowKlass 只使用 4 個(gè)字節(jié),預(yù)分配給 _metadata 的 8 字節(jié)中的另外 4 字節(jié)就可以用做它用了捧颅【巴迹看似 4 個(gè)字節(jié)無關(guān)緊要,但是堆中存在上千萬到億個(gè)對(duì)象時(shí)碉哑,省下的內(nèi)存就是幾百兆啊
流程
基于以下事實(shí)
- CPU 使用的虛擬地址是 64 位的挚币,訪問內(nèi)存時(shí)亮蒋,必須使用 64 位的指針訪問內(nèi)存對(duì)象
- Java 對(duì)象是分配于具體的某個(gè)內(nèi)存位置的,對(duì)其訪問必須使用 64 位地址
- 對(duì) Java 對(duì)象內(nèi)的引用字段進(jìn)行訪問時(shí), 必須經(jīng)過虛擬機(jī)這一層, 操作某個(gè)對(duì)象引用不管是 getfield 還是 putfield妆毕,都是由虛擬機(jī)來執(zhí)行慎玖。或者簡單來說笛粘,要改變 Java 對(duì)象某個(gè)引用字段, 必須經(jīng)過虛擬機(jī)的參與
細(xì)心的你從上面一定可以看出一點(diǎn)線索趁怔,由于存一個(gè)對(duì)象引用和取一個(gè)對(duì)象引用必須經(jīng)過虛擬機(jī),所以完全可以在虛擬機(jī)這一層做些手腳薪前。對(duì)于外部來說润努,putfield 提供的對(duì)象地址是 64 位的,經(jīng)過虛擬機(jī)的轉(zhuǎn)換示括,映射到 32 位铺浇,然后存入對(duì)象;getfield 指定目標(biāo)對(duì)象的 64 位地址和其內(nèi)部引用字段的偏移垛膝,取 32 位的數(shù)據(jù)鳍侣,然后反映射到 64 位內(nèi)存地址。對(duì)于外部來說吼拥,只看見 64 位的對(duì)象放進(jìn)去倚聚,拿出來,內(nèi)部的轉(zhuǎn)換是透明的(本質(zhì)上凿可,就是按字節(jié)尋址秉沼,變成了按字尋址)
原理
描述
CompressedOops 的原理是,解釋器在解釋字節(jié)碼時(shí)矿酵,植入壓縮指令(不影響正常和 JVM 優(yōu)化后的指令順序)
具體邏輯是,當(dāng)對(duì)象被讀取時(shí)矗积,解壓全肮,存入 heap 時(shí),壓縮
壓縮指令偽碼
! int R8; oop[] R9; // R9 is 64 bits
! oop R10 = R9[R8]; // R10 is 32 bits
! load compressed ptr from wide base ptr:
movl R10, [R9 + R8<<3 + 16]
! klassOop R11 = R10._klass; // R11 is 32 bits
! void* const R12 = GetHeapBase();
! load compressed klass ptr from compressed base ptr:
movl R11, [R12 + R10<<3 + 8]
零基壓縮優(yōu)化(Zero Based Compressd Oops)
零基壓縮是針對(duì)壓解壓動(dòng)作的進(jìn)一步優(yōu)化棘捣。它通過改變正常指針的隨機(jī)地址分配特性辜腺,強(qiáng)制從零開始做分配(需要 OS 支持),進(jìn)一步提高了壓解壓效率
要啟用零基壓縮乍恐,你分配給 JVM 的內(nèi)存大小必須控制在 4G 以上评疗,32G 以下
如果小于 4G,那么 JVM 會(huì)使用低虛擬地址空間(low virutal address space茵烈,64 位下模擬 32 位)百匆,這樣就不需要做壓解壓動(dòng)作了
而對(duì)于大于 32G,將采用默認(rèn)的隨機(jī)地址分配特性呜投,進(jìn)行壓解壓
適用場(chǎng)景
CompressedOops加匈,可以讓跑在 64 位平臺(tái)下的 JVM存璃,不需要因?yàn)楦鼘挼膶ぶ罚冻?Heap 容量損失的代價(jià)雕拼。
不過纵东,它的實(shí)現(xiàn)方式是在機(jī)器碼中植入壓縮與解壓指令,可能會(huì)給 JVM 增加額外的開銷
參數(shù)控制
-XX:+UseCompressedOops
開啟(jdk1.6.0_14+
)
-XX:-UseCompressedOops
關(guān)閉
零基壓縮的邊界
$ JAVA_HOME=`/usr/libexec/java_home -v 1.8` java -Xmx32766m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
bool UseCompressedOops := true
$ JAVA_HOME=`/usr/libexec/java_home -v 1.8` java -Xmx32767m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
bool UseCompressedOops = false
# 如果已經(jīng) JVM 進(jìn)程已經(jīng)啟動(dòng)了啥寇,可以通過 jinfo 進(jìn)行查詢
$ jinfo -flag UseCompressedOops 18979
-XX:-UseCompressedOops
壓縮 class 信息中的指針
從 JDK6_u23 開始 UseCompressedOops 被默認(rèn)打開了偎球。因此既能享受 64bit 帶來的好處,又避免了 64bit 帶來的性能損耗辑甜。當(dāng)然衰絮,如果你有機(jī)會(huì)使用超過 32G 的堆內(nèi)存,記得把這個(gè)選項(xiàng)關(guān)了
到了 Java8栈戳,永久代被干掉了岂傲,有了 “meta space” 的概念,存儲(chǔ) JVM 中的元數(shù)據(jù)子檀,包括 Byte code镊掖,class 等信息。Java8 在 UseCompressedOops 之外褂痰,額外增加了一個(gè)新選項(xiàng)叫做 UseCompressedClassPointer亩进。這個(gè)選項(xiàng)打開后,class 信息中的指針也用 32bit 的 Compressed 版本缩歪。而這些指針指向的空間被稱作 “Compressed Class Space”归薛。默認(rèn)大小是 1G,但可以通過 “CompressedClassSpaceSize” 調(diào)整
如果你的 Java 程序引用了太多的包匪蝙,有可能會(huì)造成這個(gè)空間不夠用主籍,于是會(huì)看到
java.lang.OutOfMemoryError: Compressed class space
這時(shí),一般調(diào)大 CompreseedClassSpaceSize 就可以了
常用 Collector
CMS
定義
CMS逛球,全稱 Concurrent Mark Sweep千元,是一款并發(fā)的、使用標(biāo)記-清除算法的垃圾回收器
內(nèi)存碎片
CMS 本身是不會(huì)移動(dòng)內(nèi)存的颤绕,長時(shí)間運(yùn)行后幸海,會(huì)產(chǎn)生很多內(nèi)存碎片,導(dǎo)致沒有一段足夠大的連續(xù)區(qū)域可以存放大對(duì)象奥务,導(dǎo)致 promotion failed
物独、concurrent mode failure
等異常,從而觸發(fā) Full GC
啟用 -XX:+UseCMSCompactAtFullCollection
參數(shù)之后氯葬,會(huì)在 Full GC 的時(shí)候挡篓,對(duì)年老代的內(nèi)存進(jìn)行壓縮。再配合 -XX:CMSFullGCsBeforeCompaction=0
參數(shù)可以控制多少次 FGC 后對(duì)老年代做壓縮操作帚称。默認(rèn)值為 0瞻凤,代表每次都?jí)嚎s憨攒。該參數(shù)開啟后,會(huì)把對(duì)象移動(dòng)到內(nèi)存的最左邊阀参,可能會(huì)影響性能肝集,但是可以消除碎片
浮動(dòng)垃圾
由于 CMS 并發(fā)清理階段用戶線程還在運(yùn)行著,伴隨程序運(yùn)行自然就還會(huì)有新的垃圾不斷產(chǎn)生蛛壳,這一部分垃圾出現(xiàn)在標(biāo)記過程之后杏瞻,CMS 無法在當(dāng)次收集中處理掉它們,只好留待下一次 GC 時(shí)再清理掉衙荐。這些無法被 GC 掉捞挥,留到下一次 GC 的垃圾,稱之為浮動(dòng)垃圾
G1GC
C4
Shenandoah
ZGC
常用參數(shù)
默認(rèn)配置
-Xss 堆棧大小
Platform | Default(KB) |
---|---|
Windows IA32 | 64 |
Linux IA32 | 128 |
Windows x86_64 | 128 |
Linux x86_64 | 256 |
Windows IA64 | 320 |
Linux IA64 | 1024 |
Solaris Sparc | 512 |
Tips: 實(shí)際案例 OpenTSDB 大查詢導(dǎo)致 java.long.StackOverflowError
后砌函,調(diào)整 -Xss32m
得以解決
日志方面
常用的配置項(xiàng)
Flag | Comment |
---|---|
-verbose:gc | The -verbose:gc option enables logging of garbage collection (GC) information. |
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps |
-XX:+PrintGCDetails and -XX:+PrintGCTimeStamps are used to print detailed information about garbage collection.(這里由于 -verbose:gc 相當(dāng)于 -XX:+PrintGCDetails 的別名,避免冗余溜族,應(yīng)該去掉 -verbose:gc ) |
-XX:-PrintTenuringDistribution | Print tenuring age information. |
-XX:-UseGCLogFileRotation | Enabled GC log rotation, requires -Xloggc. |
-XX:NumberOfGCLogFiles=3 | Set the number of files to use when rotating logs, must be >= 1. The rotated log files will use the following naming scheme, <filename> .0, <filename> .1, ..., <filename> .n-1. |
-XX:GCLogFileSize=8K | The size of the log file at which point the log will be rotated, must be >= 8K. |
實(shí)際效果
描述
以 Apache Druid 為例讹俊,在 Tranquility 組件啟動(dòng)時(shí),加上 -XX:+PrintTenuringDistribution
參數(shù)后的效果如下:
$ nohup ./bin/tranquility -J-XX:+HeapDumpOnOutOfMemoryError -J-XX:HeapDumpPath=/data02/druid/ -J-verbose:gc -J-XX:+PrintGCDetails -J-XX:+PrintGCDateStamps -J-XX:+PrintGCDetails -J-XX:+PrintTenuringDistribution -J-Xloggc:/data02/druid/gc.log -Ddruid.extensions.directory=/home/druid/software/druid/extensions -Ddruid.extensions.loadList='["druid-avro-extensions"]' kafka -configFile conf/service/hbase_metrics_kafka2_avro.json > /home/druid/logs/tranquility/hbase_metrics_kafka2_avro.log 2>&1 &
# before
2017-03-23T14:38:23.582+0800: 90.288: [GC (Allocation Failure) [PSYoungGen: 9934821K->5622K(10469376K)] 12602986K->7093554K(28291584K), 0.6114898 secs] [Times: user=13.74 sys=0.06, real=0.61 secs]
2017-03-23T14:38:24.801+0800: 91.507: [GC (Allocation Failure) [PSYoungGen: 10071976K->3022K(10469376K)] 17159909K->8861529K(28291584K), 0.1748974 secs] [Times: user=3.91 sys=0.03, real=0.17 secs]
2017-03-23T14:38:26.178+0800: 92.884: [GC (Allocation Failure) [PSYoungGen: 10303201K->3733K(10469376K)] 19161707K->9746931K(28291584K), 0.1744318 secs] [Times: user=3.89 sys=0.02, real=0.17 secs]
2017-03-23T14:38:27.277+0800: 93.983: [GC (Allocation Failure) [PSYoungGen: 10091092K->902K(10469888K)] 19834290K->10628837K(28292096K), 0.1108355 secs] [Times: user=2.48 sys=0.01, real=0.12 secs]
2017-03-23T14:38:28.586+0800: 95.292: [GC (Allocation Failure) [PSYoungGen: 10089131K->1078K(10472448K)] 20717066K->11513094K(28294656K), 0.1054663 secs] [Times: user=2.34 sys=0.01, real=0.11 secs]
2017-03-23T14:38:29.840+0800: 96.546: [GC (Allocation Failure) [PSYoungGen: 10400114K->1501K(10461696K)] 21912131K->13281552K(28283904K), 0.1337201 secs] [Times: user=2.99 sys=0.01, real=0.13 secs]
2017-03-23T14:38:30.865+0800: 97.571: [GC (Allocation Failure) [PSYoungGen: 10133038K->1099K(10472448K)] 23413089K->15049201K(28294656K), 0.1312707 secs] [Times: user=2.94 sys=0.02, real=0.13 secs]
2017-03-23T14:38:30.996+0800: 97.702: [Full GC (Ergonomics) [PSYoungGen: 1099K->0K(10472448K)] [ParOldGen: 15048102K->1783009K(17664000K)] 15049201K->1783009K(28136448K), [Metaspace: 54341K->54341K(1095680K)], 0.3020992 secs] [Times: user=3.90 sys=0.00, real=0.31 secs]
# after
2017-03-23T14:41:03.576+0800: 31.699: [GC (Allocation Failure)
Desired survivor size 23592960 bytes, new threshold 1 (max 15)
[PSYoungGen: 9975727K->6112K(10454016K)] 13540045K->6222293K(27026432K), 0.2530233 secs] [Times: user=5.67 sys=0.03, real=0.25 secs]
2017-03-23T14:41:05.031+0800: 33.154: [GC (Allocation Failure)
Desired survivor size 23592960 bytes, new threshold 1 (max 15)
[PSYoungGen: 10241863K->11872K(10457088K)] 16458045K->8885401K(27029504K), 0.2811251 secs] [Times: user=6.27 sys=0.05, real=0.29 secs]
2017-03-23T14:41:06.022+0800: 34.145: [GC (Allocation Failure)
Desired survivor size 25165824 bytes, new threshold 1 (max 15)
[PSYoungGen: 10378567K->15664K(10458624K)] 19252097K->10661383K(27031040K), 0.2552984 secs] [Times: user=5.70 sys=0.05, real=0.25 secs]
2017-03-23T14:41:06.893+0800: 35.016: [GC (Allocation Failure)
Desired survivor size 24117248 bytes, new threshold 1 (max 15)
[PSYoungGen: 10054634K->10669K(10460160K)] 20700353K->12431545K(27032576K), 0.3152384 secs] [Times: user=6.89 sys=0.19, real=0.31 secs]
2017-03-23T14:41:07.208+0800: 35.331: [Full GC (Ergonomics) [PSYoungGen: 10669K->0K(10460160K)] [ParOldGen: 12420876K->1803376K(14484992K)] 12431545K->1803376K(24945152K), [Metaspace: 54185K->54185K(1095680K)], 0.2905749 secs] [Times: user=4.77 sys=0.00, real=0.29 secs]
參考
內(nèi)存方面
-XX:+AlwaysPreTouch
描述
Pre-touch the Java heap during JVM initialization. Every page of the heap is thus demand-zeroed during initialization rather than incrementally during application execution.
參考
啟動(dòng)方面
-ea
# Usage:
-enableassertions[:<packageName>"..." | :<className>]
-ea[:<packageName>"..." | :<className>]
該參數(shù)用來設(shè)置 jvm 是否啟動(dòng)斷言機(jī)制(從 JDK 1.4 開始支持)仍劈,默認(rèn) JVM 是關(guān)閉斷言機(jī)制的,增加 `-ea` 參數(shù)可打開斷言機(jī)制
不指定 packageName 和 className 時(shí)運(yùn)行所有包和類中的斷言
如果希望只運(yùn)行某些包或類中的斷言寡壮,可將包名或類名加到 `-ea` 之后
比如想要啟動(dòng)包 `com.yuzhouwan.common` 下的斷言機(jī)制贩疙,可用命令 `java -ea:com.yuzhouwan.common...<Main Class>`
實(shí)戰(zhàn)技巧
查看 JVM 參數(shù)的默認(rèn)值
# 查看 JDK8 中是否默認(rèn)打開了 “對(duì)象頭壓縮” 開關(guān)
# 實(shí)際上,JDK6u23 版本之后况既,Hotspot 都已經(jīng)打開了 -XX:+UseCompressedOops 功能(OOP这溅,Ordinary Object Pointer)
$ java -XX:+PrintFlagsFinal -version | grep UseCompressedOops
bool UseCompressedOops := true
{lp64_product}
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
查看 Java 進(jìn)程的 JVM 參數(shù)
$ jcmd 666 VM.flags
666:
-XX:CICompilerCount=15 -XX:ConcGCThreads=6 -XX:G1HeapRegionSize=33554432 -XX:InitialHeapSize=197904039936 -XX:MarkStackSize=4194304 -XX:MaxGCPauseMillis=200 -XX:MaxHeapSize=197904039936 -XX:MaxNewSize=118715580416 -XX:MinHeapDeltaBytes=33554432 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC
堆外內(nèi)存分析
google-perftools
安裝
# 先安裝 g++
$ yum -y install gcc gcc-c++
# 安裝 libunwind
$ wget http://download.savannah.gnu.org/releases/libunwind/libunwind-0.99.tar.gz
$ tar -xzvf libunwind-0.99.tar.gz
$ cd libunwind-0.99
$ ./configure --prefix=/data0/java/deploy/google-perftools/local/libunwind
$ make && make install
# 安裝 gperftools
$ wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.5/gperftools-2.5.tar.gz
$ tar -xzvf gperftools-2.5.tar.gz
$ cd gperftools-2.5
$ ./configure --prefix=/data0/java/deploy/google-perftools/local/gperftools-2.5/
$ make && make install
# 使配置生效
$ vim /etc/ld.so.conf.d/usr_local_lib.conf
/data0/java/deploy/google-perftools/local/libunwind/lib
# 執(zhí)行 ldconfig 命令,使libunwind生效棒仍。 需要 sudo 權(quán)限
$ /sbin/ldconfig
# 創(chuàng)建 tmp 目錄
$ mkdir -p /data0/java/deploy/google-perftools/local/tmp
# 加入環(huán)境變量
# 不要加在 .bashrc 里面芍躏,放在 jvm 啟動(dòng)腳本里面即可
export LD_PRELOAD=/data0/java/deploy/google-perftools/local/gperftools-2.5/lib/libtcmalloc.so
export HEAPPROFILE=/data0/java/deploy/google-perftools/local/tmp/gzip
# 啟動(dòng) jvm 進(jìn)程,就會(huì)在 /data0/java/deploy/google-perftools/local/ 目錄下生成 heap 文件
$ bin/hitsdb restart
分析
# 分析函數(shù)調(diào)用
$ /data0/java/deploy/google-perftools/local/gperftools-2.5/bin/pprof --text /usr/local/jdk1.8.0_181/bin/java /data0/java/deploy/google-perftools/local/tmp/gzip.0001.heap
Using local file /usr/local/jdk1.8.0_181/bin/java.
Using local file /data0/java/deploy/google-perftools/local/tmp/gzip.0001.heap.
Total: 0.0 MB
0.0 87.9% 87.9% 0.0 100.0% __FRAME_END__
0.0 9.6% 97.5% 0.0 9.6% _nl_intern_locale_data
0.0 1.2% 98.7% 0.0 1.2% __gconv_lookup_cache
0.0 0.6% 99.3% 0.0 0.6% new_composite_name
0.0 0.3% 99.6% 0.0 10.0% _nl_load_locale_from_archive
0.0 0.2% 99.8% 0.0 0.2% __GI___strdup
0.0 0.1% 99.9% 0.0 1.3% __wcsmbs_load_conv
0.0 0.1% 100.0% 0.0 0.1% __bindtextdomain
0.0 0.0% 100.0% 0.0 10.7% __GI_setlocale
0.0 0.0% 100.0% 0.0 1.3% __btowc
0.0 0.0% 100.0% 0.0 1.2% __gconv_find_transform
0.0 0.0% 100.0% 0.0 100.0% __libc_start_main
0.0 0.0% 100.0% 0.0 0.0% __textdomain
0.0 0.0% 100.0% 0.0 10.0% _nl_find_locale
常用工具
JMC 監(jiān)控報(bào)警工具(Java Mission Control)
JMC 工具是 JDK 里面自帶的降狠,只需要運(yùn)行 jmc
命令即可
GCViewer
安裝
在 GCViewer 的下載頁面,找到當(dāng)前最新版本 gcviewer-1.35.jar 進(jìn)行下載
使用
# 文件 yuzhouwan01.gc 是由增加了 `verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/fm/logs/worker-gc.log` 參數(shù)的 JVM 進(jìn)程生成的 GC 日志
$ java -jar gcviewer-1.35.jar yuzhouwan01.gc
MAT 內(nèi)存分析工具(Memory Analyzer Tool)
創(chuàng)建 dump 文件
# 如果指定 live 參數(shù)的話庇楞,將會(huì)在 dump 之前榜配,強(qiáng)制進(jìn)行一次 Full GC
$ jmap -dump:[live,]format=b,file=<file_name>.hprof <pid>
火焰圖
簡介
火焰圖是一個(gè)二維圖片,火焰圖的 X 軸代表采樣總量吕晌,而 Y 軸代表棧深度蛋褥。每個(gè)框就代表了一個(gè)棧里的函數(shù),其寬度代表了所占用的 CPU 總時(shí)間睛驳。因此烙心,比較寬的框就表示膜廊,該函數(shù)運(yùn)行時(shí)間較慢或被調(diào)用次數(shù)較多,從而占用的 CPU 時(shí)間多淫茵。通過火焰圖爪瓜,相關(guān)設(shè)計(jì)或分析人員就可以輕松觀察到各個(gè)應(yīng)用占用 CPU 的情況
Linux 平臺(tái)上,對(duì)于多數(shù) C/C++ 編寫的應(yīng)用匙瘪,可以通過 perf 來方便的采樣铆铆,還可以進(jìn)一步生成火焰圖來更直觀地觀察。Java 是沒法直接用 perf 的丹喻。雖然有一個(gè) perf-map-agent薄货,但是并不方便,嘗試過程中還弄出了 kernel panic碍论,所以這玩意是不敢在線上用了谅猾。不過 JDK 自己其實(shí)已經(jīng)帶了一個(gè)采樣工具 FlightRecorder,算是 JMC 的一部分
以往獲得火焰圖所需要的復(fù)雜步驟
首先鳍悠,應(yīng)用啟動(dòng)的時(shí)候税娜,要給 java 加上參數(shù)
-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=loglevel=info
因?yàn)?JVM 默認(rèn)在 safepoint 的地方才可以返回棧,所以最好加上下面兩個(gè)參數(shù)贼涩,讓 JVM 在非 safepoint 的時(shí)候也提供原數(shù)據(jù)
-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
然后在準(zhǔn)備開始采樣的時(shí)候巧涧,使用下面命令,指定輸出的文件路徑遥倦,和采樣時(shí)間
sudo -u <java_user> -i jcmd <pid> JFR.start filename=/tmp/app.jfr duration=60s
之后可以用 JFR.check 來檢查采樣是不是已經(jīng)完成了(詳見:Java Platform, Standard Edition Java Flight Recorder Runtime Guide)
sudo -u <java_user> -i jcmd <pid> JFR.check
確認(rèn)完成后谤绳,就可以把 jfr 文件傳回本地,用 jmc 來分析了袒哥。如果想要生成火焰圖缩筛,還有這么個(gè)工具:jfr-flame-graph。具體用法可以看文檔堡称。大致上瞎抛,代碼拖回來后就編譯好了。另外還需要 FlameGraph
cd jfr-flame-graph
install-mc-jars.sh
mvn clean install -U
工具準(zhǔn)備好以后却紧,執(zhí)行下面命令桐臊,就能生成一個(gè)漂亮的火焰圖了
path/to/jfr-flame-graph/run.sh -f app.jfr -o app.txt
cat app.txt | path/to/FlameGraph/flamegraph.pl >app.svg
使用新版 JVM Profile 功能之后一鍵搞定
<div class="note success">這里我們以 2018.3 版本為例,但只要是高于該版本的 IDEA 也都是支持的</div>
打開下載 Intellij Idea 下載頁面晓殊,找到 Coming in 2018.3断凶,然后下載 EAP 版本的 Intellij Idea
打開項(xiàng)目后,使用快捷鍵 ???/
打開 Maintenance
面板巫俺,選擇 Experimantal features
认烁,勾選 linux.native.menu
和 idea.profiler.enabled
使用 Run xxx with Async Profiler
執(zhí)行任意程序
即可獲得火焰圖、方法調(diào)用鏈、方法列表
<center>(對(duì) <a target="_blank">Intellij IDEA</a>? 的截圖)</center>
火焰圖却嗡,主要用于分析 CPU 性能消耗舶沛。可以交互式地分析 JVM 進(jìn)程中所有線程的 CPU 消耗火焰圖窗价,也可以選擇某一個(gè)線程來分析如庭;
方法調(diào)用鏈,可以找到在某個(gè)線程中舌镶,消耗 CPU 最多的方法
方法列表柱彻,可以看到每個(gè)方法的調(diào)用次數(shù),展開后還可以看到詳細(xì)的調(diào)用棧
Tips: Spark 任務(wù)生成火焰圖
序列化 ID 排查工具
使用
serialver [ options ] [ classnames ]
options
The command-line options. See Options.
classnames
The classes for which the serialVersionUID is to be returned.
參考
Arthas 診斷工具
如果你想要對(duì)線上運(yùn)行的 JVM 進(jìn)程進(jìn)行邏輯或性能分析餐胀,但是此時(shí)如果進(jìn)程本身重啟很耗時(shí)哟楷,或者進(jìn)程內(nèi)存很大無法進(jìn)行 Dump 操作,亦或是不想有任何的代碼侵入否灾,就統(tǒng)計(jì)出各個(gè)方法的調(diào)用次數(shù)和耗時(shí)卖擅,那么,Arthas 絕對(duì)是不二之選
常見問題
編碼相關(guān)
SimpleDateFormat 多線程安全問題
分析
不安全的主要原因是墨技,SimpleDateFormat 繼承的 DateTime 類惩阶,本身就是不安全的。而根本原因是 DateTime 的類屬性中 Calendar
實(shí)例扣汪,并沒有使用同步代碼塊進(jìn)行多線程安全處理断楷。如果在執(zhí)行 format(...)
方法的同時(shí),有其他線程調(diào)用 setCalendar(Calendar newCalendar)
方法崭别,則會(huì)出現(xiàn)混亂冬筒。處理的方法有很多,除了在每次需要使用重新初始化 SimpleDateFormat 實(shí)例茅主,另外還可使用 ThreadLocal 對(duì)其進(jìn)行緩存舞痰,再或者使用 Joda-time 替換 Jdk 原生的 SimpleDateFormat 和 Java 8 里面的 DateTimeFormatter(注意別觸發(fā) JDK-8031085,該問題在 JDK9 才修復(fù))诀姚,亦可
Tips: Full code is here and here.
參考
- 深入理解 Java:SimpleDateFormat 安全的時(shí)間格式化
- “Java DateFormat is not threadsafe” what does this leads to?
- Java 8 新增的 DateTimeFormatter 與 SimpleDateFormat 的區(qū)別
java.lang.IllegalThreadStateException
報(bào)錯(cuò)是因?yàn)?Thread
不可以使用 start()
啟動(dòng)多次响牛,可以將邏輯放在 Runnable
對(duì)象中,每次啟動(dòng)的時(shí)候赫段,再通過 new Thread(runnable).start()
初始化一個(gè) Thread
對(duì)象來啟動(dòng)即可
取消數(shù)值的科學(xué)計(jì)數(shù)法
// 數(shù)值過大后呀打,double、long 的 toString 可能會(huì)出現(xiàn)科學(xué)計(jì)數(shù)法
// 可以通過 NumberFormat 來解決
NumberFormat nf = NumberFormat.getInstance();
nf.setGroupingUsed(false);
nf.setMaximumFractionDigits(0);
nf.setMaximumIntegerDigits(64);
assertEquals("1234567890123456789", nf.format(1234567890123456789L));
assertEquals(1234567890123456789L, Long.valueOf(nf.format(1234567890123456789L)).longValue());
// 或者轉(zhuǎn)換 double 的字符串為 long
assertEquals(0L, Long.valueOf(nf.format(0.0)).longValue());
nf.setMaximumFractionDigits(64);
assertEquals("0.12345678901234568", nf.format(0.1234567890_12345678D));
assertEquals(0.12345678901234568D, Double.valueOf(nf.format(0.1234567890_12345678D)), 64);
File#toURL 過期
使用 file.toURI().toURL()
替代
Jersey 的 @Produces 默認(rèn)不設(shè)置 UTF-8 存在中文亂碼
方案
實(shí)現(xiàn) ContainerResponseFilter 接口糯笙,給 @Produces
注解增加 charset=UTF-8
屬性
實(shí)現(xiàn)
編碼 MediaTypeFilter
import com.sun.jersey.core.util.Priority;
import com.sun.jersey.spi.container.*;
import javax.ws.rs.Priorities;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import java.lang.annotation.Annotation;
@Provider
@Priority(Priorities.HEADER_DECORATOR)
public class MediaTypeFilter implements ResourceFilter, ContainerResponseFilter {
@Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
Annotation[] annotations = response.getAnnotations();
for (int i = 0; i < annotations.length; i++) {
if (!(annotations[i] instanceof Produces)) {
continue;
}
Produces produces = (Produces) annotations[i];
String[] producesValues = produces.value();
for (int j = 0; j < producesValues.length; j++) {
if (!MediaType.APPLICATION_JSON.equals(producesValues[j]) && !MediaType.TEXT_PLAIN.equals(producesValues[j])) {
continue;
}
producesValues[j] += ";charset=UTF-8";
}
annotations[i] = produces;
}
response.setAnnotations(annotations);
return response;
}
@Override
public ContainerRequestFilter getRequestFilter() {
return null;
}
@Override
public ContainerResponseFilter getResponseFilter() {
return this;
}
}
在 BrokerResource 中使用
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.sun.jersey.spi.container.ResourceFilters;
import org.apache.druid.client.BrokerServerView;
import org.apache.druid.server.http.security.StateResourceFilter;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/druid/broker/v1")
@ResourceFilters({StateResourceFilter.class, MediaTypeFilter.class})
public class BrokerResource
{
private final BrokerServerView brokerServerView;
@Inject
public BrokerResource(BrokerServerView brokerServerView)
{
this.brokerServerView = brokerServerView;
}
@GET
@Path("/loadstatus")
@Produces(MediaType.APPLICATION_JSON)
public Response getLoadStatus()
{
return Response.ok(ImmutableMap.of("inventoryInitialized", brokerServerView.isInitialized())).build();
}
}
參考
- Change what @Produces(…) produces in Jersey?
- Setting Character Set for Jersey Web Services
- add charset UTF-8 to log api #6709
IDE 相關(guān)
[Intellij Idea] Code Check 中設(shè)置行長度為 120贬丛,卻仍然有一條虛線豎在 80
依次選擇菜單 Settings
- Editor
- Code Style
- Java
,這時(shí)候可以將默認(rèn)的 Scheme
設(shè)置成想要的方案炬丸,也可以在 Wrapping and Braces
中修改 Hard wrap at
為 120
,以達(dá)到想要的效果
版本相關(guān)
Unsupported major.minor version 52.0
此類報(bào)錯(cuò),是因?yàn)橛玫桶姹?JDK 去運(yùn)行高版本的 Java 程序了
JDK 版本 | Major Version Number |
---|---|
Java SE 11 | 55 |
Java SE 10 | 54 |
Java SE 9 | 53 |
Java SE 8 | 52 |
Java SE 7 | 51 |
Java SE 6.0 | 50 |
Java SE 5.0 | 49 |
JDK 1.4 | 48 |
JDK 1.3 | 47 |
JDK 1.2 | 46 |
JDK 1.1 | 45 |
Maven 相關(guān)
需要正確地在 Maven 的 checkstyle 插件的 Regexp 正則中使用 XML 關(guān)鍵字
如果不對(duì)下面 5 個(gè)符號(hào)加 \
反斜杠轉(zhuǎn)義的話稠炬,就需要使用 HTML 來表示
原始符號(hào) | 含義 | HTML |
---|---|---|
小于 | < |
|
大于 | > |
|
& | 和 | & |
' | 單引號(hào) | ' |
" | 雙引號(hào) | " |
LogBack 相關(guān)
日志文件過大
描述
多個(gè) JVM 進(jìn)程將日志寫入同一個(gè)日志文件中焕阿,導(dǎo)致按照文件大小切割的策略失效,xxx.log.1
會(huì)持續(xù)寫下去首启,以至于磁盤被撐爆
解決
在日志路徑上增加 ${JVM_PREFIX}
變量暮屡,并在不同的 JVM 啟動(dòng)的時(shí)候,通過 -DJVM_PREFIX="process001"
的方式傳入毅桃,使得不同的 JVM 進(jìn)程將日志寫入各自的日志文件中
參考
配置相關(guān)
ConfigFactory.load() 只能加載固定目錄下的配置文件
對(duì)于 Java 開發(fā)而言褒纲,要實(shí)現(xiàn)加載配置文件的功能,一般都會(huì)選用 typesafe 下 config 框架钥飞,它具備純 Java 源碼和無任何外部依賴的優(yōu)點(diǎn)莺掠。而調(diào)用 ConfigFactory.load()
方法只能加載 src/main/resources
下配置文件的問題。針對(duì)該問題读宙,只需調(diào)用 ConfigFactory.parseFile(new File("yuzhouwan.conf"))
方法彻秆,即可指定其他任意位置的配置文件
JVM 相關(guān)
Too small initial heap
-Xmx1024 -Xms512
應(yīng)改為 -Xmx1024M -Xms512M
如何計(jì)算 Java 對(duì)象實(shí)際占用內(nèi)存
- 一個(gè) Java 對(duì)象到底占用多大內(nèi)存?
- Java 對(duì)象占用空間大小計(jì)算
- Java 中 int 與 byte 數(shù)組互轉(zhuǎn)代碼詳細(xì)分析
- Java 基礎(chǔ)【04】數(shù)組內(nèi)存分配
- Java 對(duì)象內(nèi)存布局
- Hotspot GC 研究 - 64 位引用指針壓縮技術(shù)
- 在線文件大薪嵴ⅰ(bit唇兑,bytes,KB桦锄,MB扎附,GB,TB)轉(zhuǎn)換換算
G1GC 報(bào)錯(cuò) To-space Exhausted
描述
2018-08-12T00:52:36.255+0800: 17308164.871: [GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 2.3349764 secs]
[Parallel Time: 331.1 ms, GC Workers: 23]
[GC Worker Start (ms): Min: 17308164872.2, Avg: 17308164872.3, Max: 17308164872.3, Diff: 0.2]
[Ext Root Scanning (ms): Min: 0.7, Avg: 1.0, Max: 3.1, Diff: 2.4, Sum: 23.1]
[Update RS (ms): Min: 56.3, Avg: 58.3, Max: 59.2, Diff: 2.9, Sum: 1341.8]
[Processed Buffers: Min: 75, Avg: 95.6, Max: 133, Diff: 58, Sum: 2199]
[Scan RS (ms): Min: 0.8, Avg: 1.4, Max: 1.5, Diff: 0.7, Sum: 33.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Object Copy (ms): Min: 269.3, Avg: 269.5, Max: 269.8, Diff: 0.6, Sum: 6197.7]
[Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.6, Diff: 0.5, Sum: 9.3]
[Termination Attempts: Min: 1, Avg: 290.0, Max: 325, Diff: 324, Sum: 6669]
[GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 3.0]
[GC Worker Total (ms): Min: 330.7, Avg: 330.8, Max: 331.0, Diff: 0.3, Sum: 7608.0]
[GC Worker End (ms): Min: 17308165202.9, Avg: 17308165203.0, Max: 17308165203.1, Diff: 0.2]
[Code Root Fixup: 0.2 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 1.1 ms]
[Other: 2002.7 ms]
[Evacuation Failure: 779.0 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 1217.9 ms]
[Ref Enq: 1.6 ms]
[Redirty Cards: 0.8 ms]
[Humongous Register: 0.2 ms]
[Humongous Reclaim: 0.9 ms]
[Free CSet: 1.5 ms]
[Eden: 8520.0M(8520.0M)->0.0B(3536.0M) Survivors: 368.0M->440.0M Heap: 14.8G(16.0G)->10.4G(16.0G)]
[Times: user=25.22 sys=0.29, real=2.33 secs]
原因
在日志中看到類似 Evacuation Failure结耀、To-space Exhausted 或者 To-space Overflow 這樣的輸出(取決于不同版本的 JVM留夜,輸出略有不同)。這是 G1GC 收集器在將某個(gè)需要垃圾回收的分區(qū)進(jìn)行回收時(shí)饼记,無法找到一個(gè)能將其中存活對(duì)象拷貝過去的空閑分區(qū)香伴。這種情況被稱為 Evacuation Failure,常常會(huì)引發(fā) Full GC
解決
- 增加
-XX:G1ReservePercent
選項(xiàng)的值(并相應(yīng)增加總的堆大芯咴颉)即纲,為目標(biāo)空間增加預(yù)留內(nèi)存量 - 將
-XX:InitiatingHeapOccupancyPercent
參數(shù)調(diào)低(默認(rèn)值是45),可以使 G1GC 收集器更早開始 Mixed GC博肋;但另一方面低斋,會(huì)增加 GC 發(fā)生頻率 - 提高
-XX:ConcGCThreads
的值,在 Mixed GC 階段投入更多的并發(fā)線程匪凡,爭取提高每次暫停的效率膊畴。但是此參數(shù)會(huì)占用一定的有效工作線程資源
參考
- 本文作者: Benedict Jin
- 本文鏈接: https://yuzhouwan.com/posts/190413/
- 版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-ND 許可協(xié)議病游。轉(zhuǎn)載請(qǐng)注明出處唇跨!