日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

Java虚拟机|JVM知识点汇总及简述->性能监控与调优

發(fā)布時間:2023/12/20 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java虚拟机|JVM知识点汇总及简述->性能监控与调优 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

性能監(jiān)控與調(diào)優(yōu)

  • 前言

這里學(xué)完整章后選擇一到兩個工具使用熟練,個人推薦Visual VM和Arthas搭配熟練使用

一、概述

1.性能評價/測試指標(biāo)

1.1 停頓時間(響應(yīng)時間)

  • 提交請求和返回該請求的響應(yīng)之間使用的時間,一般比較關(guān)注平均響應(yīng)時間常用操作的響應(yīng)時間列表:

  • 在垃圾回收環(huán)節(jié)中,暫停時間:執(zhí)行垃圾收集時,程序的工作線程被暫停的時間。

1.2 吞吐量

  • 對單位時間內(nèi)完成的工作量(請求)的量度

  • 在GC中:運(yùn)行用戶代碼的時間占總運(yùn)行時間的比例(總運(yùn)行時間:程序的運(yùn)行時間+內(nèi)存回收的時間)吞吐量為1-1/(1+n)。

    -XX:GCTimeRatio=n

1.3 并發(fā)數(shù)

同—時刻,對服務(wù)器有實際交互的請求數(shù)

1.4 內(nèi)存占用

Java堆區(qū)所占的內(nèi)存大小

1.5 相互間的關(guān)系

這里主要討論停頓時間、吞吐量、并發(fā)數(shù)之間的關(guān)系,當(dāng)吞吐量越高,并發(fā)數(shù)也就也高,而停頓時間就越短

二、JVM監(jiān)控及診斷工具-命令行

1.概述

使用數(shù)據(jù)說明問題,使用知識分析問題,使用工具處理問題。無監(jiān)控、不調(diào)優(yōu)!

2.jps:查看正在運(yùn)行的Java進(jìn)程

  • 說明

Java process status,顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機(jī)進(jìn)程(查看虛擬機(jī)進(jìn)程信息),可用于查詢正在運(yùn)行的虛擬機(jī)進(jìn)程。
說明:對于本地虛擬機(jī)進(jìn)程來說,進(jìn)程的本地虛擬機(jī)ID與操作系統(tǒng)的進(jìn)程ID是一致的,是唯一的。

  • options參數(shù):

參數(shù)說明:

-q:僅僅顯示LVMID (local virtual machine id),即本地虛擬機(jī)唯一id。不顯示主類的名稱等
-l:輸出應(yīng)用程序主類的全類名或如果進(jìn)程執(zhí)行的是jar包,則輸出jar完整路徑
-m:輸出虛擬機(jī)進(jìn)程啟動時傳遞給主類main()的參數(shù)
-v:列出虛擬機(jī)進(jìn)程啟動時的JVM參數(shù)。比如:-Xms20m -Xmx50m是啟動程序指定的jvm參數(shù)。

  • 注意:

如果某Java進(jìn)程關(guān)閉了默認(rèn)開啟的UsePerfData參數(shù)(即使用參數(shù)-XX:-UsePerfData),那么jps命令(以及下面介紹的jstat)將無法探知該Java進(jìn)程。

3.jstat:查看JVM的統(tǒng)計信息

  • 說明
  • JVM Statistics Monitoring Tool:用于監(jiān)視虛擬機(jī)各種運(yùn)行狀態(tài)信息的命令行工具。它可以顯示本地或者遠(yuǎn)程虛擬機(jī)進(jìn)程中的類裝載、內(nèi)存、垃圾收集、IT編譯等運(yùn)行數(shù)據(jù)。

  • 在沒有GUI圖形界面,只提供了純文本控制臺環(huán)境的服務(wù)器上,它將是運(yùn)行期定位虛擬機(jī)性能問題的首選工具。常用于檢測垃圾回收問題以及內(nèi)存泄漏問題。

3.1 option參數(shù)

  • 類裝載相關(guān)的

-class:顯示ClassLoader的相關(guān)信息:類的裝載、卸載數(shù)量、總空間、類裝載所消耗的時間等

  • 垃圾相關(guān)的
  • -gc:顯示與GC相關(guān)的堆信息。包括Eden區(qū)、兩個Survivor區(qū)、老年代、永久代等的容量、己用空間、GC時間合計等信息。
  • -gccapacity:顯示內(nèi)容與-gc基本相同,但輸出主要關(guān)注Java堆各個區(qū)域使用到的最大、最小空間。
  • -gcutil:顯示內(nèi)容與-gc基本相同,但輸出主要關(guān)注已使用空間占總空間的百分比。
  • -gccause:與-gcutil功能一樣,但是會額外輸出導(dǎo)致最后一次或當(dāng)前正在發(fā)生的GC產(chǎn)生的原因。
  • -gcnew:顯示新生代GC狀況
  • -gcnewcapacity:顯示內(nèi)容與-gcnew基本相同,輸出主要關(guān)注使用到的最大、最小空間
  • -geold:顯示老年代GC狀況
  • -gcoldcapacity:顯示內(nèi)容與-gcold基本相同,輸出主要關(guān)注使用到的最大、最小空間
  • -gcpermcapacity:顯示永久代使用到的最大、最小空間。
    • JIT相關(guān)的
  • -compiler:顯示JIT編譯器編譯過的方法、耗時等信息-
  • printcompilation:輸出已經(jīng)被JIT編譯的方法
  • 3.2 其他參數(shù)

    • interval參數(shù)

    用于指定輸出統(tǒng)計數(shù)據(jù)的周期,單位為毫秒。即:查詢間隔

    • count參數(shù)

      用于指定查詢的總次數(shù),跟在interval參數(shù)后面配合使用

    • -t參數(shù)

    可以在輸出信息加上一個TimeStamp列,來顯示程序自打開運(yùn)行的時間,單位:秒

    可以根據(jù)-t參數(shù)來判斷是否要出現(xiàn)OOM:比較Java進(jìn)程的啟動時間以及總GC時間(GCT列),或者兩次測量的間隔時間以及總GC時間的增量,來得出 GC時間占運(yùn)行時間的比例。如果該比例超過20%,則說明目前堆的壓力較大;如果該比例超過90%,則悅明堆里幾乎沒有可用空間,隨時都可能拋出 OOM異常。

    • -h參數(shù)

    可以在周期性數(shù)據(jù)輸出時,輸出多少行數(shù)據(jù)后輸出一個表頭信息

    3.3 如何通過jstat判斷內(nèi)存泄露

    分別為兩步:

  • 在長時間運(yùn)行的 Java程序中,我們可以運(yùn)行jstat命令連續(xù)獲取多行性能數(shù)據(jù),并取這幾行數(shù)據(jù)中OU列(即已占用的老年代內(nèi)存)的最小值。
  • 每隔一段較長的時間重復(fù)一次上述操作,來獲得多組OU最小值。如果這些值呈上漲趨勢,則說明該Java程序的老年代內(nèi)存己使用量在不斷上漲,這意味著無法回收的對象在不斷增加,因此很有可能存在內(nèi)存泄漏。
  • 4.jinfo:實時查看和修改JVM配置參數(shù)

    • 說明

    Configuration Info for Java,在很多情況下,Java應(yīng)用程序不會指定所有的Java虛擬機(jī)參數(shù)。而此時,開發(fā)人員可能不知道某一個具體的Java虛擬機(jī)參數(shù)的默認(rèn)值。在這種情況下,可能需要通過查找文檔獲取某個參數(shù)的默認(rèn)值。這個查找過程可能是非常艱難的。但有了jinfo工具,開發(fā)人員可以很方便地找到Java虛擬機(jī)參數(shù)的當(dāng)前值。

    4.1 option參數(shù)

    基本使用語法:jinfo [ options ] pid

    注意:標(biāo)記為manageable的參數(shù)非常有限

    4.2 拓展參數(shù)

    • java -XX:+PrintFlagsInitial

      查看所有JVM參數(shù)啟動的初始值

    • java -xx:+PrintFlagsFinal

      查看所有JVM參數(shù)的最終值

    • java -XX:+ PrintCommandLineFlags

      查看那些已經(jīng)被用戶或者JVM設(shè)置過的詳細(xì)的XX參數(shù)的名稱和值

    5.jmap:導(dǎo)出內(nèi)存映像文件&內(nèi)存使用情況

    • 說明:

    JVM Memory Map:作用一方面是獲取dump文件(堆轉(zhuǎn)儲快照文件,二進(jìn)制文件),它還可以獲取目標(biāo)Java進(jìn)程的內(nèi)存相關(guān)信息,包括Java堆各區(qū)域的使用情況、堆中對象的統(tǒng)計信息、類加載信息等。

    5.1 option參數(shù)

    基本語法:

    • jmap [option] <pid>
    • jmap [option] <executable> <core>
    • jmap [option] [server_id@] <remote server IP or hostname>

    5.2 兩種用法詳解

  • 導(dǎo)出內(nèi)存映像文件(dump)
    • 手動方式:

      jmap -dump:live, format=b,file=d:\4.hprof pid

      說明:live參數(shù)表示只打印內(nèi)存的存活對象(往往出現(xiàn)OOM的時候就是太多存貨對象回收不了導(dǎo)致的,沒有該參數(shù)就表示打印全部對象),format參數(shù)標(biāo)識打印的文件格式可以被監(jiān)控工具識別,file就是指定文件生成位置,文件名后綴為 .hprof ,pid為進(jìn)程號

    • 自動方式:

      -XX:+HeapDumpOnoutOfMemoryError:在程序發(fā)生OOM時,導(dǎo)出應(yīng)用程序的當(dāng)前堆快照。

      -XX:HeapDumpPath:可以指定堆快照的保存位置。

      說明:當(dāng)程序發(fā)生OOM退出系統(tǒng)時,一些瞬時信息都隨著程序的終止而消失,而重現(xiàn)OOM問題往往比較困難或者耗時。此時若能在OOM時,自動導(dǎo)出dump文件就顯得非常迫切。

  • 顯示內(nèi)存使用情況
  • 說明:這兩個參數(shù)都是對于內(nèi)存某一個時刻進(jìn)行的時間點信息,無法做到持續(xù)監(jiān)控

    • -jmap -heap pid
    • -jmap -histo pid

    5.3 小結(jié)

    由于jmap將訪問堆中的所有對象,為了保證在此過程中不被應(yīng)用線程干擾,jmap需要借助安全點機(jī)制,讓所有線程停留在不改變堆中數(shù)據(jù)的狀態(tài)。與前面講的jstat則不同,垃圾回收器會主動將jstat所需要的摘要數(shù)據(jù)保存至固定位置之中,而jstat只需直接讀取即可。

    • 缺點:
    • 由jmap導(dǎo)出的堆快照必定是安全點位置的。這可能導(dǎo)致基于該堆快照的分析結(jié)果存在偏差。
    • 加入生命周期只在兩個安全點之間有效,jmap記錄的時候是檢測不到的
    • 如果某個線程長時間無法跑到安全點,jmap將一直等下去。

    6.jhat:JDK自帶堆分析工具

    • 概述

    JVM Heap Analysis Tool,jhat內(nèi)置了一個微型的HTTP/HTML服務(wù)器,生成dump文件的分析結(jié)果后,用戶可以在瀏覽器中查看分析結(jié)果(分析虛擬機(jī)轉(zhuǎn)儲快照信息)。使用了jhat命令,就啟動了一個http服務(wù),端口是7000,即http://localhost:7000/,就可以在瀏覽器里分析。

    • 注意

    jhat命令在JDK9、JDK10中已經(jīng)被刪除,官方建議用VisualVM代替。所以這里就不做過多介紹

    7.jstack:打印JVM中的線程快照

    • 概述

    JVM Stack Trance打印當(dāng)前進(jìn)程的所有線程

    • 作用

    生成線程快照的作用:可用于定位線程出現(xiàn)長時間停頓的原因,如線程間死鎖、死循環(huán)、請求外部資源導(dǎo)致的長時間等待等問題。這些都是導(dǎo)致線程長時間停頓的常見原因。當(dāng)線程出現(xiàn)停頓時,就可以用jstack顯示各個線程調(diào)用的堆棧情況。

    • 快照中需要注意的地方
    • 死鎖,Deadlock(重點關(guān)注)
    • 等待資源,waiting on condition(重點關(guān)注)
    • 等待獲取監(jiān)視器,waiting on monitor entry(重點關(guān)注)阻塞,Blocked(重點關(guān)注)
    • 執(zhí)行中,Runnable
    • 暫停,Suspended

    7.1 option參數(shù)

    • -F:當(dāng)正常輸出的請求不被響應(yīng)時,強(qiáng)制輸出線程堆棧
    • -l:除堆棧外,顯示關(guān)于鎖的附加信息
    • -m:如果調(diào)用到本地方法的話,可以顯示C/C++的堆棧
    • -h:幫助操作

    8.jcmd:多功能命令行

    • 概述

    它是一個多功能的工具,可以用來實現(xiàn)前面除了jstat之外所有命令的功能。比如:用它來導(dǎo)出堆、內(nèi)存使用、查看Java進(jìn)程、導(dǎo)出線程信息、執(zhí)行GC、JVM運(yùn)行時間等。官方推薦使用jcmd代替jmap

    8.1 基本語法

    • jcmd -l:列出所有的JVM進(jìn)程
    • jcmd pid help:針對指定的進(jìn)程,列出支持的所有命令
    • jcmd pid具體命令團(tuán):顯示指定進(jìn)程的指令命令的數(shù)據(jù)

    三、JVM監(jiān)控及診斷工具-GUI

    1.工具概述

    • 使用命令行工具的弊端
  • 無法獲取方法級別的分析數(shù)據(jù),如方法間的調(diào)用關(guān)系、各方法的調(diào)用次數(shù)和調(diào)用時間等(這對定位應(yīng)用性能瓶頸至關(guān)重要)。
  • 要求用戶登錄到目標(biāo)Java應(yīng)用所在的宿主機(jī)上,使用起來不是很方便。
  • 分析數(shù)據(jù)通過終端輸出,結(jié)果展示不夠直觀。
    • 工具分類
    • JDK自帶的工具:jConsole、Visual VM、JMC(Java mission control)

    • 第三方工具:MAT(Eclipse)、JProfiler、Arthas、Btrace

    2.jConsole

    2.1 概述

    • 從JDK5開始,在JDK中自帶的java監(jiān)控和管理控制臺。

    • 用于對VM中內(nèi)存、線程和類等的監(jiān)控,是一個基于JMX(java management extensions)的GUI性能監(jiān)控工具

    • 位置:

    在JDK目錄下的bin目錄可找到

    3.Visual VM

    3.1 概述

    • Visual VM是一個功能強(qiáng)大的多合一故障診斷和性能監(jiān)控的可視化工具。
    • 它集成了多個JDK命令行工具,使用Visual M可用于顯示虛擬機(jī)進(jìn)程及進(jìn)程的配置和環(huán)境信息(jps,jinfo),監(jiān)視應(yīng)用程序的CPU、GC、堆、方法區(qū)及線程的信息(jstat、jstack)等,代替JConsole。
    • 在JDK 6 Update 7以后,Visual VM便作為DK的一部分發(fā)布(VisualVM在JDK/bin目錄下),是完全免費(fèi)的
    • Visual VM也可以作為獨(dú)立的軟件安裝
    • 安裝方式
    • 在JDK的bin目錄下,如果沒有則自行下載

    • 可在idea中下載啟動Visual 的插件,記得要配置.exe文件和JDK的home目錄

    • 插件安裝可在官網(wǎng)或客戶端中下載(強(qiáng)烈推薦Visual GC這個插件)

    3.2 主要功能

  • 生成/讀取堆內(nèi)存快照

  • 查看JVM參數(shù)和系統(tǒng)屬性

  • 查看運(yùn)行中的虛擬機(jī)進(jìn)程

  • 生成/讀取線程快照

  • 程序資源的實時監(jiān)控

  • 其他功能

    JMX代理連接、遠(yuǎn)程環(huán)境監(jiān)控、CPU分析和內(nèi)存分析

  • 4.MAT

    4.1 概述

    MAT(Memory Analyzer Tool)工具是一款功能強(qiáng)大的Java堆內(nèi)存分析器。**主要用于dump文件的分析可以用于查找內(nèi)存泄漏以及查看內(nèi)存消耗情況。**MAT是基于Eclipse開發(fā)的,不僅可以單獨(dú)使用,還可以作為插件的形式嵌入在Eclipse中使用。是免費(fèi)軟件

    4.2 dump文件信息

    • 內(nèi)容

    MAT可以分析heap dump文件。在進(jìn)行內(nèi)存分析時,只要獲得了反映當(dāng)前設(shè)備內(nèi)存映像的hprof文件,通過MAT打開就可以直觀地看到當(dāng)前的內(nèi)存信息。一般說來,這些內(nèi)存信息包含:

    • 所有的對象信息,包括對象實例、成員變量、存儲于棧中的基本類型值和存儲于堆中的其他對象的
      引用值。
    • 所有的類信息,包括classloader、類名稱、父類、靜態(tài)變量等. GCRoot到所有的這些對象的引用路徑
    • 線程信息,包括線程的調(diào)用棧及此線程的線程局部變量(TLS)
    • 優(yōu)點

    能夠快速為開發(fā)人員生成內(nèi)存泄漏報表,方便定位問題和分析問題

    • 導(dǎo)出dump文件方式
    • 可以在visual vm里面生成
    • 在第5節(jié)imap參數(shù)中,直接用imap參數(shù)導(dǎo)出
    • 有兩個參數(shù)可以導(dǎo)出dump文件-XX:+HeapDumpOnoutOfMemoryError:在程序發(fā)生OOM時,導(dǎo)出應(yīng)用程序的當(dāng)前堆快照。-XX:HeapDumpPath:可以指定堆快照的保存位置。
    • 當(dāng)然也可以直接用MAT生成dump文件,前提要知道進(jìn)程號

    4.3 分析MAT中的dump文件過程

    • histogram

    展示了各個類的實例數(shù)目以及這些實例的Shallowheap 或Retainedheap的總和

    • thread overview

    查看系統(tǒng)中的Java線程、查看局部變量的信息

    • 獲得對象相互引用的關(guān)系

      with outgoing references:查看這個對象引用了誰

      with incoming references:查看誰引用了這個對象

    ?

    4.4 深堆和淺堆

    • 淺堆(Shallow Heap)

    淺堆是指一個對象所消耗的內(nèi)存。在32位系統(tǒng)中,一個對象引用會占據(jù)4個字節(jié),一個int類型會占據(jù)4個字節(jié),long型變量會占據(jù)8個字節(jié),每個對象頭需要占用8個字節(jié)。根據(jù)堆快照格式不同,對象的大小可能會向8字節(jié)進(jìn)行對齊。以String為例: 2個int值共占8字節(jié),對象引用占用4字節(jié),對象頭8字節(jié),合計20字節(jié),向8字節(jié)對齊,故占24字節(jié)。(jdk7中)。

    注意:與value值的多少是無關(guān)的

    • 保留集(Retained Set)

    對象A的保留集指當(dāng)對象A被垃圾回收后,可以被釋放的所有的對象集合(包括對象A本身),即對象A的保留集可以被認(rèn)為是只能通過對象A被直接或間接訪問到的所有對象的集合。通俗地說,就是指僅被對象A所持有的對象的集合

    • 深堆(Retained Heap)

    就是自己的淺堆大小加上保留集的大小就是深堆的大小

    • 對象實際大小

    對象的實際大小就是指:淺堆大小+自己能夠引用的全部對象大小

    • 案例分析

    代碼:

    public class StudentTrace {static List<WebPage> webpages = new ArrayList<>();public static void createWebPages() {for (int i = 0; i < 100; i++) {WebPage wp = new WebPage();wp.setUrl( "http://www." + Integer.toString(i) + ".com" );wp.setContent( Integer.toString(i));webpages.add(wp);}}public static void main(String[] args) {createWebPages();Student s3 = new Student(3,"LLL");Student s5 = new Student(5,"HHH");Student s7 = new Student(7,"JJJ");for (int i = 0; i < webpages.size(); i++) {if (i % s3.getId() == 0)s3.visit(webpages.get(i));if (i %s5.getId( ) == 0)s5.visit(webpages.get(i));if (i %s7.getId( ) == 0)s7.visit(webpages.get(i));}webpages.clear();System.gc();} }class Student{private int id;private String name;private List<WebPage> history = new ArrayList<>();public Student(int id, String name) {this.id = id;this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<WebPage> getHistory() {return history;}public void setHistory(List<WebPage> history) {this.history = history;}public void visit(WebPage wp){if(wp != null){history.add(wp);}} }class WebPage{private String url;private String content;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getContent() {return content;}public void setContent(String content) {this.content = content;} }

    分析7號JJJ對象的內(nèi)存占用

    • 考慮JJJ同學(xué):15個 webpage,每個對應(yīng)152個字節(jié) 15* 152= 2280字節(jié),即為elementData的實際大小
    • 能被7整除,且能被3整除,以及能被7整除,且能被5整除的數(shù)值有:0,21,42,63,84,35,70 共7個數(shù)。7*152 = 1064字節(jié),2280 -1064 +72 = 1288字節(jié)
    • 72個字節(jié)組成為:15個elementData的元素*4字節(jié) =60字節(jié),60+8個對象頭的字節(jié)數(shù)+4字節(jié)=72字節(jié)

    4.5 支配樹

    • 概述

    MAT提供了一個稱為支配樹(Dominator Tree)的對象圖。支配樹體現(xiàn)了對象實例間的支配關(guān)系。**在對象引用圖中,所有指向?qū)ο驜的路徑都經(jīng)過對象A,則認(rèn)為對象A支配對象B。如果對象A是離對象B最近的一個支配對象,則認(rèn)為對象A為對象B的直接支配者。**支配樹是基于社象間的引用圖所建立的,它有以下基本性質(zhì):

    • 對象A的子樹(所有被對象A支配的對象集合)表示對象A的保留集(retained set),即深堆。
    • 如果對象A支配對象B,那么對象A的直接支配者也支配對象B。
    • 支配樹的邊與對象引用圖的邊不直接對應(yīng)。
    • 圖示

    四、再談內(nèi)存泄露

    1.內(nèi)存泄露的理解與分類

    • 概述

    可達(dá)性分析算法來判斷對象是否是不再使用的對象,本質(zhì)都是判斷一個對象是否還被引用。那么對于這種情況下,由于代碼的實現(xiàn)不同就會出現(xiàn)很多種內(nèi)存泄漏問題(讓JVM誤以為此對象還在引用中,無法回收,造成內(nèi)存泄漏)。

    1.1 內(nèi)存泄漏與內(nèi)存溢出的關(guān)系

  • 內(nèi)存泄漏(Memory Leak):
    申請了內(nèi)存用完了不釋放,比如一共有1024M 的內(nèi)存,分配了512M的內(nèi)存一直不回收,那么可以用的內(nèi)存只有521M 了,仿佛泄露掉了一部分。

  • 內(nèi)存溢出(Out Of Memory):

    申請內(nèi)存時,沒有足夠的內(nèi)存可以使用。

  • 可見,內(nèi)存泄漏和內(nèi)存溢出的關(guān)系:內(nèi)存泄漏的增多,最終會導(dǎo)致內(nèi)存溢出。

    1.2 內(nèi)存泄漏的分類

    • 經(jīng)常發(fā)生:發(fā)生內(nèi)存泄露的代碼會被多次執(zhí)行,每次執(zhí)行,泄露一塊內(nèi)存;
    • 偶然發(fā)生:在某些特定情況下才會發(fā)生;
    • 一次性:發(fā)生內(nèi)存泄露的方法只會執(zhí)行一次;
    • 隱式泄漏:一直占著內(nèi)存不釋放,直到執(zhí)行結(jié)束;嚴(yán)格的說這個不算內(nèi)存泄漏,因為最終釋放掉了,但是如果執(zhí)行時間特別長,也可能會導(dǎo)致內(nèi)存耗盡。

    2.Java中內(nèi)存泄漏的8種情況

    2.1 靜態(tài)集合類

    靜態(tài)集合類,如HashMap、LinkedList等等。如果這些容器為靜態(tài)的,那么它們的生命周期與JVM程序一致,則容器中的對象在程序結(jié)束之前將不能被釋放,從而造成內(nèi)存泄漏。簡單而言,長生命周期的對象持有短生命周期對象的引用,盡管短生命周期的對象不再使用,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收。

    public class test01(){static List list = new ArrayList();public void oomTest(){Object o = new Object();//局部變量list.add(o);} }

    2.2 單例模式

    單例模式,和靜態(tài)集合導(dǎo)致內(nèi)存泄露的原因類似,因為單例的靜態(tài)特性,它的生命周期和JVM 的生命周期一樣長,所以如果單例對象如果持有外部對象的引用,那么這個外部對象也不會被回收,那么就會造成內(nèi)存泄漏。

    2.3 內(nèi)部類持有外部類

    內(nèi)部類持有外部類,如果一個外部類的實例對象的方法返回了一個內(nèi)部類的實例對象。
    這個內(nèi)部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由于內(nèi)部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內(nèi)存泄漏。

    2.4 各種連接,數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接和IO連接等

    在對數(shù)據(jù)庫進(jìn)行操作的過程中,首先需要建立與數(shù)據(jù)庫的連接,當(dāng)不再使用時,需要調(diào)用close方法來釋放與數(shù)據(jù)庫的連接。只有連接被關(guān)閉后,垃圾回收器才會回收對應(yīng)的對象。
    否則,如果在訪問數(shù)據(jù)庫的過程中,對Connection、Statement或ResultSet不顯性地關(guān)閉,將會造成大量的對象無法被回收,從而引起內(nèi)存泄漏。

    public static void main( String[] args) {try {Connection conn = null;Class.forName( "com.mysql.jdbc.Driver" );conn = DriverManager.getConnection( "ur1", "","");Statement stmt = conn.createStatement();Resultset rs = stmt.executeQuery("....");}catch (Exception e) {//異常日志}finally {//1.關(guān)閉結(jié)果集Statement//2.關(guān)閉聲明的對象ResultSet//3.關(guān)閉連接Connection } }

    2.5 變量不合理作用域

    變量不合理的作用域。一般而言,一個變量的定義的作用范圍大于其使用范圍,很有可能會造成內(nèi)存泄漏。另一方面,如果沒有及時地把對象設(shè)置為null,很有可能導(dǎo)致內(nèi)存泄漏的發(fā)生。

    public class UsingRandom {private String msg;public void receiveMsg(){readFromNet();//從網(wǎng)絡(luò)中接受數(shù)據(jù)保存到msg中saveDB();//把msg保存到數(shù)據(jù)庫中} } //如上面這個偽代碼,通過readFromNet方法把接受的消息保存在變量 //msg中,然后調(diào)用saveDB方法把msg的內(nèi)容保存到數(shù)據(jù)庫中, //此時msg已經(jīng)就沒用了,由于msg的生命周期與對象的生命周期相同, //此時msg還不能回收,因此造成了內(nèi)存泄漏。 //實際上這個msg變量可以放在receiveMsg方法內(nèi)部,當(dāng)方法使用完, //那么msg的生命周期也就結(jié)束,此時就可以回收了。還有一種方法, //在使用完msg后,把msg設(shè)置為null, //這樣垃圾回收器也會回收msg的內(nèi)存空間。

    2.6 改變哈希值

    改變哈希值,當(dāng)一個對象被存儲進(jìn)HashSet集合中以后,就不能修改這個對象中的那些參與計算哈希值的字段了。
    否則,對象修改后的哈希值與最初存儲進(jìn)HashSet集合中時的哈希值就不同了,在這種情況下,即使在contains方法使用該對象的當(dāng)前引用作為的參數(shù)去HashSet集合中檢索對象,也將返回找不到對象的結(jié)果,這也會導(dǎo)致無法從HashSet集合中單獨(dú)刪除當(dāng)前對象,造成內(nèi)存泄漏。
    這也是String為什么被設(shè)置成了不可變類型,我們可以放心地把String存入 HashSet,或者把String 當(dāng)做HashMap的key 值;

    public class test02 {public static void main(String[ ] args) {HashSet set = new HashSet( );Person p1 = new Person( id: 1001,name: "AA" );Person p2 = new Person( id: 1002,name: "BB");set.add(p1);set.add(p2);p1.name = "CC";//此操作就是導(dǎo)致對象刪除不掉的原因set.remove(p1);System.out.println(set);set.add(new Person(1001,"CC" ));System.out.println(set);//輸出三個對象set.add(new Person(1001,"AA"));System.out.printLn(set);//輸出四個對象,解析如下圖} }class Person{private int id;private String name;public Person(int id, String name) {this.id = id;this.name = name;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof Person)) return false;Person person = (Person) o;return getId() == person.getId() && Objects.equals(getName(), person.getName());}@Overridepublic int hashCode() {return Objects.hash(id, name);}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;} }

    2.7 緩存泄漏

    內(nèi)存泄漏的另一個常見來源是緩存,一旦你把對象引用放入到緩存中,他就很容易遺忘。比如:之前項目在一次上線的時候,應(yīng)用啟動奇慢直到夯死,就是因為代碼中會加載一個表中的數(shù)據(jù)到緩存(內(nèi)存)中,測試環(huán)境只有幾百條數(shù)據(jù),但是生產(chǎn)環(huán)境有幾百萬的數(shù)據(jù)。
    對于這個問題,可以使用WeakHashMap(軟引用)代表緩存,此種Map的特點是,當(dāng)除了自身有對key的引用外,此key沒有其他引用那么此map會自動丟棄此值。

    public class MapTest {static Map wMap = new WeakHashMap();static Map map = new HashMap();public static void main(String[] args) {init();testweakHashMap( );testHashMap();}public static void init() {String s1 = new String( original: "obejct1");String s2 = new String( original: "obejct2");String s3 = new String( original: "obejct3");String s4 = new strEing( original: "obejct4");wMap.put(s1, "cacheObject1");wMap.put(s2,"cacheObject2");map.put(ref3,"cacheObject3" );map.put(ref4,"cacheObject4" );System.out.println("string引用s1,s2,s3,s4消失");}public static void testweakHashMap() {System.out.print1n( "weakHashMap GC之前");for (Object o : wMap.entrySet()){System.out.println(o);} try {System.gc();TimeUnit.SECONDS.sleep( timeout: 5);}catch (InterruptedException e) {e.printStackTrace();}System.out.println( "weakHashMap GC之后");for (object o : wMap. entrySet()) {System.out.println(o);}}public static void testHashMap() {System.out.print1n( "HashMap GC之前");for (Object o : Map.entrySet()){System.out.println(o);} try {System.gc();TimeUnit.SECONDS.sleep( timeout: 5);}catch (InterruptedException e) {e.printStackTrace();}System.out.println( "HashMap GC之后");for (object o : Map. entrySet()) {System.out.println(o);}} }

    2.8 監(jiān)聽器和回調(diào)

    內(nèi)存泄漏另一個常見來源是監(jiān)聽器和其他回調(diào),如果客戶端在你實現(xiàn)的API中注冊回調(diào),卻沒有顯示的取消,那么就會積聚。
    需要確保回調(diào)立即被當(dāng)作垃圾回收的最佳方法是只保存它的弱引用,例如將他們保存成為weakHashMap中的鍵。

    3.內(nèi)存泄漏案例分析

    3.1 案例一

    public class MyStack {private Object[] elements;private int size = 0;private static final int DEFAULT_INITIAL_CAPACITY = 16;public MyStack() {elements = new Object[DEFAULT_INITIAL_CAPACITY];}//壓棧操作public void push(Object e) {ensureCapacity();elements[size++] = e;}//這個出棧操作是錯誤的,我們只是把想要刪除的指針引用向下面移了,被刪除的對象還占著空間/*public Object pop(){if (size == 0)throw new EmptyStackException();return elements[ --size];}*///這樣就可以了,讓被刪除的對象的值置為nullpublic Object pop(){if(size == 0){throw new EmptyStackException();}Object o = elements[--size];elements[size] = null; //size已經(jīng)減過了return o;}private void ensureCapacity() {if (elements.length == size)elements = Arrays.copyOf(elements,2 * size + 1);}}

    3.2 案例二

    public class TestActivity extends Activity{private static final Object key = new Object();@Overrideprotected void onCreate( Bundle savedInstanceState) {super.onCreate( savedInstanceState) ;setContentview(R.layout.activity_main);new Thread(){//匿名線程public void run() {synchronized (key) {try {key.wait();}catch (InterruptedException e) {e. printstackTrace();}}}}.start();} }
    • 出現(xiàn)的問題

    匿名線程始終不能被GC

    • 解決辦法
  • 使用線程時,一定要確保線程在周期性對象(如Activity)銷毀時能正常結(jié)束,如能正常結(jié)束,但是Activity銷毀后還需執(zhí)行一段時間,也可能造成泄露,此時可采用weakReference方法來解決,另外在使用Handler的時候,如存在Delay操作,也可以采用weakReference;
  • 使用Handler + HandlerThread時,記住在周期性對象銷毀時調(diào)用looper.quit()方法;
  • 五、OQL語言查詢對象信息

    • 概述

    MAT支持一種類似于SQL的查詢語言O(shè)QL (Object Query Language)。OQL使用類SQL語法,可以在堆中進(jìn)行對象的查找和篩選。

    1.SELECT子句

    在MAT中,Select子句的格式與SQL基本一致,用于指定要顯示的列。Select子句中可以使用“*”,查看結(jié)果對象的引用實例(相當(dāng)于outgoing references)。

    • 使用“OB3ECTS”關(guān)鍵字,可以將返回結(jié)果集中的項以對象的形式顯示。

      SELECT objects v.elementData FROM java.util.Vector v

      SELECT OBECTS s.value FROM java.lang.string s

    • 在Select子句中,使用“AS RETAINED SET”關(guān)鍵字可以得到所得對象的保留集。

      SELECT AS RETAINED SET * FROM com.atguigu.mat. Student

    • “DISTINCT”關(guān)鍵字用于在結(jié)果集中去除重復(fù)對象。
      SELECT DISTINCT OBECTS classof(s)FROM java.lang.String s

    2.FROM子句

    • From子句用于指定查詢范圍,它可以指定類名、正則表達(dá)式或者對象地址。

      SELECT * FROM java.lang.String s

    • 下例使用正則表達(dá)式,限定搜索范圍,輸出所有com.atguigu包下所有類的實例

      SELECT FROM "com.atguigul…”

    • 也可以直接使用類的地址進(jìn)行搜索。使用類的地址的好處是可以區(qū)分被不同ClassLoader加載的同一種類型。
      select * from 0x37a0b4d

    3.WHERE子句

    where子句用于指定oQL的查詢條件。oQL查詢將只返回滿足where子句指定條件的對象。Where子句的格式與傳統(tǒng)SQL極為相似。

    • 下例返回長度大于10的char數(shù)組。
      SELECT * FROM char[] s WHERE s.@length>10
    • 下例返回包含“java”子字符串的所有字符串,使用“LIKE”操作符,“LIKE”操作符的操作參數(shù)為正則表達(dá)式。
      SELECT * FROM java.lang.String s WHERE toString(s)LIKE “.java.”
    • 下例返回所有value域不為null的字符串,使用“=”操作符。
      SELECT * FROM java.lang.String s where s.value !=null
    • where子句支持多個條件的AND、OR運(yùn)算。下例返回數(shù)組長度大于15,并且深堆大于1000字節(jié)的所有Vector對象。
      SELECT * FROM java.util.Vector v WHERE v.elementData.@length>15 AN Dv.@retainedHeapsize>1000

    4.內(nèi)置對象與方法

    0QL中可以訪問堆內(nèi)對象的屬性,也可以訪問堆內(nèi)代理對象的屬性。訪問堆內(nèi)對象的屬性時,格式如下:
    [ <alias>. ] <field> . <field>. <field>其中alias為對象名稱。

    • 訪問java.io.File對象的path屬性,并進(jìn)一步訪問path的value屬性:

      SELECT toString(f.path.value)FROM java.io.File f

    • 下例顯示了String對象的內(nèi)容、objectid和objectAddress。
      SELECT s.toString(), s.@objectId, s.@objectAddress FROMjava.lang.String s

    • 下例顯示java.util.Vector內(nèi)部數(shù)組的長度。
      SELECT v.elementData.@length FROM java.util.Vector v

    • 下例顯示了所有的java.util.Vector對象及其子類型
      select * from INSTANCEOF java.util.Vector

    六、JProfiler

    1.基本概述

    想要用一款集成在idea的分析工具,或想要比mat工具更加全面,JProfiler由此誕生,是一款Java應(yīng)用性能診斷工具,功能強(qiáng)大,但注意是收費(fèi)的

    1.1 特點

    • 使用方便、界面操作友好―(簡單且強(qiáng)大)
    • 對被分析的應(yīng)用影響小(提供模板)
    • CPU, Thread ,Memory分析功能尤其強(qiáng)大
    • 支持對jdbc ,nosql,jsp, servlet, socket等進(jìn)行分析
    • 支持多種模式(離線,在線)的分析
    • 支持監(jiān)控本地、遠(yuǎn)程的JVM
    • 跨平臺,擁有多種操作系統(tǒng)的安裝版本

    1.2 主要功能

  • 方法調(diào)用:對方法調(diào)用的分析可以幫助您了解應(yīng)用程序正在做什么,并找到提高其性能的方法
  • 內(nèi)存分配:通過分析堆上對象、引用鏈和垃圾收集能幫您修復(fù)內(nèi)存泄漏問題,優(yōu)化內(nèi)存使用
  • 線程和鎖:JProfiler提供多種針對線程和鎖的分析視圖助您發(fā)現(xiàn)多線程問題
  • 高級子系統(tǒng):許多性能問題都發(fā)生在更高的語義級別上。例如,對于JDBC調(diào)用,您可能希望找出;執(zhí)行最慢的SQL語句。JProfiler支持對這些子系統(tǒng)進(jìn)行集成分析
  • 2.具體使用

    2.1 數(shù)據(jù)采集方式

    • Instrumentation(重構(gòu)模式):這是JProfiler全功能模式。在class加載之前,JProfier把相關(guān)功能代碼寫入到需要分析的class的bytecode中,對正在運(yùn)行的jvm有一定影響。
    • 優(yōu)點:功能強(qiáng)大。在此設(shè)置中,調(diào)用堆棧信息是準(zhǔn)確的。
    • 缺點:若要分析的class較多,則對應(yīng)用的性能影響較大,CPU開銷可能很高(取決于Filter的控制)。因此使用此模式一般配合Filter使用,只對特定的類或包進(jìn)行分析。
    • Full sampling(抽樣模式):類似于樣本統(tǒng)計,每隔一定時間(5ms )將每個線程棧中方法棧中的信息統(tǒng)計出來。

      • 優(yōu)點:對[PU的開銷非常低,對應(yīng)用影響小(即使你不配置任何Filter)
      • 缺點:一些數(shù)據(jù)/特性不能提供(例如:方法的調(diào)用次數(shù)、執(zhí)行時間)

    2.2 各種重要功能

    這里這些功能演示及案例分析就不扣字寫了,大家想要了解請去尚硅谷看JVM視頻(343~349)

    • 遙感監(jiān)測(Telemetries)
    • 內(nèi)存視圖(Live Memory)
    • 堆遍歷(heap walker)
    • cpu視圖(cpu views)
    • 線程視圖(threads)
    • 監(jiān)視器&鎖(Monitors&locks)

    七、Arthas

    1.前奏

    1.1 JProfiler與JvisualVM的缺點

    這兩款工具有個缺點,都必須在服務(wù)端項目進(jìn)程中配置相關(guān)的監(jiān)控參數(shù)。然后工具通過遠(yuǎn)程連接到項目進(jìn)程,獲取相關(guān)的數(shù)據(jù)。這樣就會帶來一些不便,比如線上環(huán)境的網(wǎng)絡(luò)是隔離的,本地的監(jiān)控工具根本連不上線上環(huán)境。并且類似于Jprofiler這樣的商業(yè)工具,是需要付費(fèi)的。

    1.2 概述

    Arthas(阿爾薩斯)是Alibaba開源的Java診斷工具,在線排查問題,無需重啟;動態(tài)跟蹤Java代碼;實時監(jiān)控JVM狀態(tài)。Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同時提供豐富的Tab自動補(bǔ)全功能,進(jìn)一步方便進(jìn)行問題的定位和診斷。借鑒并基于很多優(yōu)秀的軟件組合而成的一個工具

    • 注意:

    由于是開源的項目且是國人開發(fā),這里就不做過多介紹,大家可移步至官網(wǎng)查看中文文檔https://arthas.aliyun.com/doc/,里面有十分詳細(xì)的說明

    八、JMC(Java Mission Control)

    1.概述

    是oracle公司自己的工具,在JDK的bin目錄下找到j(luò)mc.exe可執(zhí)行文件

    九、其他調(diào)優(yōu)工具

    • Tprofiler:是由阿里開源的一款尋找錯誤熱點代碼的工具
    • Btrace:簡潔明了,大意是一個Java平臺的安全的動態(tài)追蹤工具。可以用來動態(tài)地追蹤一個運(yùn)行的Java程序。BTrace動態(tài)調(diào)整目標(biāo)應(yīng)用程序的類以注入跟蹤代碼(“字節(jié)碼跟蹤”)。
    • YourKit
    • JProbe
    • Spring Insight

    總結(jié)

    以上是生活随笔為你收集整理的Java虚拟机|JVM知识点汇总及简述->性能监控与调优的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。