美國推出全球首個可移動光伏發電車棚

Envision Solar公司開發了全球首個可移動的光伏發電車棚系統EV ARC (Electric Vehicle Autonomous Renewable Charger),EV ARC長4.88m、寬2.74m,正好收納停車位內。該車棚配備2.5kW或3.3kW的太陽能電池,年發電量為3800~7000kWh,另外,該系統還配備了21.6kWh的蓄電池。   Envision Solar的Desmond Wheatley介紹,“ARC無需打地基和鋪設電纜等等土木施工。也無需更新變壓器和開關裝置。而且ARC的尺寸就是一個車位大小,因此設置充電樁,不會使寶貴的車位減少”。   EV ARC利用該公司開發的配備水壓控制功能、類似於托運船隻使用的拖車(名為ARC Mobility)運到目的地,設置在指定的停車場,5分鐘內就能完成充電樁的設置。   目前,穀歌已經開始使用EV ARC。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

007.Kubernetes二進制部署Flannel

一 部署flannel

1.1 安裝flannel


kubernetes 要求集群內各節點(包括 master 節點)能通過 Pod 網段互聯互通。flannel 使用 vxlan 技術為各節點創建一個可以互通的 Pod 網絡,使用的端口為 UDP 8472。




flanneld 第一次啟動時,從 etcd 獲取配置的 Pod 網段信息,為本節點分配一個未使用的地址段,然後創建 flannedl.1 網絡接口(也可能是其它名稱,如 flannel1 等)。




flannel 將分配給自己的 Pod 網段信息寫入 /run/flannel/docker 文件,docker 後續使用這個文件中的環境變量設置 docker0 網橋,從而從這個地址段為本節點的所有 Pod 容器分配 IP。

更多flannel參考:《008.Docker Flannel+Etcd分佈式網絡部署》。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work/
  2 [root@k8smaster01 work]# mkdir flannel
  3 [root@k8smaster01 work]# wget https://github.com/coreos/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz
  4 [root@k8smaster01 work]# tar -xzvf flannel-v0.11.0-linux-amd64.tar.gz -C flannel


1.2 分發flannel

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp flannel/{flanneld,mk-docker-opts.sh} root@${master_ip}:/opt/k8s/bin/
  7     ssh root@${master_ip} "chmod +x /opt/k8s/bin/*"
  8   done


1.3 創建flannel證書和密鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > flanneld-csr.json <<EOF
  3 {
  4     "CN": "flanneld",
  5     "hosts": [],
  6     "key": {
  7         "algo": "rsa",
  8         "size": 2048
  9     },
 10     "names": [
 11         {
 12             "C": "CN",
 13             "ST": "Shanghai",
 14             "L": "Shanghai",
 15             "O": "k8s",
 16             "OU": "System"
 17         }
 18     ]
 19 }
 20 EOF
 21 #創建flanneld的CA證書請求文件



解釋:

該證書只會被 kubectl 當做 client 證書使用,所以 hosts 字段為空。

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \
  3 -ca-key=/opt/k8s/work/ca-key.pem -config=/opt/k8s/work/ca-config.json \
  4 -profile=kubernetes flanneld-csr.json | cfssljson -bare flanneld	#生成CA密鑰(ca-key.pem)和證書(ca.pem)


1.4 分發證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "mkdir -p /etc/flanneld/cert"
  7     scp flanneld*.pem root@${master_ip}:/etc/flanneld/cert
  8   done


1.5 寫入集群 Pod 網段信息

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# etcdctl \
  4   --endpoints=${ETCD_ENDPOINTS} \
  5   --ca-file=/opt/k8s/work/ca.pem \
  6   --cert-file=/opt/k8s/work/flanneld.pem \
  7   --key-file=/opt/k8s/work/flanneld-key.pem \
  8   mk ${FLANNEL_ETCD_PREFIX}/config '{"Network":"'${CLUSTER_CIDR}'", "SubnetLen": 21, "Backend": {"Type": "vxlan"}}'



注意:注意:本步驟只需執行一次。

提示:flanneld 當前版本 (v0.11.0) 不支持 etcd v3,故使用 etcd v2 API 寫入配置 key 和網段數據;

寫入的 Pod 網段 ${CLUSTER_CIDR} 地址段(如 /16)必須小於 SubnetLen,必須與 kube-controller-manager 的 –cluster-cidr 參數值一致。

1.6 創建flanneld的systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > flanneld.service << EOF
  4 [Unit]
  5 Description=Flanneld overlay address etcd agent
  6 After=network.target
  7 After=network-online.target
  8 Wants=network-online.target
  9 After=etcd.service
 10 Before=docker.service
 11 
 12 [Service]
 13 Type=notify
 14 ExecStart=/opt/k8s/bin/flanneld \\
 15   -etcd-cafile=/etc/kubernetes/cert/ca.pem \\
 16   -etcd-certfile=/etc/flanneld/cert/flanneld.pem \\
 17   -etcd-keyfile=/etc/flanneld/cert/flanneld-key.pem \\
 18   -etcd-endpoints=${ETCD_ENDPOINTS} \\
 19   -etcd-prefix=${FLANNEL_ETCD_PREFIX} \\
 20   -iface=${IFACE} \\
 21   -ip-masq
 22 ExecStartPost=/opt/k8s/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
 23 Restart=always
 24 RestartSec=5
 25 StartLimitInterval=0
 26 
 27 [Install]
 28 WantedBy=multi-user.target
 29 RequiredBy=docker.service
 30 EOF



解釋:

mk-docker-opts.sh:該腳本將分配給 flanneld 的 Pod 子網段信息寫入 /run/flannel/docker 文件,後續 docker 啟動時使用這個文件中的環境變量配置 docker0 網橋;

flanneld:使用系統缺省路由所在的接口與其它節點通信,對於有多個網絡接口(如內網和公網)的節點,可以用 -iface 參數指定通信接口;

flanneld:運行時需要 root 權限;

-ip-masq: flanneld 為訪問 Pod 網絡外的流量設置 SNAT 規則,同時將傳遞給 Docker 的變量 –ip-masq(/run/flannel/docker 文件中)設置為 false,這樣 Docker 將不再創建 SNAT 規則; Docker 的 –ip-masq 為 true 時,創建的 SNAT 規則比較“暴力”:將所有本節點 Pod 發起的、訪問非 docker0 接口的請求做 SNAT,這樣訪問其他節點 Pod 的請求來源 IP 會被設置為 flannel.1 接口的 IP,導致目的 Pod 看不到真實的來源 Pod IP。 flanneld 創建的 SNAT 規則比較溫和,只對訪問非 Pod 網段的請求做 SNAT。

1.7 分發flannel systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp flanneld.service root@${master_ip}:/etc/systemd/system/
  7   done


二 啟動並驗證

2.1 啟動flannel

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for master_ip in ${MASTER_IPS[@]}
  3   do
  4     echo ">>> ${master_ip}"
  5     ssh root@${master_ip} "systemctl daemon-reload && systemctl enable flanneld && systemctl restart flanneld"
  6   done


2.2 檢查flannel啟動

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for master_ip in ${MASTER_IPS[@]}
  3   do
  4     echo ">>> ${master_ip}"
  5     ssh root@${master_ip} "systemctl status flanneld|grep Active"
  6   done



2.3 檢查pod網段信息

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# etcdctl \
  3   --endpoints=${ETCD_ENDPOINTS} \
  4   --ca-file=/etc/kubernetes/cert/ca.pem \
  5   --cert-file=/etc/flanneld/cert/flanneld.pem \
  6   --key-file=/etc/flanneld/cert/flanneld-key.pem \
  7   get ${FLANNEL_ETCD_PREFIX}/config			#查看集群 Pod 網段(/16)



  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# etcdctl \
  3   --endpoints=${ETCD_ENDPOINTS} \
  4   --ca-file=/etc/kubernetes/cert/ca.pem \
  5   --cert-file=/etc/flanneld/cert/flanneld.pem \
  6   --key-file=/etc/flanneld/cert/flanneld-key.pem \
  7   ls ${FLANNEL_ETCD_PREFIX}/subnets			#查看已分配的 Pod 子網段列表(/24)
  8 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  9 [root@k8smaster01 ~]# etcdctl \
 10   --endpoints=${ETCD_ENDPOINTS} \
 11   --ca-file=/etc/kubernetes/cert/ca.pem \
 12   --cert-file=/etc/flanneld/cert/flanneld.pem \
 13   --key-file=/etc/flanneld/cert/flanneld-key.pem \
 14   get ${FLANNEL_ETCD_PREFIX}/subnets/172.30.32.0-21	#查看某一 Pod 網段對應的節點 IP 和 flannel 接口地址




解釋:

172.30.32.0/21 被分配給節點 k8smaster01 (172.24.8.71);

VtepMAC 為 k8smaster01 節點的 flannel.1 網卡 MAC 地址。

2.4 檢查flannel網絡信息

  1 [root@k8smaster01 ~]# ip addr show



解釋:flannel.1 網卡的地址為分配的 Pod 子網段的第一個 IP(.0),且是 /32 的地址。

  1 [root@k8smaster01 ~]# ip route show |grep flannel.1
  2 172.30.128.0/21 via 172.30.128.0 dev flannel.1 onlink
  3 172.30.208.0/21 via 172.30.208.0 dev flannel.1 onlink



解釋:

到其它節點 Pod 網段請求都被轉發到 flannel.1 網卡;

flanneld 根據 etcd 中子網段的信息,如 ${FLANNEL_ETCD_PREFIX}/subnets/172.30.32.0-21 ,來決定進請求發送給哪個節點的互聯 IP。

2.5 驗證各節點flannel


在各節點上部署 flannel 后,檢查是否創建了 flannel 接口(名稱可能為 flannel0、flannel.0、flannel.1 等):

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for master_ip in ${MASTER_IPS[@]}
  3   do
  4     echo ">>> ${master_ip}"
  5     ssh ${master_ip} "/usr/sbin/ip addr show flannel.1|grep -w inet"
  6   done



輸出:

  1 >>> 172.24.8.71
  2     inet 172.30.32.0/32 scope global flannel.1
  3 >>> 172.24.8.72
  4     inet 172.30.128.0/32 scope global flannel.1
  5 >>> 172.24.8.73
  6     inet 172.30.208.0/32 scope global flannel.1



在各節點上 ping 所有 flannel 接口 IP,確保能通:

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for master_ip in ${MASTER_IPS[@]}
  3   do
  4     echo ">>> ${master_ip}"
  5     ssh ${master_ip} "ping -c 1 172.30.32.0"
  6     ssh ${master_ip} "ping -c 1 172.30.128.0"
  7     ssh ${master_ip} "ping -c 1 172.30.208.0"
  8   done


本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Java程序線上故障排查

目錄

這篇文章是在公司做了不少的線上Java服務故障排查和優化之後的一個總結,可以作為一個工具清單,在分析問題的時候需要有整體思路:全局觀,先從系統層面入手,大致定位方向(內存,cpu,磁盤,網絡),然後再去分析具體的進程。

一、Linux

內存和cpu

內存和cpu問題是出問題最多的一個點,因為有些命令如top同時可以觀察到內存和cpu所以放在一起。

top命令

常用參數: -H 打印具體的線程, -p 打印某個進程 進入后 按数字1 可以切換cpu的圖形看有幾個核

下面是我的測試環境shell:

top - 14:28:49 up 7 min,  3 users,  load average: 0.08, 0.26, 0.19
Tasks: 221 total,   2 running, 219 sleeping,   0 stopped,   0 zombie
%Cpu(s):  5.1 us,  3.4 sy,  0.0 ni, 91.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :   985856 total,    81736 free,   646360 used,   257760 buff/cache
KiB Swap:  2094076 total,  1915196 free,   178880 used.   141592 avail Mem 

我一般重點關注的指標有:

%Cpu(s): 5.1 us, 3.4 sy, 0.0 wa

這裏可以非常直觀的看到當前cpu的負載情況,us用戶cpu佔用時間,sy是系統調用cpu佔用時間,wa是cpu等待io的時間,前面兩個比較直觀,但是第三個其實也很重要,如果wa很高,那麼你就該重點關注下磁盤的負載了,尤其是像mysql這種服務器。

load average: 0.08, 0.26, 0.19

cpu任務隊列的負載,這個隊列包括正在運行的任務和等待運行的任務,三個数字分別是1分鐘、5分鐘和15分鐘的平均值。這個和cpu佔用率一般是正相關的,反應的是用戶代碼,如果超過了內核數,表示系統已經過載。也就是說如果你是8核,那麼這個数字小於等於8的負載都是沒問題的,我看網上的建議一般這個值不要超過ncpu*2-2為好。

KiB Mem : 985856 total, 81736 free, 646360 used, 257760 buff/cache

內存佔用情況,total總內存,free空餘內存, used已經分配內存,buff/cache塊設備和緩衝區佔用的內存,因為Linux的內存分配,如果有剩餘內存,他就會將內存用於cache,這樣可以較少磁盤的讀寫提高效率,如果有應用申請內存,buff/cache這部分內存也是可用的,所以正真的剩餘內存應該是free+buff/cache

swap

線上服務器一般都是禁用狀態,所以不用看這項。

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND

這一欄主要是看進程的詳情,重點是%CPU %MEM,上面看的是整個服務器的負載,這裡是每個進程的負載。還有看看S這個指標,這個代碼了進程的狀態,有時候有些進程會出現T(暫停)這個狀態。

網絡

ss

netstat的高性能版,參數都基本一致

常用參數: -n 打印数字端口號 -t tcp連接 -l 監聽端口 -a 所有端口 -p 進程號 -s 打印統計信息

ss -s示例:

Total: 1732 (kernel 1987)
TCP:   42373 (estab 1430, closed 40910, orphaned 2, synrecv 0, timewait 40906/0), ports 1924
Transport Total     IP        IPv6
*     1987      -         -        
RAW   18        9         9        
UDP   18        11        7        
TCP   1463      503       960     

可以看到整體的連接情況,如timewait過高,連接數過高等情況

然後使用ss -ntap|grep 進程號 or 端口號查看進程的連接

ping

查看時延和丟包情況

mtr

查看丟包請求

磁盤

磁盤問題在mysql服務器中非常常見,很多時候mysql服務器的CPU不高但是卻出現慢查詢日誌飆升,就是因為磁盤出現了瓶頸。還有mysql的備份策略,如果沒有監控磁盤空間,可能出現磁盤滿了服務不可用的現象。

iostat命令

常用參數: -k 用kb為單位 -d 監控磁盤 -x显示詳情 num count 每個幾秒刷新 显示次數

這個是我查看磁盤負載的主要工具,也可以显示cpu的負載,不過我一般用iostat -kdx 2 10,下面是我測試環境執行情況:

root@ubuntu:~# iostat -kdx 2 10
Linux 4.13.0-38-generic (ubuntu)    11/18/2018  _x86_64_    (1 CPU)
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda              24.75   196.05  121.66    9.75  2481.33   961.29    52.40     0.44    3.33    1.12   30.95   0.51   6.71
scd0              0.00     0.00    0.02    0.00     0.08     0.00     7.00     0.00    0.25    0.25    0.00   0.25   0.00

我一般重點關注的指標有:

  1. rkB/s和wkB/s: 分別對應讀寫速度
  2. avgqu-sz: 讀寫隊列的平均請求長度,可以類比top命令的load average
  3. await r_await w_await: io請求的平均時間(毫秒),分別是讀寫,讀和寫三個平均值。這個時間都包括在隊列中等待的時間和實際處理讀寫請求的時間,還有svctm這個參數,他說的是實際處理讀寫請求的時間,照理來講w_await肯定是大於svctm的,但是我在線上看到有w_await小於svctm的情況,不知道是什麼原因。我看iostat的man手動中說svctm已經廢棄,所以一般我看的是這三個。
  4. %util: 這個參數直觀的看磁盤的負載情況,我首先看的就是這個參數。和top的wa命令有關聯。

df

查看文件系統的容量

常用參數: -h 友好的單位 如Kb,Mb等

du

統計具體的文件大小

常用參數: -h 友好的單位 如Kb,Mb等 -s 總計,而不是進入每個子目錄分別統計

場景:例如系統磁盤空間不足時,先通過df命令定位到具體的掛載目錄,在進去掛載目錄后,使用
du -sh *查看各個文件或者子目錄的大小定位具體文件

這裏還有ls命令,可以通過加-h和-S(按大小排序)

iostat命令

常用參數: -k 用kb為單位 -d 監控磁盤 -x显示詳情 num count 每個幾秒刷新 显示次數

這個是我查看磁盤負載的主要工具,也可以显示cpu的負載,不過我一般用iostat -kdx 2 10,下面是我測試環境執行情況:

root@ubuntu:~# iostat -kdx 2 10
Linux 4.13.0-38-generic (ubuntu)    11/18/2018  _x86_64_    (1 CPU)
Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda              24.75   196.05  121.66    9.75  2481.33   961.29    52.40     0.44    3.33    1.12   30.95   0.51   6.71
scd0              0.00     0.00    0.02    0.00     0.08     0.00     7.00     0.00    0.25    0.25    0.00   0.25   0.00

我一般重點關注的指標有:

  1. rkB/s和wkB/s: 分別對應讀寫速度
  2. avgqu-sz: 讀寫隊列的平均請求長度,可以類比top命令的load average
  3. await r_await w_await: io請求的平均時間(毫秒),分別是讀寫,讀和寫三個平均值。這個時間都包括在隊列中等待的時間和實際處理讀寫請求的時間,還有svctm這個參數,他說的是實際處理讀寫請求的時間,照理來講w_await肯定是大於svctm的,但是我在線上看到有w_await小於svctm的情況,不知道是什麼原因。我看iostat的man手動中說svctm已經廢棄,所以一般我看的是這三個。
  4. %util: 這個參數直觀的看磁盤的負載情況,我首先看的就是這個參數。和top的wa命令有關聯。

lsof

列出當前系統打開文件,因為在linux下一切皆是文件,連接,硬件等均被描述為文件,所以這個命令也十分有用。

常用參數:

  1. -p 查看某個進程的文件
  2. 直接加文件名 查看哪些進程打開了文件
  3. +d 目錄 查看哪些進程打開了目錄以及下面的文件(不遞歸,+D是遞歸)

Sar
最後補充一個sar(System Activity Reporter)命令,如果系統沒有一個良好的監控,那麼這個命令對於排查問題是很好的補充,很多時候去排查問題的時候發現問題已經沒了,可以通過這個命令查看系統的活動情況,比如各個時間段cpu情況,內存情況。

常用參數:

  1. -r 內存信息
  2. -q loader信息,運行隊列情況
  3. -u cpu信息
  4. -W Swap換頁情況

/proc文件系統

/proc是個虛擬文件系統,是內核的一些數據,很多linux命令的都是通過解析/proc文件系統實現的,每個進程都會有一個以pid為目錄名的子目錄存在,通過解析/proc下的進程目錄可以得到很多進程的設置信息和資源佔用信息等。

這裏簡單說個排查過的問題,當時我們線上有個服務,正常ssh登錄的情況下,我們設置了ulimit中的open files為(進程可打開的最大描述符數量)100000,但是有一次在服務的日誌中發現有報錯說文件描述符不夠用。所以

二、JVM

java -XX:+PrintFlagsInitial 可以查看所以的jvm默認參數,其中帶有manageable表示運行時可以動態修改。

20:45 [root@centos]$ java -XX:+PrintFlagsInitial |grep manageable
     intx CMSAbortablePrecleanWaitMillis            = 100                                 {manageable}
     intx CMSTriggerInterval                        = -1                                  {manageable}
     intx CMSWaitDuration                           = 2000                                {manageable}
     bool HeapDumpAfterFullGC                       = false                               {manageable}
     bool HeapDumpBeforeFullGC                      = false                               {manageable}
     bool HeapDumpOnOutOfMemoryError                = false                               {manageable}
    ccstr HeapDumpPath                              =                                     {manageable}
    uintx MaxHeapFreeRatio                          = 70                                  {manageable}
    uintx MinHeapFreeRatio                          = 40                                  {manageable}
     bool PrintClassHistogram                       = false                               {manageable}
     bool PrintClassHistogramAfterFullGC            = false                               {manageable}
     bool PrintClassHistogramBeforeFullGC           = false                               {manageable}
     bool PrintConcurrentLocks                      = false                               {manageable}
     bool PrintGC                                   = false                               {manageable}
     bool PrintGCDateStamps                         = false                               {manageable}
     bool PrintGCDetails                            = false                               {manageable}
     bool PrintGCID                                 = false                               {manageable}
     bool PrintGCTimeStamps                         = false                               {manageable}

Java堆和垃圾收集器

java內存結構

堆內存結構:

java8元空間改動:

java 7種垃圾收集器:

常見搭配:

  1. java8默認:Parallel Scavenge和 Parallel Old
  2. 低延遲:ParNew和CMS
  3. java8以後可以直接使用G1,參數比較簡單

ParNew

Serial的并行版本

Parallel Scavenge

注重的是吞吐量,吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),其具有自適應的特性

  1. 控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis參數
    MaxGCPauseMillis參數允許的值是一個大於0的毫秒數,收集器將儘力保證內存回收花費的時間不超過設定值。不過大家不要異想天開地認為如果把這個參數的值設置得稍小一點就能使得系統的垃圾收集速度變得更快,GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的:系統把新生代調小一些,收集300MB新生代肯定比收集500MB快吧,這也直接導致垃圾收集發生得更頻繁一些,原來10秒收集一次、每次停頓100毫秒,現在變成5秒收集一次、每次停頓70毫秒。停頓時間的確在下降,但吞吐量也降下來了。

  2. 直接設置吞吐量大小的 -XX:GCTimeRatio參數
    GCTimeRatio參數的值應當是一個大於0小於100的整數,也就是垃圾收集時間佔總時間的比率。如果把此參數設置為19,那允許的最大GC時間就佔總時間的5%(即1 /(1+19)),默認值為99,就是允許最大1%(即1 /(1+99))的垃圾收集時間。

  3. UseAdaptiveSizePolicy開關參數
    -XX:+UseAdaptiveSizePolicy是一個開關參數,當這個參數打開之後,就不需要手工指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)、晉陞老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量,這種調節方式稱為GC自適應的調節策略(GC Ergonomics)。

說說UseAdaptiveSizePolicy參數,加了這個參數-XX:SurvivorRatio會失效,所以有些人會發現新生代比例未如自己的預期,而UseAdaptiveSizePolicy有默認是開啟的

CMS

併發垃圾收集器,注重的是時延,有分配擔保失敗的風險

CMS收集器的GC周期由6個階段組成。其中4個階段(名字以Concurrent開始的)與實際的應用程序是併發執行的,而其他2個階段需要暫停應用程序線程。

初始標記:為了收集應用程序的對象引用需要暫停應用程序線程,該階段完成后,應用程序線程再次啟動。
併發標記:從第一階段收集到的對象引用開始,遍歷所有其他的對象引用。
併發預清理:改變當運行第二階段時,由應用程序線程產生的對象引用,以更新第二階段的結果。
重標記:由於第三階段是併發的,對象引用可能會發生進一步改變。因此,應用程序線程會再一次被暫停以更新這些變化,並且在進行實際的清理之前確保一個正確的對象引用視圖。這一階段十分重要,因為必須避免收集到仍被引用的對象。
併發清理:所有不再被應用的對象將從堆里清除掉。
併發重置:收集器做一些收尾的工作,以便下一次GC周期能有一個乾淨的狀態。

  1. -XX:CMSInitiatingOccupancyFraction=90 (jdk1.5默認值68,1.6開始默認值92,指設定CMS在對內存佔用率達到70%的時候開始GC(因為CMS會有浮動垃圾,所以一般都較早啟動GC)
  2. -XX:+UseCMSInitiatingOccupancyOnly 只是用設定的回收閾值(上面指定的70%),如果不指定,JVM僅在第一次使用設定值,後續則自動調整
  3. -XX:+CMSScavengeBeforeRemark 在CMS GC前啟動一次ygc,目的在於減少old gen對ygc gen的引用,降低remark時的開銷
  4. -XX:+CMSParallelRemarkEnabled 併發標記
  5. -XX:+ExplicitGCInvokesConcurrent命令JVM無論什麼時候調用系統GC(system.gc()),都執行CMS GC,而不是Full GC
  6. -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保證當有系統GC調用時,永久代也被包括進CMS垃圾回收的範圍內
  7. -XX:UseParNewGC 使用CMS時自動開啟,因為CMS不能和Parallel Scavenge搭配使用

上面的參數都建議開啟,CMS需要注意的一個問題就是CMSInitiatingOccupancyFraction參數,這個參數直接影響CMS回收老年代的時機,需要結合自己的業務場景來調整,一般情況下應該盡量設置大一點,但是有一個嚴重的問題,就是浮動垃圾的問題,如果CMS在併發收集的時候出現老年代不能存放晉陞對象將直接進行Full GC使用Serial Old垃圾收集器,所以不能一味追求最大化,如果老年代增長比較慢,那麼可以設置的稍微較大些,如果增長比較快,可以從增大新生代,調低CMSInitiatingOccupancyFraction入手

最後在提下-XX:+DisableExplicitGC :禁用显示gc (system.gc())這個參數,很多人因為system.gc()會導致Full gc而禁用显示調用gc,但是這個參數最好不要禁用,現在很多服務端程序都使用了Nio,jvm為了減少內存拷貝,採用了直接內存,直接內存屬於堆外內存,java大多使用了Netty這個框架,他幫我們處理堆外內存的回收,實現的機制就是通過調用system.gc(),發起Full Gc,Full Gc會回收堆外內存,如果將system.gc()禁用,則得等到Full Gc發生才能回收堆外內存,很有可能出現堆外內存佔用過高影響系統性能或者因為內存不足被系統Kill的問題。

gc日誌參數

  1. -XX:+PrintGC 輸出GC日誌
  2. -XX:+PrintGCDetails 輸出GC的詳細日誌
  3. -XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式)
  4. -XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
  5. -XX:+PrintHeapAtGC 在進行GC的前後打印出堆的信息
  6. -XX:+PrintGCApplicationStoppedTime // 輸出GC造成應用暫停的時間
  7. -Xloggc:../logs/gc.log 日誌文件的輸出路徑
  8. -XX:+PrintTenuringDistribution 打印新生代的年齡分佈(這裏需要注意,如果使用的是Parallel Scavenge,那麼打印的時候是沒有年齡分佈信息的)
  9. -XX:+UseGCLogFileRotation 開啟日誌輪換
  10. -XX:NumberOfGCLogFiles=5 日誌保留數量
  11. -XX:GCLogFileSize=10m 每份日誌保留大小

堆參數

  1. -Xms 最小堆大小
  2. -Xmx 最大堆大小
  3. -Xmn 新生代大小
  4. -XX:SurvivorRatio 新生代中Eden區與Survivor區的比例,默認值為8

gc日誌分析

ParNew Gc日誌:

{Heap before GC invocations=4196 (full 3):
 par new generation   total 1887488K, used 1683093K [0x0000000640000000, 0x00000006c0000000, 0x00000006c0000000)
  eden space 1677824K, 100% used [0x0000000640000000, 0x00000006a6680000, 0x00000006a6680000)
  from space 209664K,   2% used [0x00000006a6680000, 0x00000006a6ba5430, 0x00000006b3340000)
  to   space 209664K,   0% used [0x00000006b3340000, 0x00000006b3340000, 0x00000006c0000000)
 concurrent mark-sweep generation total 4194304K, used 1565111K [0x00000006c0000000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 59881K, capacity 64953K, committed 66588K, reserved 1107968K
  class space    used 6615K, capacity 7729K, committed 8224K, reserved 1048576K
2019-10-29T23:48:00.181+0800: 27966.548: [GC (Allocation Failure) 2019-10-29T23:48:00.181+0800: 27966.548: [ParNew
Desired survivor size 107347968 bytes, new threshold 15 (max 15)
- age   1:    2287832 bytes,    2287832 total
- age   2:     132752 bytes,    2420584 total
- age   3:     102408 bytes,    2522992 total
- age   4:     125768 bytes,    2648760 total
- age   5:     145464 bytes,    2794224 total
- age   6:      82808 bytes,    2877032 total
- age   7:     104736 bytes,    2981768 total
- age   8:      79216 bytes,    3060984 total
- age   9:      89496 bytes,    3150480 total
- age  10:      81864 bytes,    3232344 total
- age  11:      91304 bytes,    3323648 total
- age  12:      78912 bytes,    3402560 total
- age  13:      80960 bytes,    3483520 total
- age  14:      91560 bytes,    3575080 total
- age  15:      78992 bytes,    3654072 total
: 1683093K->5343K(1887488K), 0.0342117 secs] 3248204K->1570530K(6081792K), 0.0343754 secs] [Times: user=0.17 sys=0.01, real=0.03 secs]
Heap after GC invocations=4197 (full 3):
 par new generation   total 1887488K, used 5343K [0x0000000640000000, 0x00000006c0000000, 0x00000006c0000000)
  eden space 1677824K,   0% used [0x0000000640000000, 0x0000000640000000, 0x00000006a6680000)
  from space 209664K,   2% used [0x00000006b3340000, 0x00000006b3877f50, 0x00000006c0000000)
  to   space 209664K,   0% used [0x00000006a6680000, 0x00000006a6680000, 0x00000006b3340000)
 concurrent mark-sweep generation total 4194304K, used 1565186K [0x00000006c0000000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 59881K, capacity 64953K, committed 66588K, reserved 1107968K
  class space    used 6615K, capacity 7729K, committed 8224K, reserved 1048576K
}

gc日誌中打印了新生代,老年代和元空間等內存信息,其中Times: user=0.02 sys=0.01, real=0.01 secs三個時間分別是用戶態的時間,內核態的時間和牆鍾時間。牆鍾時間表示真正過去的時間,而用戶態和內核態的時間則是乘了相應的cpu核心數。

CMS GC日誌:

2019-10-29T18:03:19.578+0800: 7285.945: [GC (CMS Initial Mark) [1 CMS-initial-mark: 3182477K(4194304K)] 3254261K(6081792K), 0.0044508 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
2019-10-29T18:03:19.582+0800: 7285.949: [CMS-concurrent-mark-start]
2019-10-29T18:03:20.812+0800: 7287.179: [CMS-concurrent-mark: 1.229/1.229 secs] [Times: user=3.86 sys=0.46, real=1.23 secs]
2019-10-29T18:03:20.812+0800: 7287.179: [CMS-concurrent-preclean-start]
2019-10-29T18:03:20.823+0800: 7287.190: [CMS-concurrent-preclean: 0.011/0.011 secs] [Times: user=0.03 sys=0.01, real=0.01 secs]
2019-10-29T18:03:20.823+0800: 7287.190: [CMS-concurrent-abortable-preclean-start]
{Heap before GC invocations=896 (full 3):
 par new generation   total 1887488K, used 1747877K [0x0000000640000000, 0x00000006c0000000, 0x00000006c0000000)
  eden space 1677824K, 100% used [0x0000000640000000, 0x00000006a6680000, 0x00000006a6680000)
  from space 209664K,  33% used [0x00000006a6680000, 0x00000006aaae9780, 0x00000006b3340000)
  to   space 209664K,   0% used [0x00000006b3340000, 0x00000006b3340000, 0x00000006c0000000)
 concurrent mark-sweep generation total 4194304K, used 3182477K [0x00000006c0000000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 60431K, capacity 66281K, committed 66588K, reserved 1107968K
  class space    used 6828K, capacity 8138K, committed 8224K, reserved 1048576K
2019-10-29T18:03:25.649+0800: 7292.016: [GC (Allocation Failure) 2019-10-29T18:03:25.649+0800: 7292.016: [ParNew
Desired survivor size 107347968 bytes, new threshold 15 (max 15)
- age   1:    1362152 bytes,    1362152 total
- age   3:     124920 bytes,    1487072 total
- age   4:     115256 bytes,    1602328 total
- age   5:     165000 bytes,    1767328 total
- age   6:      99776 bytes,    1867104 total
- age   7:      97728 bytes,    1964832 total
- age   8:      94616 bytes,    2059448 total
- age   9:      93176 bytes,    2152624 total
- age  10:     111352 bytes,    2263976 total
- age  11:     127800 bytes,    2391776 total
- age  12:      85248 bytes,    2477024 total
- age  13:     110984 bytes,    2588008 total
- age  14:     101880 bytes,    2689888 total
- age  15:      96288 bytes,    2786176 total
: 1747877K->18163K(1887488K), 0.0364969 secs] 4930355K->3200776K(6081792K), 0.0366162 secs] [Times: user=0.17 sys=0.00, real=0.04 secs]
Heap after GC invocations=897 (full 3):
 par new generation   total 1887488K, used 18163K [0x0000000640000000, 0x00000006c0000000, 0x00000006c0000000)
  eden space 1677824K,   0% used [0x0000000640000000, 0x0000000640000000, 0x00000006a6680000)
  from space 209664K,   8% used [0x00000006b3340000, 0x00000006b44fcd88, 0x00000006c0000000)
  to   space 209664K,   0% used [0x00000006a6680000, 0x00000006a6680000, 0x00000006b3340000)
 concurrent mark-sweep generation total 4194304K, used 3182613K [0x00000006c0000000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 60431K, capacity 66281K, committed 66588K, reserved 1107968K
  class space    used 6828K, capacity 8138K, committed 8224K, reserved 1048576K
}
 CMS: abort preclean due to time 2019-10-29T18:03:25.825+0800: 7292.192: [CMS-concurrent-abortable-preclean: 4.952/5.002 secs] [Times: user=10.51 sys=1.44, real=5.01 secs]
2019-10-29T18:03:25.826+0800: 7292.193: [GC (CMS Final Remark) [YG occupancy: 81039 K (1887488 K)]2019-10-29T18:03:25.826+0800: 7292.194: [Rescan (parallel) , 0.0142974 secs]2019-10-29T18:03:25.841+0800: 7292.208: [weak refs processing, 0.0019208 secs]2019-10-29T18:03:25.843+0800: 7292.210: [class unloading, 0.0230836 secs]2019-10-29T18:03:25.866+0800: 7292.233: [scrub symbol table, 0.0054818 secs]2019-10-29T18:03:25.871+0800: 7292.238: [scrub string table, 0.0707817 secs][1 CMS-remark: 3182613K(4194304K)] 3263652K(6081792K), 0.1182958 secs] [Times: user=0.17 sys=0.01, real=0.11 secs]
2019-10-29T18:03:25.946+0800: 7292.313: [CMS-concurrent-sweep-start]
2019-10-29T18:03:27.771+0800: 7294.138: [CMS-concurrent-sweep: 1.825/1.826 secs] [Times: user=3.98 sys=0.52, real=1.82 secs]
2019-10-29T18:03:27.771+0800: 7294.138: [CMS-concurrent-reset-start]
2019-10-29T18:03:27.781+0800: 7294.148: [CMS-concurrent-reset: 0.010/0.010 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]

JVMTI介紹

JVM相關參數:

 -agentlib:<庫名>[=<選項>]
                  加載本機代理庫 <庫名>, 例如 -agentlib:jdwp
                  另請參閱 -agentlib:jdwp=help
-agentpath:<路徑名>[=<選項>]
                  按完整路徑名加載本機代理庫
-javaagent:<jar 路徑>[=<選項>]
                  加載 Java 編程語言代理, 請參閱 java.lang.instrument

JVMTI(Java Virtual Machine Tool Interface)即指Java虛擬機工具接口,它是一套由虛擬機直接提供的 native 接口,通過這些接口,開發人員不僅調試在該虛擬機上運行的 Java 程序,還能查看它們運行的狀態,設置回調函數,控制某些環境變量(JMX),從而優化程序性能。Java Agent就是基於JVMTI的,所以眾多基於Java Agent的技術例如APM,遠程調試,各種性能剖析同樣是基於這個技術。

JVMTI 接口:

JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved);

JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved);

JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm); 

-agentpath是c/c++編寫的動態庫,-agentlib和-javaagent是一個instrument的JVMTIAgent(linux下對應的動態庫是libinstrument.so)。

Attach機制

Jvm提供一種jvm進程間通信的能力,能讓一個進程傳命令給另外一個進程,並讓它執行內部的一些操作,比如說我們為了讓另外一個jvm進程把線程dump出來,那麼我們跑了一個jstack的進程,然後傳了個pid的參數,告訴它要哪個進程進行線程dump。

Attach命令列表

static AttachOperationFunctionInfo funcs[] = {
  { "agentProperties",  get_agent_properties },
  { "datadump",         data_dump },
  { "dumpheap",         dump_heap },
  { "load",             JvmtiExport::load_agent_library },
  { "properties",       get_system_properties },
  { "threaddump",       thread_dump },
  { "inspectheap",      heap_inspection },
  { "setflag",          set_flag },
  { "printflag",        print_flag },
  { "jcmd",             jcmd },
  { NULL,               NULL }
};

Attach流程:

Jstack源碼:
https://android.googlesource.com/platform/libcore/+/0ebbfbdbca73d6261a77183f68e1f3e56c339f9f/ojluni/src/main/java/sun/tools/jstack/JStack.java

查看java線程:

其中Siginal Dispatcher是處理進程信號的線程,Attach Listener正式Attach機制處理線程。

java自帶工具

jps

查看Java進程列表

常用參數:

  1. -l: 輸出應用程序主類完整package名稱或jar完整名稱
  2. -m:輸出主函數傳入的參數

jmap

查看JVM堆的情況

常用參數:

  1. -heap
  2. -dump 這個命令還有兩個常用參數
    1. live 只dump存活對象,會導致GC
    2. file=file dump文件名

示例:jmap -dump:live,file=heap.dump <pid>

這裡有兩點,一方面需要注意live會導致GC,有時候在查問題的時候可能不是你預期的效果,一般查內存問題時不加這個選項,另外dump文件如果比較大,可以先壓縮在傳回本地

jstack

查看JVM的堆棧情況,監測死鎖等

這個命令比較簡單,一般不用加什麼參數,有時候JVM沒響應時可以加-F參數。一般這個命令可以結合top,在top定位到佔用cpu高的線程后,在具體在Jstack打印的堆棧中查看線程,有時候也需要多次打印堆棧來進行對比

jstat

查看JVM gc信息,觀察JVM的GC活動

常用參數: -gccause 這個參數包含了-gcutil的信息多了一個gc原因

示例: jstat -gccause <pid> 1000

11:19 [supertool@y051]$ jstat -gccause  10711 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT    LGCC                 GCC                 
  0.00  21.23  95.99  69.88  91.56  82.62   1187   22.511     4    0.141   22.652 Allocation Failure   No GC               
  0.00  21.23  99.51  69.88  91.56  82.62   1187   22.511     4    0.141   22.652 Allocation Failure   No GC               
 21.30   0.00   3.51  69.88  91.56  82.62   1188   22.530     4    0.141   22.671 Allocation Failure   No GC               
 21.30   0.00   7.02  69.88  91.56  82.62   1188   22.530     4    0.141   22.671 Allocation Failure   No GC               
 21.30   0.00  10.14  69.88  91.56  82.62   1188   22.530     4    0.141   22.671 Allocation Failure   No GC               
 21.30   0.00  13.62  69.88  91.56  82.62   1188   22.530     4    0.141   22.671 Allocation Failure   No GC               
 21.30   0.00  16.78  69.88  91.56  82.62   1188   22.530     4    0.141   22.671 Allocation Failure   No GC               

jinfo

查看設置的JVM參數和啟動時的命令行參數,還可以動態修改JVM參數

常用參數

  1. -flags 查看jvm參數值
  2. -sysprops 查看系統屬性值

示例:jinfo -flags 10711

Non-default VM flags: -XX:BiasedLockingStartupDelay=0 -XX:CICompilerCount=4 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=75 -XX:+CMSParallelRemarkEnabled -XX:ErrorFile=null -XX:GCLogFileSize=10485760 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:MaxNewSize=268435456 -XX:MaxTenuringThreshold=15 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=268435456 -XX:NumberOfGCLogFiles=20 -XX:OldPLABSize=16 -XX:OldSize=805306368 -XX:+PrintClassHistogram -XX:+PrintCommandLineFlags -XX:+PrintConcurrentLocks -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:StringTableSize=6000000 -XX:+UseBiasedLocking -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseFastUnorderedTimeStamps -XX:+UseGCLogFileRotation -XX:+UseParNewGC 
Command line:  -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 -XX:+PrintCommandLineFlags -Xms1g -Xmx1g -Xmn256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -Dfile.encoding=UTF-8 -XX:MaxTenuringThreshold=15 -XX:StringTableSize=6000000  -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC -XX:+PrintClassHistogram -XX:+PrintConcurrentLocks -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=20 -XX:GCLogFileSize=10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/java/logs -XX:ErrorFile=/var/java/logs/jvm-error.log -Dlog4j.config.file=log4j_.properties -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.Log4jLogDelegateFactory -Dvertx.options.maxEventLoopExecuteTime=100000000 -Dvertx.options.warningExceptionTime=300000000 

JDPA(Java Platform Debugger Architecture)

java遠程調試,需要jvm啟動時加參數:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

遠程調試非常有用,有時候測試環境很難復現時,可以用通過遠程調試查看線程數據

三、三方工具

jprofile

CPU性能分析

抽樣:每隔一段時間,獲取線程棧,分析各個棧上出現的方法的次數

優點:性能高
缺點: 不適合做精確的分析
適用範圍:尋找程序的執行熱點,cpu密集型

指令插入:使用增強的技術修改java class的字節碼,在函數的出入口增加埋點

優點:數據準確
缺點:導致jvm內聯優化失效,性能低
適用範圍:分析具體耗時路徑的各個執行時間,io密集型

一般先使用抽樣在定位到大致的範圍,然後使用指令插入分析具體代碼執行路徑中的耗時,jprofile可以通過過濾只對指定類進行增強

  1. Thread Status:選擇線程統計狀態,Runnable显示的是cpu時間,不包含sleep這種時間一般都是這個模式。還可以使用IO Net模式分析io等待,Wait分析鎖競爭模式
  2. Call tree filters :調用樹過濾:用於過濾不需要的類,例如你使用web框架,棧中起始的方法都是框架中的代碼,最後才是你的業務代碼,這時候可以使用Call tree filters來過濾不需要的類型,減少統計造成的性能開銷

內存剖析

分析內存泄漏的利器,主要是看內存中內存佔比和大對象。很多時候如果有內存泄漏基本都是以為某些類型的對象佔用了大頭。

arthas (類似btrace的工具)

Arthas 是Alibaba開源的Java診斷工具。線上debug的工具,很多時候因為性能和安全等原因我們不能直接遠程調試線上的jvm,這時候我們可以使用arthas來查看內存的數據,方法調用情況,打印日誌信息等。

比較常用的:

  1. watch 看方法調用情況 -c 統計周期,默認值為120秒
  2. monitor 統計方法調用信息
  3. getstatic 查看靜態變量
  4. logger 查看和修改logger
  5. trace 方法內部調用路徑,並輸出方法路徑上的每個節點上耗時

示例:

  1. monitor -c 5 com.miaozhen.bazaro.deal.PreferredDealFilterService filter
  2. watch com.miaozhen.bazaro.share.manager.util.DealManager getDspToDealsByPid "returnObj"

gceasy

https://gceasy.io/ 一個在線分析gc日誌的網站,

四、實際案例

連接泄漏

場景描述:我們公司的用戶服務對接了第三方騰訊雲通信服務,在用戶註冊的時候我們需要走http接口調騰訊雲,問題就出在http連接那塊,同事當時採用了,線上出現了cpu100%的問題,日誌出現java.lang.OutOfMemoryError: GC overhead limit exceeded。

排查思路:這個其實很好定位,本來還想打印線程棧看下到底是哪個導致的cpu100%,一看日誌直接定位到gc出問題。GC overhead limit exceeded是指gc佔用了大量的cpu時間又回收不了內存引起的,從內存泄露去考慮,重啟服務 ,啟動參數加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./user.hprof -verbose:gc -Xloggc:user%t.log。問題復現的時候獲得了堆的dump文件,然後通過Jprofile分析,發現有大量的http.HttpKeepAliveCache實例,佔用了80%的內存,大致定位到是由於http連接泄露。同事封裝的HttpUtil中使用了HttpsURLConnection,在讀取完數據的時候沒有關閉InputStream導致連接沒有關閉。

說明:GC overhead limit exceeded,默認情況下,如果Java進程花費98%以上的時間執行GC,並且每次只有不到2%的堆被恢復,則JVM拋出此錯誤。這個錯誤是parallel Scavenge 特有的

String拼接導致內存溢出

公司的後台有段時間會間歇性的卡頓,嚴重的情況下會導致cpu100%。在cpu100%的時候,通過top定位到進程號,然後輸入H切換到線程,記住具體的進程號,使用jstack打印java進程的線程棧,jstack輸出為十六進制,需要將top的轉換成十六進制的然後入找線程經常卡在哪個方法。定位到方法發現是查詢用戶關聯設備號的方法出問題,方法的邏輯是從數據庫查詢設備號,在內存中以以逗號分隔拼接返回,如1,2,3。這個bug的原因是有如下:

sql出錯,導致查詢返回數據量很多,正常情況最多幾百個,但是異常情況有七萬個設備號
字符串拼接採用str+=”1234″的形式,導致大量的內存分配和回收。
運營在點擊後台查詢的時候發現沒返回,點掉就重新點,導致服務器多個線程卡在這個方法造成cpu100%。解決完sql,改用StringBuilder問題解決。

堆內存佔用過大

我們的一個服務程序,老年代設置了10g,新生代2g,偶會會出現內存溢出的線程,通過分析內存發現deal數據佔用了大量內存,最高可達9.4g。

堆數據:

問題代碼:

優化后堆數據:

優化后降低了老年代改為4g,大大降低了Jvm的堆的大小,16g機器現在可部署兩個實例,且Full Gc穩定在一天一次,Young Gc 5s一次,均處正常。

CPU佔用高問題

最近在分析拍賣程序時,發現com.miaozhen.bazaro.deal.PreferredDealFilterService#filter方法佔用了90%的cpu時間。

cpu熱點圖:

問題代碼:

分析該方法的時長:

查看耗時deal數據

aerospike線程阻塞導致內存溢出問題

問題:拍賣在五點多收到網站推送數據的時候發生OOM。

查看日誌發現,有很多關於線程阻塞的報錯,是讀取aerospike卡住導致。報錯如下:

觀察gc分析結果:

可以看到本來堆內存始終穩定在一個水平,在一個時間點之後,堆內存開始穩步上漲,十分符合內存泄漏的特徵。

觀察堆內存數據:

注:這個堆內存不是當時,當時的堆內存沒找到,佔比是類似的。這個圖內存優化之後的,所以老年代只有4g。

可以看到其中OrderedExecutor佔用了大量的內存,這個數據接口是用來存放http請求的接口。

總結:

晚上九點40線程阻塞,但是請求的任務不停地往他的tasks裏面放,十分鐘后grafana監控显示上升了16%的超時率(六個verticle掛了一個),從4%到20%。
查看內存監控圖,9點40開始內存上升,不再回收,最終存了2900萬個tasks,一個線程佔用了10g內存,到晚上11.15左右日誌出現大量的空指針和超時,十分鐘后監控圖显示全部超時,gc監控显示大量full gc,因為內存不夠大量的gc佔用了進程cpu時間。,5點多的時候推送物料,服務器內存溢出。

參考資料:

問題

  1. 什麼樣的代碼算是耗時的代碼,或者說耗時代碼的特徵是什麼
  2. jvm一個線程發生OOM會導致JVM掛掉嗎
  3. 內存問題會導致cpu飆高嗎

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

中國發佈《電動汽車充電基礎設施發展指南(2015-2020年)》

  各省、自治區、直轄市、新疆生產建設兵團發展改革委(能源局)、工業和資訊化主管部門、住房城鄉建設廳(委、局),國家電網公司、南方電網公司:   為落實《國務院辦公廳關於加快新能源汽車推廣應用的指導意見》(國辦發〔2014〕35號),科學引導電動汽車充電基礎設施建設,促進電動汽車產業健康快速發展,我們組織編制了《電動汽車充電基礎設施發展指南(2015-2020年)》,現予印發,請認真貫徹執行。  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

納智捷LUXGEN推電動車版S3房車

納智捷(LUXGEN)全力搶攻小車市場,在2015台北國際新車大展上以「智能與電能」為主題,正式推出電動車S3 EV+,造型與汽油版的S3 Turbo大致相同。同時展出的還有S5、U7、U6、M7 TURBO ERO HYPER與V7 TURBO等車款。

在十位LUXGEN Girls「十全十美」的陣容下,納智捷在台北新車大展舉行之前預先舉辦展示活動。LUXGEN將成立科技互動體驗區,帶領參展民眾親身體驗虛擬實境的感受。

S3 EV+ 本次首度亮相,以LUXGEN旗下最小型的房車S3為設計藍本。LUXGEN先前已透露2016年第二季將推出首款小型入門車S3 Turbo,搭配1.5升直列四缸全鋁合金汽油引擎、日本Aisin六速手自排變速箱,油耗最低可達每公升行駛17公里。S3 EV+ 之造型與S3 Turbo相似,但具體規格還未公告。

今年的台北新車大展將於12月25日於台北世貿一館舉行。

(照片來源:LUXGEN)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

簡單看看@RequestBody註解原理

       又到了很無聊的時候了,於是隨便看看源碼假裝自己很努力的樣子,哈哈哈;

  記得上一篇博客隨便說了一下RequestBody的用法以及注意的問題,這個註解作為非常常用的註解,也是時候了解一波其中的原理了。

    溫馨提示:閱讀本篇博客,默認你之前大概看過springmvc源碼,懂得其中的基本流程

1.HttpMessageConverter接口

  這個接口就是@RequestBody和@ResponseBody這兩個註解的精髓,我們就先看看這個頂層接口定義了哪些方法:

public interface HttpMessageConverter<T> {

    //判斷當前轉換器是否可以解析前端傳過來的數據
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

    //判斷當前轉換器是否可以將後端數據解析為前端需要的格式
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

    //當前轉換器能夠解析所有的數據類型
    List<MediaType> getSupportedMediaTypes();

    //這個方法就是讀取前端傳過來的數據
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

    //將後台數據轉換然後返回給前端
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

}

   

  我們從這個頂層接口中這幾個方法就大概能看到一些東西,可以肯定的就是在@RequestBody和@ResponseBody這兩個註解原理的內部轉換器應該都是實現了這個HttpMessageConverter,因為這個接口裡又是read,又是write;然後就是在上面我只是簡單說了前端傳過來的數據,返回給前端需要的格式這種模糊的說法,為什麼不直接說返回json格式的數據呢?

  哈哈,可能有的小夥伴會說,瑪德,你絕逼是怕說錯,才說這些模糊的說法;咳,當然有部分是這個意思,但是最大的原因就是上面方法參數中有個類MediaType,你打開看看就知道了,這裏定義了很多的可以解析的數據類型,比如”application/json”,”application/xml”,”image/gif”,”text/html”….等等,還有好多聽都沒聽過的;

  其實了解http請求的小夥伴應該已經看出來了,這裏這些數據類型就是下圖所示的這些;當然,我們暫時只關注json的,至於其他類型的怎麼解析有興趣的小夥伴可以研究研究;

 

 2.HandlerMethodArgumentResolver接口

  我們看看這個接口,看名字就知道應該是方法參數解析器,很明顯就是用於解析方法Controller中方法的參數的,還是簡單看看這個接口中的方法:

public interface HandlerMethodArgumentResolver {

    //該解析器是否支持解析Controller中方法中的參數,因為這裏參數類型可以是簡單類型,也可以是集合等類型
    boolean supportsParameter(MethodParameter parameter);

    //開始解析Http請求中的數據,解析出來的數據要和方法參數對應
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

   這個接口定義的方法作用其實就是將http請求中的參數對應到Controller中的參數

 

3.HandlerMethodReturnValueHandler接口 

public interface HandlerMethodReturnValueHandler {

    //這個方法判斷該處理器是否支持返回值類型,這裏的返回值就是controller方法執行后的返回值
    boolean supportsReturnType(MethodParameter returnType);

    //將controller方法的返回值進行解析成前端需要的格式,後續就會丟給前端
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

   這個接口的作用:比如一個Controller方法中的返回值是一個集合,那麼springmvc內部在將數據返回給前端之前,就會先拿到所有的返回值解析器,然後遍歷每一個,分別執行supportsReturnType方法,看看哪個解析器可以解析集合類型,找到解析器之後,然後再執行handleReturnValue方法解析就行了,其實2中的方法參數解析器也是這樣的一個步驟

 

4.ServletInvocableHandlerMethod類

  這個類是干什麼的呢?看過springmvc源碼的人應該知道一點,還是簡單說說吧,在springmvc中會將controller中的每個被RequestMapping註解修飾的方法(也可以叫做處理器)給封裝成ServletInvocableHandlerMethod類,封裝后想要執行該處理器方法只需要執行該類的invokeAndHandle方法;

  請注意:就是在invokeAndHandle這個方法中會調用:調用方法參數解析器——>執行處理器方法——–>調用返回值解析器、

  可以簡單看看源碼,請一定要了解springmvc的流程,因為我不會從頭到尾講一遍,我們直接從DispatcherServlet中的doDispatch方法的ha.handle(xxx)這裏說起,這裏主要是執行處理器適配器的handle方法,這裏具體的處理器適配器實現是:AbstractHandlerMethodAdapter

  

  我們進入AbstractHandlerMethodAdapter這個適配器的handle方法看看(^o^)/:

//可以看到這裏就是調用了handleInternal方法,而handleInternal方法未實現
public final ModelAndView handle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }


//這個方法在子類RequestMappingHandlerAdapter中實現
protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception;

 

  接下來我們看看RequestMappingHandlerAdapter中實現的handleInternal方法(」゜ロ゜)」:

protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception {

        //省略跟邏輯無關的代碼
                .......
               .........
        
        return invokeHandlerMethod(request, response, handlerMethod);
    }

 

  進入invokeHandlerMethod方法看看(´・_・`):

private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception {
        
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //省略一些代碼

        //就是再這裏去執行Controller中的處理器方法
        requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

        //此處省略好多代碼
    }

 

  繼續進入到invokeAndHandle方法內部看看╮(╯_╰)╭:

public final void invokeAndHandle(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
         //請注意,這裏面就是執行handler方法的位置
        Object returnValue = invokeForRequest(request, mavContainer, providedArgs);

        //省略一些代碼              

        try {
            //這裏就是執行我們前面說的返回值解析器
            returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
        } 
       //省略一些代碼
    }

 

  看看invokeForRequest方法你就能看到有趣的東西ヽ(”`▽´)ノ

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
                //這裏就是從request中拿到參數,利用方法參數解析器進行解析,映射到方法參數中,這個方法就在下面
                Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        
               //省略一些代碼
        
                //這裏就是根據上一步將請求參數映射到處理器方法參數中,然後執行對應的處理器方法
               Object returnValue = doInvoke(args);
        
               //省略一些代碼
        
                return returnValue;
    }


private Object[] getMethodArgumentValues(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
          //這裏獲取匹配到的handler方法的參數數組,每個參數在之前都被封裝成了一個MethodParameter對象,然後再遍歷這個數組,將其中每個MethodParameter和http請求提供的參數進行比較,
至於怎麼比較,就會用到之前說的參數解析器的那個support方法
MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } //省略一些代碼 return args; }

 

     其實到這裏,有木有感覺清晰一點了,那麼肯定有小夥伴要問了,說了半天,你還是沒有說json是怎麼解析的啊?

  不要急,我們先把大概的流程過一遍之後,後面的都是小問題,那麼,我們的轉換器是在哪裡轉換的呢?

  欲知後事如何,請往後面看

 

5.無題

  在4中我們重點看兩個地方,第一個地方:this.argumentResolvers.supportsParameter(parameter);第二個地方:this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

  5.1.RequestResponseBodyMethodProcessor

    第一個地方,我們點進去supportsParameter方法,

   

    

  然後我們進入getArgumentResolver方法內部(´□`川):

@Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
             //for循環遍歷所有的方法參數解析器,
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                
//省略一些代碼
//判斷哪一個解析器支持解析request中的參數,這裏很關鍵,因為眾多解析器中其中有一個參數解析器是RequestResponseBodyMethodProcessor,
          //下面我們看看這個解析器的supportParameter方法
if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }

  

  RequestResponseBodyMethodProcessor的supportsParameter方法,這裏想必能看得懂吧,就是看Controller中的處理器方法中的參數前面有沒有RequestBody註解,有註解,那麼這個RequestResponseBodyMethodProcessor解析器就會生效

  對了,補充一點,這個解析器RequestResponseBodyMethodProcessor可是同時實現了方法參數解析器接口、返回參數解析器接口的哦,這說明了處理返回值解析器用的也是這個解析器

 

  5.2.執行argumentResolvers.resolveArgument()方法

//這個方法就到了最關鍵的地方了,注意了注意了
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 

  throws Exception { //獲取一個轉換器,用於讀取前端傳過來的json數據 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());

//獲取handler方法形參中的所有註解,例如@Valid。@PathVariable等 Annotation[] annotations = parameter.getParameterAnnotations();

     for (Annotation annot : annotations) {       //判斷如果是以valid開頭的註解,其實就是@Valid註解或者是@Validated註解,那麼就會去校驗是否符合規則嘛,這個不用多說         if(annot.annotationType().getSimpleName().startsWith("Valid")) { String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); BindingResult bindingResult = binder.getBindingResult(); if (bindingResult.hasErrors()) { throw new MethodArgumentNotValidException(parameter, bindingResult); } } } return arg; }

  最後我們只需要輕輕點開readWithMessageConverters方法,就能看到更有意思的東西~^o^~

 

6.xxxConverter

  我們進入readWithMessageConverters這個方法,

@SuppressWarnings("unchecked")
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Class<T> paramType) throws IOException,
            HttpMediaTypeNotSupportedException {
            //這裏回答了本篇最開始說的MediaType 到底是什麼東西,從哪裡獲取的,很明顯是從請求頭中的ContentType獲取的
             MediaType contentType = inputMessage.getHeaders().getContentType();
            if (contentType == null) {
                 contentType = MediaType.APPLICATION_OCTET_STREAM;
            }
            //獲取所有的HttpMessageConverter,遍歷,看看哪一個轉換器支持前端傳過來的數據類型
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                 if (messageConverter.canRead(paramType, contentType)) {
//調用對應的轉換器的read方法去把前端傳過來的json字符串轉為java對象
return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage); } } //省略一些代碼 }

  

   到了這裏肯定有人會說,那到底用的是哪一個轉換器呢?難道是要我們自己導入?還是默認已經導入轉換器了呢?

  當然是默認就為你初始化了一些轉換器了啊,如果你想自定義也行,而且仔細看看下圖中跟json有關的只有MappingJackson2HttpMessageConverter這個轉換器了,可想而知這個轉換器(實際上是這個轉換器的父類方法中才有具體的操作)中使用的就是開源的Jackson來將json字符串轉為java對象的,有興趣了解的可以使用一下Jackson自己嘗試一下;

  偷偷告訴你(҂ ˘ _ ˘ ),springboot默認已經導入了Jackson包,如果你後期想用其他的轉換器,只需要導入相關依賴就ok了;

  

 

 

7.結束

  這篇博客到這裏就差不多了,寫了好久,邊寫邊查資料,可以說每次看源碼都能學到新的東西,當然,我也沒有死磕精神,不是不想,主要是沒有那個水平,哈哈;

  其實還要寫還能寫,不是還有個@ResponseBody原理還沒說嗎?其實跟@RequestBody大同小異的,有興趣的可以在第4點最後的代碼中返回值參數處理器方法,這裏就是入口

  話說還有個問題沒有解決,本來我想找一下最後的那幾個轉換器初始化時機的,然後實在是找不出來啊,查了一下資料,都說是在處理器適配器的構造器中初始化這些轉換器的,哎,jdk1.8,我在適配器中找了好久,愣是沒找到,打斷點也調試不出來,把自己坑了好久;

  有沒有大哥知道初始化時機的,評論一下,謝謝了(ㄒoㄒ)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Java nio 空輪詢bug到底是什麼

編者注:Java nio 空輪詢bug也就是Java nio在Linux系統下的epoll空輪詢問題。

epoll機制是Linux下一種高效的IO復用方式,相較於select和poll機制來說。其高效的原因是將基於事件的fd放到內核中來完成,在內核中基於紅黑樹+鏈表數據結構來實現,鏈表存放有事件發生的fd集合,然後在調用epoll_wait時返回給應用程序,由應用程序來處理這些fd事件。

使用IO復用,Linux下一般默認就是epoll,Java NIO在Linux下默認也是epoll機制,但是JDK中epoll的實現卻是有漏洞的,其中最有名的java nio epoll bug就是即使是關注的select輪詢事件返回數量為0,NIO照樣不斷的從select本應該阻塞的Selector.select()/Selector.select(timeout)中wake up出來,導致CPU 100%問題。如下圖所示:

那麼產生這個問題的原因是什麼的?其實在 上已經說明的很清楚了,比如下面是bug復現的一個場景:

A DESCRIPTION OF THE PROBLEM :
The NIO selector wakes up infinitely in this situation..
0. server waits for connection
1. client connects and write message
2. server accepts and register OP_READ
3. server reads message and remove OP_READ from interest op set
4. client close the connection
5. server write message (without any reading.. surely OP_READ is not set)
6. server's select wakes up infinitely with return value 0

上面的場景描述的問題就是連接出現了RST,因為poll和epoll對於突然中斷的連接socket會對返回的eventSet事件集合置為POLLHUP或者POLLERR,eventSet事件集合發生了變化,這就導致Selector會被喚醒,進而導致CPU 100%問題。根本原因就是JDK沒有處理好這種情況,比如SelectionKey中就沒定義有異常事件的類型。

class SelectionKey {
    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;
}

既然nio epoll bug存在,那麼能不能規避呢?答案是有的,比如netty就很巧妙的規避了這個問題,它的處理機制就是如果發生了這種情況,並且發生次數超過了SELECTOR_AUTO_REBUILD_THRESHOLD(默認512),則調用rebuildSelector()進行Selecttor重建,這樣就不用管之前發生了異常情況的那個連接了。因為重建也是根據SelectionKey事件對應的連接來重新註冊的。

該問題最早在 Java 6 發現,隨後很多版本聲稱解決了該問題,但實際上只是降低了該 bug 的出現頻率,目前從網上搜索到的資料显示,Java 8 還是存在該問題()。

最後一起來分析下,nio epoll bug不是linux epoll的問題,而是JDK自己實現epoll時沒有考慮這種情況,或者說因為其他系統不存在這個問題,Java為了封裝(比如SelectionKey 中的4個事件類型)的統一而沒去處理?

這裏思考下,如果想要從java nio層面上來解決這個問題,該如何做呢?

一種是nio事件類型SelectionKey新加一種”錯誤”類型,比如針對linux epoll中的epollhup和epollerr,如果出現這種事件,建議程序直接close socket,但這種方式相對來說對於目前的nio SelectionKey改動有點大,因為SelectionKey的定義目前是針對所有jdk平台的;還有一種是針對jdk nio 對epoll的封裝中,對於epoll的epollhup和epollerr事件,epoll封裝內部直接處理,比如close socket,但是這種方案也有一點尷尬的是,可能上層應用代碼還保留有出現問題的socket引用,這時最好是應用程序能夠感知這種情況來處理比較好。

Java nio空轉問題由來已久,一般程序中是通過新建Selector的方式來屏蔽掉了JDK5/6的這個問題,因此,對於開發者來講,還是盡量將JDK的版本更新到最新,或者使用NIO框架如Netty,Grizzly等進行研發,以免出更多的問題。

推薦閱讀

歡迎小夥伴關注【TopCoder】閱讀更多精彩好文。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

第二次汽車下鄉政策或啟動 新能源汽車將成為受益者?

據報導,中國將啟動新一輪汽車下鄉政策,1.6升以下乘用車、微型客貨、皮卡等輕型貨車將納入產品範圍。與此同時,中國還計畫加大停車場和新能源汽車充電基礎設施建設力度。

分析人士認為,二三線城市及四五線的農村的汽車保有量較低,剛性需求較大,預計將成為未來汽車市場增長的主力。

中國品牌汽車與新能源汽車有望雙提升

據中汽協發佈的10月份汽車產銷資料來看,1-10月,中國乘用車累計銷售1648.47萬輛,中國品牌乘用車共銷售675.71萬輛,同比增長12.58%,乘用車銷售總量的40.99%,佔有率比上年同期提升3.16個百分點。

根據乘聯會廠家資料,2015年4季度的新能源乘用車出現爆發增長態勢。11月的新能源乘用車銷量24664台,暴增2.4倍,其中插電混合動力達到7599台,純電動車達到17065台。

一旦新一輪汽車下鄉政策實施,加之新能源汽車充電基礎設施建設力度不斷提升,這不僅將為二三線甚至四五線的消費者帶來更多新車選擇,更將帶動中國汽車品牌以及新能源汽車產銷實現新突破。

可以預見,即將到來的“第二次汽車下鄉”,有望對“十三五”開局之年的汽車產業和發展注入新的動力這一方面會進一步刺激中國品牌車企加速對四五線市場的深耕;另一方面,新能源汽車廠家的佈局與規劃能否滿足未來的市場需求,一切都尚待觀察。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

第六屆中國國際新能源汽車論壇2016打造行業規模最大,最國際化的新能源汽車論壇

2016年4月20-22日∣中國•上海

誰能智造未來-技術應用與新熱點

隨著全球能源危機的加重及汽車排放引起的環境問題日益得到世界各國的重視,發展環保節能的汽車已經成為全球汽車行業的必然趨勢。

在過去五屆新能源汽車論壇成功舉辦的基礎上,由希邁商務諮詢(上海)有限公司主辦的2016年第六屆中國國際新能源汽車論壇即將於4月20日-4月22日在上海隆重舉行。新能源汽車系列論壇成功邀請了包括國家發改委能源研究所、世界電動車協會、亞太電動車協會、世界氫能協會、世界分散式能源聯盟、中國工程院等在內的政府單位與研究機構,以及包括寶馬、賓士、奇瑞捷豹路虎、大眾、奧迪、比亞迪、上汽、北汽等在內的知名整車商,共同研討新能源汽車行業政策趨勢、技術路線及難點、基礎設施建設、商業模式等並取得了豐碩的成果,獲得了業內外人士的一致好評。

在即將到來的2016年,組委會為感謝業內外人士對系列論壇長期以來的支援和關注,將傾情奉上相比歷屆舉辦規模最大的第六屆新能源汽車論壇,涉及三個考察活動,主論壇及三個分論壇。屆時將誠邀全球範圍內的整車製造商、電網電力公司、電池廠商、零部件供應商、核心技術提供商和政府官員近500位行業人士一起,對新能源汽車產業面臨的挑戰,機遇與對策各方面進行為期三天更深層次並具有建設和戰略性的探討。

部份往屆演講嘉賓

會議亮點

豐富的內容:
8大板塊的深度解析

參會嘉賓:
500+高度滿意的企業決策者,120+業內知名企業,30+國家和地區

參會嘉賓分析:
17%+來自各國政府部門及權威機構,25%+來自知名整車商

演講嘉賓:
40+世界新能源汽車行業知名發言嘉賓

交流機會:
16+小時的交流機會:圓桌討論、VIP午宴和開放式問答

會議形式:
1個主論壇,1個晚宴,3個分論壇以及一天3個考察活動

會議結構

若您對峰會有更多要求,請撥打021-6045 1760與我們聯繫,謝謝理解和支持!
我們期待與貴單位一起出席於2016年4月20-22日在上海舉辦的第六屆中國國際新能源汽車論壇2016,以利決策!
想瞭解詳細內容,請登陸官方網站:
連絡人:Hill ZENG(曾先生)
電話:0086 21-6045 1760
傳真:0086 21-6047 5887
郵箱:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

為何特斯拉 Model S 有高滿意度,「擁有好的客服不怕產品出包」

俗話說早起的鳥兒有蟲吃,不過早買的消費者買到的第一批產品常常問題最多,因為日後的產品,隨著製造技術與經驗進步,以及收到第一批產品的問題回報,品質會逐漸改進。電動車也不例外,美國電動車及油電混合車推動組織 Plug In America 分析報告顯示,特斯拉(Tesla)Model S 電動車,其 2012 ~ 2013 年款的早期車主,可能有高達三分之二行駛 6 萬英里(9.66 萬公里)就需要更換傳動系統。

Plug In America 的分析報告資料來自 370 名車主,其中 43 名擁有 2014 ~ 2015 年份 Model S,由於是新買的車,行駛里程尚未超過 2 萬英里,而根據 327 名早期購買的車主資料進行數學運算分析,預測有 66% 車主行駛 6 萬英里就需要更換傳動系統。不過需要更換傳動系統不代表傳動系統已經故障損壞,因為特斯拉的政策是,一旦傳動系統開始發出不正常雜音就提早換新,而不是等到傳動系統真的故障損壞才換修,另外由於特斯拉保固 8 年,換新對車主也不會造成額外負擔。   這份資料也說明了為何 Model S 遭到一些負面評價,如美國消費性商品評鑑雜誌《消費者報告》(Consumer Reports)把 Model S 從推薦名單中除名,其原因就是認定其「問題發生率高於平均」。日後特斯拉多次表示其技術與經驗進步讓產品品質大幅提升,2015 年 11 月時更表示將故障率降至原有的一半,執行長伊隆‧馬斯克(Elon Musk)也對近幾個月出貨的傳動系統表示信心滿滿。   不過,到底特斯拉的製造品質是否提升,恐怕要等 2014 ~ 2015 年的車輛行駛更久才能見真章。   無論如何,可以確定的一點是,Model S  仍銷售破 10 萬輛,而《消費者報告》指出,儘管問題發生率高於平均,特斯拉車主的滿意度卻居高不下,有高達 97% 車主說會再買特斯拉,原因在於高效率的客服,以最快速度幫助消費者解決問題,不論是馬達、差速器、剎車、資訊系統出問題,都以最不讓消費者困擾的方式盡速更換,「誠意」讓人滿意。   新創產品總是難免有許多意料之外的問題,特斯拉的例子或許可以成為很好的範例,不論是硬體產品還是服務,爛的客服能把好產品弄臭,好的客服讓產品出包也無妨,這天差地別,業者可得戒慎恐懼。

(首圖來源: CC BY 2.0)    (本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!