JVM(五) 生產環境內存溢出調優


1.gc配置參數

1.1 控制台打印gc日志

-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC(詳細的gc信息)

1.2 輸出gc日志到指定文件 -Xloggc:

(例如:  -Xloggc:C:\logs\gc.log)

1.3 Gc日志分塊

-XX:-UseGCLogFileRotation
-XX:GCLogFileSize = 8M

1.4 指定最小堆內存 -Xms

(例如-Xms20M指定最小堆內存為20M)

1.5 指定最大堆內存 -Xmx

(例如-Xms20M指定最大堆內存為20M)

1.6 指定新生代內存大小 -Xmn

(例如-Xmn10M指定新生代內存為10M)

1.7 指定eden區在新生代的占比 -XX:SurvivorRatio=8

(eden比S0,S1區比例為8:1:1)

1.8元空間設置大小 -XX:MetaspaceSize

初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當提高該值。
-XX:MaxMetaspaceSize

最大空間,默認是沒有限制的。

1.9 指定創建的對象超過多少會直接創建在老年代 -XX:PretenureSizeThreshold

此參數只能在serial收集器和parnew收集器才有效

(比如 -XX:PretenureSizeThreshold=1M)

1.10 指定多大年齡的對象進入老年代 -XX:MaxTenuringThreshold

(比如 -XX:MaxTenuringThreshold=15 默認也是15次)

1.11 內存溢出時候打印堆內存快照 -XX:+HeapDumpOnOutOfMemoryError

-XX:+HeapDumpOnOutOfMemoryError

該配置會把快照保存在用戶目錄或者tomcat目錄下,也可以通過 -XX:HeapDumpPath=/tmp/heapdump.dump來顯示指定路徑

1.12 使用serialGC收集器 -XX:+UseSerialGC  

1.13 使用ParNew收集器 -XX:+UseParNewGC

1.14使用cms收集器 -XX:+UseConcMarkSweepGC

該標志首先是激活CMS收集器。默認HotSpot JVM使用的是並行收集器。

啟動CMS多線程執行

-XX:+CMSConcurrentMTEnabled

指定CMS啟動線程個數

 -XX:ConcGCThreads

標志-XX:ConcGCThreads=<value>(例如:-XX:ConcGCThreads=4)

指定老年代內存達到多少的百分比進行垃圾收集

-XX:CMSInitiatingOccupancyFraction=70 和 -XX:+UseCMSInitiatingOccupancyOnly

執行多少次fullgc后執行一次full gc的標記壓縮算法

默認參數場景是:-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0。這意味着每次full gc(標記清除)后,都會壓縮

開啟在CMS重新標記階段之前的清除minor gc

-XX:+CMSScavengeBeforeRemark

2. GC日志詳情

2.1 minor gc

1.178: [GC (Allocation Failure) [PSYoungGen: 32768K->2688K(37888K)] 32768K->2696K(123904K), 0.0048091 secs] [Times: user=0.05 sys=0.02, real=0.00 secs]

1.178: # 虛擬機啟動以來的秒數
[GC (Allocation Failure)
  [PSYoungGen: 32768K
->2688K(37888K)] # gc類型:新生代gc前占用大小 -> 新生代gc后占用大小(新生代總容量)
  32768K->2696K(123904K), # 堆空間gc前占用大小 -> 堆空間gc后占用大小(堆空間總容量)
  0.0048091 secs #gc總耗時
] [
Times:
  user
=0.05 #用戶態耗費時間
  sys=0.02, #內核態耗費時間
  real=0.00 secs #整個過程實際耗費時間
]
# user+sys是CPU時間,每個CPU core單獨計算,所以這個時間可能會是real的好幾倍。

2.2 full gc / major gc

 7.740: [Full GC (Metadata GC Threshold) [PSYoungGen: 6612K->0K(333824K)] [ParOldGen: 10378K->15906K(95744K)] 16990K->15906K(429568K), [Metaspace: 34026K->34026K(1079296K)], 0.1300535 secs] [Times: user=0.38 sys=0.00, real=0.13 secs] 

7.740: #虛擬機啟動以來的秒數
[
Full GC (Metadata GC Threshold) #gc類型
[PSYoungGen: 6612K->0K(333824K)] # 新生代gc前占用大小 -> 新生代gc后占用大小(新生代總容量)
[ParOldGen: 10378K->15906K(95744K)] # 老年代代gc前占用大小 -> 老年代gc后占用大小(老年代總容量)
16990K->15906K(429568K), # 堆空間gc前占用大小 -> 堆空間gc后占用大小(堆空間總容量)
[Metaspace: 34026K->34026K(1079296K)], # 元空間gc前占用大小 -> 元空間gc后占用大小(元空間總容量)
0.1300535 secs #gc總耗時
]

[
Times:
  user=0.05 #用戶態耗費時間
  sys=0.02, #內核態耗費時間
  real=0.00 secs #整個過程實際耗費時間
]
# user+sys是CPU時間,每個CPU core單獨計算,所以這個時間可能會是real的好幾倍。

3. 生產環境死鎖定位

 3.1 來一段死鎖代碼

package com.kawa.xuduocloud.zuul;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: xuduocloud
 * @author: Brian Huang
 * @create: 2019-12-07 15
 **/
@RestController
public class JvmController {

    @GetMapping("/deadLock")
    public ResponseEntity<String> deadLock(){

        Object lockA = new Object();
        Object lockB = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (lockA) {
                    System.out.println(name + " got lockA,  want LockB");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                    synchronized (lockB) {
                        System.out.println(name + " got lockB");
                        System.out.println(name + ": say Hello!");
                    }
                }
            }
        }, "thread-lock-A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {

                String name = Thread.currentThread().getName();
                synchronized (lockB) {
                    System.out.println(name + " got lockB, want LockA");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                    synchronized (lockA) {
                        System.out.println(name + " got lockA");
                        System.out.println(name + ": say Hello!");
                    }
                }

            }
        }, "thread-lock-B").start();
        return  new ResponseEntity<>("deadLock", HttpStatus.OK);
    }

}

3.2 jps 查看所有的Java進程及其pid

我這邊啟動是XuduocloudZuulApplication這個項目

 3.3 然后通過jstack <pid> >  <path> 導出jstack信息

$ jstack 18640 > C:\\Users\\LiangHuang\\Desktop\\lock.txt

打開lock.txt文件,搜索關鍵字“deadlock”,可以很清晰的看到鎖與等待鎖的信息

Found one Java-level deadlock:
=============================
"thread-lock-B":
  waiting to lock monitor 0x000000001e9975b8 (object 0x00000000e8cc87d8, a java.lang.Object),
  which is held by "thread-lock-A"
"thread-lock-A":
  waiting to lock monitor 0x0000000017e26338 (object 0x00000000e8cc87c8, a java.lang.Object),
  which is held by "thread-lock-B"

Java stack information for the threads listed above:
===================================================
"thread-lock-B":
    at com.kawa.xuduocloud.zuul.JvmController$2.run(JvmController.java:55)
    - waiting to lock <0x00000000e8cc87d8> (a java.lang.Object)
    - locked <0x00000000e8cc87c8> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)
"thread-lock-A":
    at com.kawa.xuduocloud.zuul.JvmController$1.run(JvmController.java:34)
    - waiting to lock <0x00000000e8cc87c8> (a java.lang.Object)
    - locked <0x00000000e8cc87d8> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

 其實定位線上死鎖比較簡單兩個指令就可以可了  ==> jps+ jstack

4. 生產環境CPU100%定位

 4.1 來一個cpu100%代碼

4.2 通過top指令找到cpu占用率高的程序  top

可以看到pid為7977java進程占用最高

4.3 通過ps指令查找最展cpu的子線程  ps -mp pid -o THREAD,tid,time

可以看到tid為8072的子線程占用率最高

 4.4 通過jstack導出日志 分析日志   jstack pid >a.txt

查找方式最將剛才tid從十進制轉為16進制   8072  => 1f88

在日志文件搜索關鍵字 ‘1f88’ 查找nid包含這個就是對應cpu100%的日志信息 , 如下

 生產環境cpu飆高三個指令就可以了  => top + ps + jstack

5.生產環境堆內存溢出定位

環境配置 -verbose:gc -XX:+PrintGCDetails -Xloggc:C:\Users\LiangHuang\Desktop\up\gc.log -XX:-UseGCLogFileRotation -XX:GCLogFileSize=5M -XX:HeapDumpPath=C:\Users\LiangHuang\Desktop\heapdump.dump -XX:+HeapDumpOnOutOfMemoryError

可以在oom時候生產快照,方便我們使用工具分析

5.1 內存溢出的定位

 5.1.1 上代碼 內存溢出

5.1.2 jps查看程序pid

5.1.3 jstat 查看jvm內存使用情況 jstat -gctuil pid

5.1.4  oracle jdk 工具  jvisualvm.exe 分析快照

jvisualvm.exe導入dump文件

可以看到異常的線程,打開Show Threads 詳細信息,搜索關鍵字“http-nio-82-exec-1”

5.2 內存泄漏的定位

 5.2.1 上代碼內存泄漏

5.2.2 jps 查看程序pid

5.2.3 jstat查看內存情況 jstat -gcutil pid

 

 可以看到eden區和老年代  沒有被回收,內存泄漏的導致不能被回收

5.2.4  oracle jdk 工具  jvisualvm.exe 分析快照

jvisualvm.exe導入dump文件

6.總結JVM

6.1. jvm常用的調優參數

-Xmx 堆內存最大值 -Xmx2g
-Xms 堆內存最小值 -Xms2g
-Xmn 新生代大小 默認時堆的1/3
-xss 線程棧空間大小 -Xss256k

6.2. jvm運行時數據區域由哪幾個部分組成,各自作用

線程共享
堆: new出來的對象放在堆中,對象可能會棧上分配(內存逃逸分析)
元空間/方法區:class對象,常量,靜態變量,運行時常量池

內存獨占
棧:棧內部由棧幀組成,先進后出,棧幀(局部變量表,操作數棧,動態鏈接,返回地址)
PC寄存器(程序計數器):指向當前線程執行到多少行代碼
本地方法棧: native修飾的方法

6.3. gc算法有哪些,gc收集器有哪些

gc算法==
分代算法
復制算法
標記清除
標記壓縮
引用計數
可達性分析法

gc收集器==
young區 (Serial,ParNew,Parallel Scavenge)
old區(SerialOld,ParallelOld,CMS)
G1

6.4. GC Roots的對象有哪些

局部變量
靜態變量
本地方法棧
靜態常量 static final

6.5. 垃圾收集器各自優缺點

Serial:單線程收集 非單核服務器 stw比較長
Parnew: 多線程收集 多核線程比較快
PS: 可控吞吐量 (用戶線程執行時間)/(用戶線程執行時間+gc執行時間)
CMS: 初始標記(標記和gc roots相關的對象 有swt),
並發標記(標記被初始標記的對象關聯的對象 和用戶線程一起執行),
重新標記(新生代可達引用到老年代的對象,剛進入老年代的對象 stw),
並發清除 (和用戶線程一起執行,清除老年代沒有被標記的對象)

6.6.  full gc,minor gc,major gc,stw

minor gc新生代gc
major gc老年代gc
full gc= minor gc + major gc
stw: stop the word 停止其他所有的用戶線程

6.7. jvm中一次完整的gc流程 ygc => fgc ,對象如何晉升到老年代

正常流程===
經過15次ygc(復制算法)晉升到老年代
大對象直接進入到老年代

非正常=== 
動態年齡 (s區
50%以上對象年齡 > s區平均年齡則進階老年代,如果老年代空間不足則發生full gc) 空間擔保 (s0或s1放不下這些對象,會進行於此空間擔保(老年代剩余空間大於歷代s區進階的平均值則擔保成功))如果擔保 失敗則發生full gc 元空間/方法區不足也會發生full gc,但不會被垃圾收集器回收

6.8.  內存泄漏判斷

對象不能被gc回收就會導致內存泄漏
jstack 發生gull gc后,新生代和老年代的占用情況,如果占用的空間沒有降低則可以判斷放生內存泄漏
如果fgc放生頻率遠遠高於ygc則發生了內存泄漏

6.9. 內存溢出判斷

后台沒寫分頁,大數據量,內存溢出報錯后,對象會被回收,整個服務任然可用
內存泄漏導致的內存溢出,泄漏的對象不會被回收,知道我們的整個堆內存被占滿,導致整個服務不可用

打印dump文件,分析快照,查找大對象

6.10.  java中的引用類型有哪些

強引用:Object o = new Objetc(); gc不會回收強引用對象
軟引用:SoftReference 對內存占滿時就會這里面的對象(這個一般用來做緩存)
弱引用:WearkReference 只能存在下一次gc之前 (minor gc,major gc發生就會被回收)
虛引用:Object o = new Object(); o = null; 提醒gc來回收這個對象


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM