1. 程式人生 > 實用技巧 >Android OpenGL ES 開發:繪製圖形

Android OpenGL ES 開發:繪製圖形

JVM基礎

java發展的重大事件

  • 1995年5月23日,java語言誕生
  • 1998年2月,JDK1.1被下載2,000,000萬次
  • 1996年,SUN公司釋出java的三個版本,標準版(J2SE)、企業版(J2EE)、微型版(J2ME)即安卓

計算機體系結構:馮諾依曼計算機體系模型

高階語言

C、C++、Java、Python等。都要轉換成機器能夠懂的機器語言。

程式語言分為編譯型和解釋型語言。

編譯型語言:執行速度快、效率高,依靠編譯器,跨平臺性差。

解釋型語言:使用專門的直譯器對源程式逐行解釋成特定平臺的機器碼並立即執行。

java屬於編譯型+解釋型的高階語言。

class檔案通過JVM翻譯才能在對應的平臺上執行,而這個翻譯大多數時候是解釋的過程,但是也會有編譯,稱之為執行時編譯,即JIT(Just In Time)。

JVM即java vittual machine(java虛擬機器)

JVM載入順序

  • loading(裝載)

    (1)先找個classfile--位元組流--類載入classloader

    (2)位元組流--類靜態資料結構--方法區

    (3)位元組流--class物件--堆

  • linking(連結)

    (1)驗證 檔案格式、元資料、位元組碼、符號引用關係是否相等

    ​ 是否以16進位制cafebaby開頭,版本號是否正確。

    ​ 是否有父類。是否繼承了final類,final類是不能被繼承的。

    ​ -XVerify:none 取消驗證

    (2)準備 為類的靜態變數進行分配記憶體,並且初始化當前類變數的預設值

    (3)解析(Resolve) 類中的符號引用轉變為直接應用。如果有了直接引用,那引用的目標必定存在記憶體中。

  • initiaing(初始化)

    執行類構造器方法的過程。

    初始化什麼時候觸發執行?

    1. 主動引用

      • 建立類例項
      • 訪問類或介面的靜態變數,或者對靜態變數賦值
      • 呼叫類的靜態方法
      • 反射(Class.forName("com.thtf.Test"))
      • 初始化類的子類,則其父類也會被初始化
      • java虛擬機器啟動時被標明為啟動類的類
    2. 被動引用

      • 引用父類的靜態欄位,只會引起父類的初始化,不會引起子類的初始化。

      • 定義類陣列,不會引起類的初始化。

      • 引用類的static final常量,不會引起類的初始化(如果只有static修飾,還是會引起該類初始化)

  • 解除安裝

    在類使用完之後,如果滿足一下情況,類會被解除安裝

    1. 該類所有的例項都已經別回收,也就是java堆中不存在該類的任何例項
    2. 載入該類的ClassLoader已經被回收
    3. 該類對應的java.lang.Class物件沒有任何地方被引用,無法在任務地方通過反射訪問該類的方法。

類的載入機制

  • 全盤負責

    當一個類載入器負責載入某個class時,該class所依賴的和引用的其他class也將由該類載入器負責載入,除非顯式使用另外一個類載入器來載入。

  • 父類委託

    雙親委派是指子類載入器如果沒有載入過該目標類,就先委託父類載入器載入該目標類,只有在父類載入器找不到位元組碼檔案的情況下才從自己的類路徑中查詢並裝載目標類。

    雙親委派機制只是java推薦的機制,並不是強制的機制。可以繼承java.lang.ClassLoader類,實現自己的類載入器。如果想要保持雙親委派模型,就應該重寫findClass(name)方法,如果要破壞雙親委派模型,可以重寫loadClass(name)方法。

  • 快取機制

    快取機制會保證所有載入過的Class都將在記憶體中快取,當程式中需要使用某個class時,類載入器先從記憶體的快取中尋找該class,只有快取區不存在,系統才會讀取該類對應的二進位制資料,並將其轉換成class物件,存入快取區。

Java記憶體區域

執行時資料區

  • 方法區

    跟java堆一樣,是各個執行緒共享的記憶體區域,用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常。

    執行時常量池是方法區的一部分,並不是常量一定只有編譯期才能產生,執行期間也可能將新的常量放入池中,比如String類的intern()方法。

  • 虛擬機器棧

    執行緒私有的,生命週期同線程一樣。java方法執行的記憶體模型。每個方法執行時都會建立一個棧幀用於儲存區域性變數、運算元棧、動態連結、方法出口等資訊。如果執行緒請求的棧深度大於虛擬機器所允許的深度,丟擲StackOverflowError異常;如果虛擬機器可以動態擴充套件,在擴充套件時無法申請到足夠的記憶體,會丟擲OutOfMemoryError。

    動態連結:每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連結。

  • 本地方法棧

    為虛擬機器執行native方法服務。同樣會丟擲上面兩個異常。

  • 對於大多數應用來說,Java堆是java虛擬機器所管理的記憶體中最大的一塊。是所有執行緒共享的一塊區域,在虛擬機器建立時建立。存放的是物件例項。是垃圾收集器管理的主要區域。當堆無法擴充套件時,會丟擲OutOfMemoryError異常。

  • 程式計數器

    執行緒私有的,可以看做是當前執行緒所執行的位元組碼的行號指示器,此記憶體區域是唯一一個在java虛擬機器規範中沒有規定任何oom情況的區域。

    如果執行緒正在執行java方法,則計數器記錄的是正在執行的虛擬機器位元組碼指令的地址,如果正在執行的是native方法,則這個計數器為空。

常量池:靜態常量池,執行時常量池,字串常量池。

靜態常量池:儲存的是字面量以及符號引用

執行時常量池:每個類、每個介面在jvm執行的過程中在記憶體中開闢出來的一塊用來儲存靜態常量池部分資料的一塊特殊區域

字串常量池:包含在動態常量池裡

大端儲存:低序位元組在前,高序位元組在後。便於資料型別的符號判斷,因為最低地址位資料即為符號位,可以直接判斷資料的正負號。

小端儲存:高序位元組在前,低序位元組在後。便於資料之間的型別轉換,如long型別轉換為int型別時,高位地址部分的資料可以直接截掉。

雙親委派機制

類的載入順序:

bootstrap classloader---載入jre中lib/*.jar中的類   
			||
extension classloader---載入project中lib/*.jar中的類  
			||
app classloader---載入自定義的一些類
			||
自定義載入器  通過java.lang.ClassLoader的子類自定義載入class,屬於應用程式根據自身需要自定義的ClassLoader,如tomcat等

類載入的時候會先去bootstrap classloader中載入再去extension classloader中載入再去app classloader。

打破雙親委派

java想了幾種辦法可以用來打破雙親委派。

  • SPI:java從1.6出了SPI是為了優雅的解決這類為題--jdk提供介面,供應商提供服務。如JDBC中的DriverManager。
  • OSGI:實現模組化熱部署的關鍵是他自定義的類載入器機制的實現。每個程式都有一個自己的類載入器。

JVM記憶體模型

jvisualvm可以開啟本地虛擬機器記憶體監視

垃圾回收(GC)

  1. 堆的GC及記憶體分配

    物件的年齡是指垃圾回收的頻率,回收一次age+1,堆中分為old和young,即新生代和老年代,GC的時候預設15還存在的放進老年代。記憶體空間的不連續性/空間碎片時young又分為Eden、Sorvivor區。Sorvivor分為s0和s1,eden用來分配新的物件,其中一個s用來儲存存活的物件,另一個s用來空間保留(浪費)。新生代的s區不夠放了,跟老年代借一些空間,叫擔保機制(物件直接儲存在老年代)

  2. 非堆的GC(方法區):JVM執行時資料區規範

    落地:方法區:常量,靜態變數,類資訊,即時編輯器編譯之後的程式碼。

    JDK7 permspace 永久代 堆。

    JDK8 metaspace 元空間 本地記憶體

    永久代、元空間是對方法區的實現落地

(1)確定一個物件是否是垃圾有兩種方法

  1. 引用計演算法

    計數器加1 : 弊端:當a和b物件互相引用時,就不能當做垃圾去回收即迴圈引用的問題

  2. GC Roots可達性分析

    GC Root條件:區域性變量表變數,static成員

(2)什麼時候會進行垃圾回收?

​ 根據JVM自動完成的。Eden或者S區不足了執行 Minor GC,Old區不夠用了執行 Major GC--通常會觸發一次majorGC--即FUll GC
(3)怎麼回收?

  • ​ 標記-清除

    掃描堆記憶體的空間,比較耗時,會有空間碎片的問題

  • 標記-複製

    效率比較高,但是這時候會有空間的浪費

  • 標記-整理

    整理的過程非常複雜和耗時,不會有空間碎片,比較麻煩

  • 分代回收

    不同的代用不同的垃圾回收演算法 。 young區:標記-複製演算法 old區:標記清除,標記整理

(4)回收演算法的落地-->>垃圾收集器

​ cms垃圾回收,併發執行 。

​ jdk7裡面出現g1回收,jdk8推薦使用,jdk9預設的垃圾回收器

​ g1-garbage-firstl垃圾多的區域先回收,垃圾少的就先不處理

​ jdk11之後:gzc出現zero gc 停頓時間短,並且還有較高的吞吐量

回收器的選擇

(1)停頓時間

(2)吞吐量

  • 序列收集器->Serial和Serial old

    只能有一個垃圾回收執行緒執行,使用者執行緒暫停 適用於記憶體比較小的嵌入式裝置

  • 並行收集器(吞吐量優先)->Paralel Scanvenge、Parallel Old

    多條垃圾回收執行緒並行工作,但此時使用者執行緒仍然處於等待狀態 適用於科學計算、後臺處理等互動場景

  • 併發收集器(停頓時間優先)->CMS、G1

    使用者執行緒和垃圾收集執行緒同時執行(但並不一定是並行的,可能是交替執行的),垃圾收集執行緒在執行的時候不會停頓使用者執行緒的執行。

JVM引數系統

  • 標準引數:不隨著jdk版本的變化而變化
  • -X引數[非標準 修改jvm的執行的方式
  • -XX引數[非標準 a-boolean b-name = value
#java程序有多少引數
-XX:+PrintFlagsFinal
#檢視當前有哪些命令
-XX:+PrintCommandLineFlags -version

#jdk裡面常用的排查命令??
-Xms最小值,-Xmx最大值,一般設定一樣,防止由於不斷計算擴容問題帶來的卡頓。
jps檢視java程序
jinfo pid 檢視java程序資訊
jinfo -flag name PID 檢視某個java程序的name屬性的值
#jinfo -flag MaxHeapSize PID
#jinfo -flag UseG1GC PID
jinfo -flags PID 檢視曾經賦過值的一些引數
jstat -class pid 500 10 檢視類的裝載資訊,每500ms輸出一次,輸出10次
jstat -gc pid 500 10 檢視垃圾收集的資訊
jstack pid 檢視執行緒堆疊資訊(cpu彪高時可以檢視)
jmap -histo pid檢視當前程序中每個類佔用的空間大小和例項數量(記憶體彪高)
jmap -heap pid檢視堆的相關資訊
jmap -dump:format=b,file=heap.hporf PID dump出堆記憶體相關資訊
#在開發中,JVM引數可以加上下面兩句,這樣記憶體溢位時,會自動dump出該檔案
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
thread -b檢視哪個執行緒死鎖
jad 線上反編譯檔案

#常見問題
#問:為什麼程式啟動的時候會發生full gc,檢視老年代的空間還是比較充足?
#答:metaspace空間不夠用導致的,適當增加metaspace空間

#常見的調優工具
jconsole,jvisualvm可以遠端連線arthas線上排查工具

-X引數

非標準引數,也就是在jdk各個版本中可能會變動

-Xint 解釋執行
-Xcomp 第一次使用就編譯成原生代碼
-Xmixed 混合模式,JVM自己決定

-XX引數

使用的最多的引數型別

非標準化引數,相對不穩定,主要用於JVM調優和Debug

#Boolean型別
格式:-XX:[+-]<name> +或-表示啟用或者禁用name屬性
例:-XX:+UseConcMarkSweepGC 表示啟用CMS型別的垃圾回收器
	-XX:+UseG1GC 表示啟用G1型別的垃圾回收器
#非boolean型別
格式:-XX:<name>=<value> 表示name屬性的值是value
例:-XX:MaxGCPauseMillis=500

其他引數

-Xms1000M 等價於 -XX:InitialHeapSize=1000M
-Xmx1000M 等價於 -XX:MaxHeapSize=1000M
-Xss100 等價於 -XX:ThreadStackSize=100
#相當於是-XX型別的引數

檢視引數:java -XX:+PrintFlagsFinal -version > flags.txt

=表示預設值,:= 表示被使用者或JVM修改後的值。1Byte(位元組)=8bit(位)

  • [x] | 引數 | 含義 | 說明 |
    | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
    | -XX:CICompilerCount=3 | 最大並行編譯數 | 如果設定大於1,雖然編譯速度會提高,但是同樣影響系統穩定性,會增加JVM崩潰的可能 |
    | -XX:InitialHeapSize=100M | 初始化堆大小 | 簡寫-Xms100M |
    | -XX:MaxHeapSize=100M | 最大堆大小 | 簡寫-Xms100M |
    | -XX:NewSize=20M | 設定年輕代的大小 | |
    | -XX:MaxNewSize=50M | 年輕代最大大小 | |
    | -XX:OldSize=50M | 設定老年代大小 | |
    | -XX:MetaspaceSize=50M | 設定方法區大小 | |
    | -XX:MaxMetaspaceSize=50M | 方法區最大大小 | |
    | -XX:+UseParallelGC | 使用UseParallelGC | 新生代,吞吐量優先 |
    | -XX:+UseParallelOldGC | 使用UseParallelOldGC | 老年代,吞吐量優先 |
    | -XX:+UseConcMarkSweepGC | 使用CMS | 老年代,停頓時間優先 |
    | -XX:+UseG1GC | 使用G1GC | 新生代,老年代,停頓時間優先 |
    | -XX:NewRatio | 新老生代的比值 | 比如-XX:Ratio=4,則表示新生代:老年代=1:4,也就是新生代佔整個堆記憶體的1/5 |
    | -XX:SurvivorRatio | 兩個S區和Eden區的比值 | 比如-XX:SurvivorRatio=8,也就是(S0+S1):Eden=2:8,也就是一個S佔整個新生代的1/10 |
    | -XX:+HeapDumpOnOutOfMemoryError | 啟動堆記憶體溢位列印 | 當JVM堆記憶體發生溢位時,也就是OOM,自動生成dump檔案 |
    | -XX:HeapDumpPath=heap.hprof | 指定堆記憶體溢位列印目錄 | 表示在當前目錄生成一個heap.hprof檔案 |
    | -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:g1-gc.log | 打印出GC日誌 | 可以使用不同的垃圾收集器,對比檢視GC情況 |
    | -Xss128k | 設定每個執行緒的堆疊大小 | 經驗值是3000-5000最佳 |
    | -XX:MaxTenuringThreshold=6 | 提升年老代的最大臨界值 | 預設值為 15 |
    | -XX:InitiatingHeapOccupancyPercent | 啟動併發GC週期時堆記憶體使用佔比 | G1之類的垃圾收集器用它來觸發併發GC週期,基於整個堆的使用率,而不只是某一代記憶體的使用比. 值為 0 則表示”一直執行GC迴圈”. 預設值為 45. |
    | -XX:G1HeapWastePercent | 允許的浪費堆空間的佔比 | 預設是10%,如果併發標記可回收的空間小於10%,則不會觸發MixedGC。 |
    | -XX:MaxGCPauseMillis=200ms | G1最大停頓時間 | 暫停時間不能太小,太小的話就會導致出現G1跟不上垃圾產生的速度。最終退化成FullGC。所以對這個引數的調優是一個持續的過程,逐步調整到最佳狀態。 |
    | -XX:ConcGCThreads=n | 併發垃圾收集器使用的執行緒數量 | 預設值隨JVM執行的平臺不同而不同 |
    | -XX:G1MixedGCLiveThresholdPercent=65 | 混合垃圾回收週期中要包括的舊區域設定佔用率閾值 | 預設佔用率為 65% |
    | -XX:G1MixedGCCountTarget=8 | 設定標記週期完成後,對存活資料上限為G1MixedGCLIveThresholdPercent的舊區域執行混合垃圾回收的目標次數 | 預設8次混合垃圾回收,混合回收的目標是要控制在此目標次數以內 |
    | -XX:G1OldCSetRegionThresholdPercent=1 | 描述Mixed GC時,Old Region被加入到CSet中預設情況下,G1只把10%的Old Region加入到CSet中 | 預設情況下,G1只把10%的Old Region加入到CSet |

垃圾收集器

Serial

最基本,發展歷史最悠久的收集器,jdk1.3之前是虛擬機器新生代收集的唯一選擇。

它是一種單執行緒收集器,它只會使用一個cpu或者一條收集執行緒完成垃圾收集工作,並且進行垃圾收集的時候需要暫停其他執行緒。

優點:簡單高效,擁有很高的單執行緒收集效率
缺點:收集過程需要暫停所有執行緒
演算法:複製演算法
適用範圍:新生代
應用:client模式下的預設新生代收集器

Serial Old

是Serial收集器的老年代版本,也是一個單執行緒收集器,不同的是採用“標記-整理演算法”,執行過程和Serial收集器一樣。

ParNew

可以把這個收集器理解為Serial收集器的多執行緒版本。

優點:在多Cpu時,比Serial效率高
缺點:收集過程需要暫停所有執行緒,單cpu時彼Serial效率差
演算法:複製演算法
適用範圍:新生代
應用:執行在Server模式下的虛擬機器中首選的新生代收集器

Parallel Scavenge

是一個新生代收集器,也是使用複製演算法的收集器,又是並行的多執行緒收集器,看上去和ParNew一樣,但是這個更關注系統的吞吐量。

-XX:MaxGCPauseMillis控制最大的垃圾收集停頓時間,
-XX:GCTimeRatio直接設定吞吐量的大

Parallel Old

是Parallel Scavenge收集器的老年代版本,使用多執行緒和標記-整理演算法進行垃圾回收,也是更加關注吞吐量。

CMS

一種以獲取最短回收停頓時間為目標的收集器。採用的是標記-清除演算法,真個過程分為4步

  1. 初始標記 CMS initial mark 標記GC Roots直接關聯物件,不用Tracing,速度很快
  2. 併發標記 CMS concurrent mark 進行GC Root Tracing
  3. 重新標記 CMS remark 修改併發標記因使用者程式變動的內容
  4. 併發清除 CMS concurrent sweep 清楚不可達物件回收空間,同時有新垃圾生成,留著下次清理成為浮動垃圾

整個過程中,併發標記和併發清除,收集執行緒可以與使用者執行緒一起工作,所以總體上來說,CMS收集器的記憶體回收過程是與使用者執行緒一起併發執行的。

優點:併發收集、低停頓
缺點:產生大量空間碎片、併發階段會降低吞吐量,還會併發失敗

background模式為正常模式執行上述的CMS GC流程
forefroud模式為Full GC模式
//開啟CMS垃圾收集器
-XX:+UseConcMarkSweepGC
//預設開啟,與-XX:CMSFullGCsBeforeCompaction配合使用
    
-XX:+UseCMSCompactAtFullCollection  
//預設0 幾次Full GC後開始整理
-XX:CMSFullGCsBeforeCompaction=0
    
//輔助CMSInitiatingOccupancyFraction的引數,不然CMSInitiatingOccupancyFraction只會使用一次就恢復自動調整,也就是開啟手動調整。
-XX:+UseCMSInitiatingOccupancyOnly
//取值0-100,按百分比回收
-XX:CMSInitiatingOccupancyFraction  預設-1

CMS缺陷:單執行緒 單核 效率很低

​ 併發失敗:導致可中止的預處理 5s

G1

使用G1收集器時,java堆的記憶體佈局與其他收集器有很大差別,它將整個java堆劃分為多個大小相等的獨立區域(Region),雖然還保留由新生代和老年代的概念,但新生代和老年代不再是物理隔離了,他們都是一部分Region的集合。每個Region大小都是一樣的,可以是1M到32M之間的數值,但是必須保證是2的n次冪。

如果物件太大,一個Region放不下[超過Region大小的50%],那麼就會直接放到H中,設定Region大小:--XX:G1HeapRegionSize=M,所謂Garbage-First,其實就是優先回收垃圾最多的Region區域。

1.分代收集(仍然保留了分代的概念)
2.空間整合(整體上屬於標記-整理演算法,不會導致空間碎片)
3.可預測的停頓(比CMS更先進的地方在於能讓使用者明確指定一個長度為M毫秒的時間片段內,消耗在垃圾收集器上的時間不得超過N毫秒)

可以分為以下幾步

  1. 初始標記 :標記以下GC Roots能夠關聯的物件,並且修改TAMS的值,需要暫停使用者執行緒
  2. 併發標記:從GC Roots進行可達性分析,找出存活的物件,與使用者執行緒併發執行
  3. 最終標記:修正在併發標記階段因為使用者程式的併發執行導致變動的資料,需要暫停使用者執行緒
  4. 篩選回收:堆各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間制定回收計劃

-XX: +UseG1GC 開啟G1垃圾收集器

-XX: G1HeapReginSize 設定每個Region的大小,是2的冪次,1MB-32MB之間

-XX:MaxGCPauseMillis 最大停頓時間

-XX:ParallelGCThread 並行GC工作的執行緒數

-XX:ConcGCThreads 併發標記的執行緒數

-XX:InitiatingHeapOcccupancyPercent 預設45%,代表GC堆佔用達到多少的時候開始垃圾收集

ZGC

JDK11新引入的ZGC收集器,不管是物理上還是邏輯上,ZGC中已經不存在新老年代的概念了,會分為一個個page,當進行GC操作時會對page進行壓縮,因此沒有碎片問題。

只能在64位的linux上使用,目前用得還比較少

  • 可以達到10ms以內的停頓時間要求
  • 支援TB級別的記憶體
  • 堆記憶體變大後停頓時間還是在10ms以內

垃圾收集器分類

  • 序列收集器:Serial和Serial Old 只能有一個垃圾回收執行緒執行,使用者執行緒停頓
  • 並行收集器:Parallel Scanvenge和Parallel Old 多條垃圾收集執行緒並行工作,但是此時使用者執行緒仍然處於等待狀態
  • 併發收集器:CMS和G1 使用者執行緒和垃圾收集執行緒同時執行,垃圾收集執行緒在執行的時候不會停頓使用者執行緒的執行

常見問題

  • 吞吐量和停頓時間

    • 停頓時間->垃圾收集器 進行 垃圾回收端應用執行響應的時候
    • 吞吐量->執行使用者程式碼時間(執行使用者程式碼時間+垃圾收集時間)

    停頓時間越短就越適合需要和使用者互動的程式,良好的響應速度能提升使用者體驗;高吞吐量則可以高效地利用CPU時間,儘量完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。

    • 如何選擇合適的垃圾收集器

      • 優先調整堆的大小讓伺服器自己來選擇
      • 如果記憶體小於100M,使用序列收集器
      • 如果是單核,並且沒有停頓時間要求,使用序列或JVM自己選
      • 如果允許停頓時間超過1秒,選擇並行或JVM自己選
      • 如果響應時間最重要,並且不能超過1秒,使用併發收集器
    • 對於G1收集

      jdk7開始使用,jdk8非常成熟,jdk9預設的垃圾收集器,適用於新老生代。

      是否使用G1收集器:

      1. 505以上的堆被存活物件佔用
      2. 物件分配和晉升的速度變化非常大
      3. 垃圾回收時間比較長
    • G1中的RSet

      全稱Remembered Set,記錄維護Region中物件的引用關係

      試想,在G1垃圾收集器進行新生代的垃圾收集時,也就是Minor GC,假如該物件被老年代的Region中所引用,這時候新生代的該物件就不能被回收,怎麼記錄呢?不妨這樣,用一個類似於hash的結構,key記錄region的地址,value表示引用該物件的集合,這樣就能知道該物件被哪些老年代的物件所引用,從而不能回收。
      
    • 如何開啟需要的垃圾收集器

      • 序列
        -XX:+UseSerialGC
        -XX:+UseSerialOldGC
        並行(吞吐量優先)
        -XX:+UseParallelGC
        -XX:+UseParallelOldGC
        併發收集器(響應時間優先)
        -XX:+UseConcMarkSweepGC
        -XX:+UseG1GC
        

工具

jconsole

jconsole工具是JDK自帶的視覺化工具,檢視java應用程式的執行情況、監控堆資訊、永久區使用情況、類載入情況等。

命令列中輸入:jconsole

jvisualvm

命令列中輸入:jvisulavm

visualvm 外掛下載地址https://visualvm.github.io/pluginscenters.html

監控本地java程序

可以監控本地的java程序的CPU,類,執行緒等

監控遠端java程序

(1)在visualvm中選中“遠端”,右擊“新增”

(2)主機名上寫伺服器的ip地址,比如39.100.39.63,然後點選“確定”

(3)右擊該主機"39.100.39.63",新增“JMX”,也就是通過JMX技術具體監控遠端伺服器哪個Java程序

(4)要想讓伺服器上的tomcat被連線,需要改一下Catalina.sh這個檔案

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Djava.rmi.server.hostname=39.100.39.63 -Dcom.sun.management.jmxremote.port=8998-Dcom.sun.management.jmxremote.ssl=false-Dcom.sun.management.jmxremote.authenticate=true-Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password"

(5) 在../conf檔案中新增兩個檔案jmxremote.access和jmxremote.password

​ jmxremote.access

guest readonlymanager readwr

jmxremote.password

guest guestmanager manager

(6)將連線伺服器地址改為公網ip地址

hostname -i檢視輸出情況
	172.26.225.240 172.17.0.1
vim /etc/hosts
	172.26.255.240 39.100.39.63

(7)設定上述埠對應的阿里雲安全策略和防火牆策略

(8)啟動tomcat,來到bin目錄

./startup.sh

(9)檢視tomcat啟動日誌以及埠監聽

tail -f ../logs/catalina.out
lsof -i tcp:80

(10)檢視8998監聽情況,可以發現多開了幾個埠

lsof -i:8998    得到PID
netstat -antup | grep PID

(11)在剛才的JMX中輸入8998埠,並且輸入使用者名稱和密碼則登入成功

埠:8998
使用者名稱:manager
密碼:manage

arthas

github地址:https://github.com/alibaba/arthas

Arthas 是Alibaba開源的Java診斷工具,採用命令列互動模式,是排查jvm相關問題的利器。

heaphero

https://heaphero.io/

perfma

https://console.perfma.com/

GCViewer

java -jar gcviewer-1.36-SNAPSHOT.jar

gceasy

http://gceasy.io

gcplot

https://it.gcplot.com/

Gc日誌分析

要想分析日誌的資訊,得先拿到GC日誌檔案才行,所以得先配置一下,根據前面引數的學習,下面的配置很容易看懂。比如開啟windows中的catalina.bat,在第一行加上。

XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
-Xloggc:$CATALINA_HOME/logs/gc.log

這樣使用startup.bat啟動tomcat的時候就能夠在當前目錄下拿到gc.log檔案,可以看到預設使用的是ParallelGC。

效能優化

記憶體分配

正常情況下不需要設定,那如果是促銷或者秒殺的場景呢?

每臺機器配置2c4G,以每秒3000筆訂單為例,整個過程持續60秒

記憶體溢位

大併發情況下

記憶體洩露導致記憶體溢位

大併發[秒殺]

瀏覽器快取、本地快取、驗證碼

CDN靜態資源伺服器

叢集+負載均衡

動靜態資源分離、限流[基於令牌桶、漏桶演算法

]應用級別快取、介面防刷限流、佇列、Tomcat效能優化

非同步訊息中介軟體

Redis熱點資料物件快取

分散式鎖、資料庫鎖

5分鐘之內沒有支付,取消訂單、恢復庫存等

CPU佔用率高

  1. top

  2. top -Hp PID

    檢視程序中佔用CPU高的執行緒id,即tid

  3. jstack PID | grep tid