【Java應用服務體系】「序章入門」全方位盤點和總結調優技術專題指南

專題⽬標

本系列專題的目標是希望可以幫助讀者們系統和全訪問掌握應⽤系統調優的思路與方案以及相關的調優工具的使用,雖然未必會覆蓋目前的所有的問題場景,但是還是提供了較爲豐富的案例和調優理論,會幫助大家打開思維去⽀撐系統服務體系優化能力。

適合人員

Java相關的開發人員、系統架構師、數據庫DB人員以及運維人員等

什麼是調優

調優手段就是讓計算機的硬件或軟件在正常地⼯作基礎上,非常出色的發揮其應有的性能,並且將所承擔的負擔降低到最低的技術手段。在Java應用服務體系中有大致可以分爲5個維度的調優方向。

調優技術的五個維度

  • 應⽤⾃身的調優
  • 運⾏環境的調優(JVM的調優)
  • 存儲上的調優(數據庫的調優)
  • 操作系統的調優
  • 架構上的調優

如下圖所示。

一般從上到下系統優化的層面成本越來越高,而從下到上系統優化層面的成本越來月底,而且難度也適當下降,建議自下而上的去進行調優規劃。

調優技術的四條準則

藉助監控預防問題、發現問題,監控 + 告警

採用監控和預防的手段去實現提前發現問題:zabbix、promethus等等

藉助⼯具定位問題

問題排查工具使用機制

定期覆盤,防⽌同類問題再現

定期進行排查和覆盤相關的代碼問題,加深我們對問題的印象以及防止問題再次發生

定好規範,⼀定程度上規避問題

制定標準規範,約束問題的發生。

調優的原則

有問題,解決問題。not broken, don't fix.

應⽤調優

應⽤調優-⼯具篇

  • ⼯具旨在幫助我們快速找到應⽤的性能瓶頸。

  • ⽇志分析⼯具⽐較與分析

  • ELK、GrayLog、SLSLog...

  • ELK搭建與使⽤

  • 現場演示

  • 調⽤鏈跟蹤⼯具與對⽐

  • Skywalking、Sleuth + Zipkin、Jaeger...

  • Skywalking快速發現性能瓶頸

應⽤調優常⽤技巧-池化技術-對象池

通過復⽤對象,減少對象創建、垃圾回收的開銷

適⽤場景

維護⼀些很⼤、創建很慢的對象,提升性能
缺點:有學習成本、增加了代碼的複雜度

對象池框架

Apache Commons-Pool2
Commons-Pool2詳解

兩⼤類對象池:ObjectPool & KeyedObjectPool

ObjectPool

實現類如下,其中,最重要、功能最強、使⽤最⼴泛的GenericObjectPool,這個對象池⾮常的強⼤,它⽐較的通⽤,⽽且封裝得也⾮常完備。

  • BaseObjectPool:抽象類,⽤來擴展⾃⼰的對象池
  • ErodingObjectPool:“腐蝕”對象池,代理⼀個對象池,並基於factor參數,爲其添加“腐蝕”⾏爲。歸還的對象被腐蝕後,將會丟棄,⽽不是添加到空閒容量中。
  • GenericObjectPool:⼀個可配置的通⽤對象池實現。
  • ProxiedObjectPool:代理⼀個其他的對象池,並基於動態代理(⽀持JDK代理和CGLib代理),返回⼀個代理後的對象。該對象池主要⽤來增強對池化對象的控制,⽐如防⽌在歸還該對象後,還繼續使⽤該對象等。
  • SoftReferenceObjectPool:基於軟引⽤的對象池
  • SynchronizedObjectPool:代理⼀個其他對象池,併爲其提供線程安全的能⼒。
核⼼API如下
  • borrowObject() 從對象池中借對象
  • returnObject() 將對象歸還到對象池
  • invalidateObject() 失效⼀個對象
  • addObject() 增加⼀個空閒對象,該⽅法適⽤於使⽤空閒對象預加載對象池
  • clear() 清空空閒的所有對象,並釋放相關資源
  • close() 關閉對象池,並釋放相關資源
  • getNumIdle() 獲得空閒的對象數量
  • getNumActive() 獲得被借出對象數量
KeyedObjectPool

這種對象池和ObjectPool的區別在於,它是通過key找對象的,從設計上來看和ObjectPool沒什麼區別。實現類如下,使⽤最⼴的是GenericKeyedObjectPool。

  • ErodingKeyedObjectPool 類似ErodingObjectPool
  • GenericKeyedObjectPool 類似GenericObjectPool
  • ProxiedKeyedObjectPool 類似ProxiedObjectPool
  • SynchronizedKeyedObjectPool 類似SynchronizedObjectPool
使⽤
new GenericObjectPool(PooledObjectFactory<T> factory)
new GenericObjectPool(PooledObjectFactory<T> factory, GenericObjectPoolConfig<T> config)
new GenericObjectPool(PooledObjectFactory<T> factory, GenericObjectPoolConfig<T> config, AbandonedConfig abando
nedConfig)

最重要的參數是PooledObjectFactory,⼀般來說,⼯⼚是需要我們⾃⼰根據業務需求去實現的。它是⽤來創建對象的,這其實就是設計模式⾥⾯的⼯⼚模式。

⽬前PooledObjectFactory有兩個實現類。

  • BasePooledObjectFactory:抽象類,⽤於擴展⾃⼰的PooledObjectFactory
  • PoolUtils.SynchronizedPooledObjectFactory:內部類,代理⼀個其他的PooledObjectFactory,實現線程同步,⽤ PoolUtils.synchronizedPooledFactory() 創建

Factory核⼼⽅法:

  • makeObject 創建⼀個對象實例,並將其包裝成⼀個PooledObject
  • destroyObject 銷燬對象
  • validateObject 校驗對象,確保對象池返回的對象是OK的
  • activateObject 重新初始化對象
  • passivateObject 取消初始化對象。GenericObjectPool的addIdleObject、returnObject、evict調⽤該⽅法。

Commons-Pool2總體分析

  • ObjectPool:對象池,最核⼼:GenericObjectPool、 GenericKeyedObjectPool。
  • Factory:創建&管理PooledObject,⼀般要⾃⼰擴展
  • PooledObject:包裝原有的對象,從⽽讓對象池管理,⼀般⽤DefaultPooledObject即可
Factory示例
class MyPooledObjectFactory implements PooledObjectFactory<Model> { 
   public static final Logger LOGGER = LoggerFactory.getLogger(MyPooledObjectFactory.class); 
   @Override
   public PooledObject<Model> makeObject() throws Exception {
      DefaultPooledObject<Model> object = new DefaultPooledObject<>(new Model(1, "S")); 
      LOGGER.info("makeObject..state = {}", object.getState());
      return object; 
   }
  @Override
  public void destroyObject(PooledObject p) throws Exception{
      LOGGER.info("destroyObject..state = {}", object.getState());
   }
  @Override
  public boolean validateObject(PooledObject p) {
      LOGGER.info("validateObject..state = {}", object.getState());
      return true;
   }
  @Override
  public void activateObject(PooledObject p) throws Exception{
    LOGGER.info("activateObject..state = {}", p.getState());
   }
@Override
  public void passivateObject(PooledObject p) {
      LOGGER.info("passivateObject..state = {}", object.getState());
      return true;
   }

所有操作面向的都是PooledObject這個參數,makeObject返回的是PooledObject,其他API爲什麼操作的也是 PooledObject,⽽不是直接操作我們創建的對象呢?

這其實也是commons-pool設計巧妙之處。Pooledobject可以對原始對象進⾏包裝,從⽽被對象池管理。⽬前 pooledobject有兩個實現類:

  • DefaultPooledObject:包裝原始對象,實現監控(例如創建時間、使⽤時間等)、狀態跟蹤等
  • PooledSoftReference:封裝了DefaultPooledObject,⽤來和SoftReferenceObjectPool配合使⽤。
DefaultPooledObject定義了對象的若⼲種狀態
  • IDLE 對象在隊列中,並空閒。
  • ALLOCATED 使⽤中(即出借中)
  • EVICTION 對象當在隊列中,正在進⾏驅逐測試
  • EVICTION_RETURN_TO_HEAD 對象驅逐測試通過後,放回到隊列頭部
  • VALIDATION 對象當前在隊列中,空閒校驗中
  • VALIDATION_PREALLOCATED 對象當前不在隊列中,出借前校驗中 VALIDATION_RETURN_TO_HEAD 對象當前不在隊列中,校驗通過後放回頭部 INVALID 對象失效,驅逐測試失敗、校驗失敗、對象銷燬,都會將對象置爲 INVALID。
  • ABANDONED 放逐中,如果對象上次使⽤時間超過removeAbandonedTimeout的配置,則將其標記爲ABANDONED。標記爲ABANDONED的對象即將變成 INVALID。
  • RETURNING 對象歸還池中。

JVM調優

本系列專題將針對於Oracle Java HotSpot虛擬機爲爲開發者們提供不同的Java Heap內存空間的較爲深入的分析介紹。對於任何接觸的開發者都是非常重要的理論依據。頻繁遇到的內存問題,提供生產環境的優化調整。那麼適當的實戰層級的Java虛擬機的內存空間分析能力是至關重要的。

前提概述

  • Java虛擬機是你的Java程序運行的基礎,它爲你提供動態的分配內存服務、垃圾收集、線程調度和切換、IO處理和本機操作等
  • Java堆空間是運行時Java程序的內存“容器”,它提供給您的Java應用程序所需的適當內存空間(Java堆、本機堆),並由JVM本身去管理。

JVM HotSpot內存被劃分2類和5空間:

  • Heap堆內存空間:屬於線程共享區域,也是我們JVM的內存管理範疇的最大的一部分運行時內存區域。
  • 方法區(永久代/元空間):屬於線程共享區域,往往我們會忽略了這個區域的內存回收能力。
  • 本地堆 (C-Heap):本地方法的調用棧。
  • 虛擬機棧:Java方法的調用棧。

Heap堆內存空間

JVM的堆空間的變化在<18的版本之內,主要有一個分水嶺,主要集中在8之前和8之後。

JDK8之前的對空間

JDK8之前的Heap空間如下圖所示:

JDK8之後的Heap空間如下圖所示:

主要時針對於方法區的實現機制:永久代 -> 元空間結構模型,接下來我們看看元數據空間在方法區中的分佈結構模型。

後續版本中的-元空間和方法去的內存才能出分配關係

可以看到JDK8之後,方法去的實現有元空間和一部分堆內存組成。之前主要只有單純的永久代去實現的。

常量池

常量池主要有靜態常量池和運行時常量池組成。

  • 類信息
    • 類的版本
    • 字段描述信息
    • 方法描述信息
    • 接口和父類等描述信息
    • class文件常量池(靜態常量池)
靜態常量池,也叫class⽂件常量池,主要存放:
  • 字⾯量:例如⽂本字符串、 final修飾的常量。
  • 符號引⽤:例如類和接⼝的全限定名、字段的名稱和描述符、⽅法的名稱和描述符。
運⾏時常量池

當類加載到內存中後,JVM就會將靜態常量池中的內容存放到運⾏時的常量池中;運⾏時常量池⾥⾯存儲的主要是編譯期間⽣成的字⾯量、符號引⽤等等。如下圖對應的字符串常量在字符串常量池中的存儲模式。

字符串常量池

字符串常量池,也可以理解成運⾏時常量池分出來的⼀部分,類加載到內存的時候,字符串,會存到字符串常量池⾥⾯。

對象和類在內存分佈

針對於代碼的執行和存儲在JVM的分佈,主要集中在棧空間和堆空間、方法區。它們各個的職能不同,對應的能力也是不同的。我們針對於一段代碼塊進行分析和介紹

虛擬機棧的基本結構模型

代碼在堆棧中的存儲結構信息

內存泄漏怎麼排查[java內存溢出排查]

top 等查看系統內存概況

top:顯示所有進程運行情況,按M鍵按照內存大小排序。

使用格式

top [-] [d] [p] [q] [c] [C] [S] [s] [n]

參數說明

  • d:指定每兩次屏幕信息刷新之間的時間間隔,當然用戶可以使用s交互命令來改變之
  • p:通過指定監控進程ID來僅僅監控某個進程的狀態。
  • q:該選項將使top沒有任何延遲的進行刷新。如果調用程序有超級用戶權限,那麼top將以儘可能高的優先級運行。
  • S:指定累計模式。
  • s:使top命令在安全模式中運行。這將去除交互命令所帶來的潛在危險。
  • i:使top不顯示任何閒置或者僵死進程。
  • c:顯示整個命令行而不只是顯示命令名。

命令說明

  • jmx 快速發現jvm中的內存異常項

【實戰階段】JVM排查問題優化參數

jps [-q] [-mlvV] [<hostid>]

參數如下:

  • -q 只顯示進程號
  • -m 顯示傳遞給main⽅法的參數
  • -l 顯示應⽤main class的完整包名應⽤的jar⽂件完整路徑名
  • -v 顯示傳遞給JVM的參數
  • -V 禁⽌輸出類名、JAR⽂件名和傳遞給main⽅法的參數,僅顯示本地JVM標識符的列表

hostid的參數格式

  • hostid:想要查看的主機的標識符,格式爲: [protocol:][[//]hostname][:port][/servername] ,其中:
  • protocol:通信協議,默認rmi
  • hostname:⽬標主機的主機名或IP地址
  • port:通信端⼝,對於默認 rmi 協議,該參數⽤來指定 rmiregistry 遠程主機上的端⼝號。如省略該參數,並且該
  • protocol指示rmi,則使⽤默認使⽤1099端⼝。
  • servicename:服務名稱,取值取決於實現⽅式,對於rmi協議,此參數代表遠程主機上RMI遠程對象的名稱

今天就寫到這裏,未完待續,等待下一部分的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章