JVM:第一章:設計一個剛好在一秒堆溢出的程序

創建了一個JVMDemo類:

package common;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author:Liaozhiwei
 * @Date: 2019/11/23 18:45
 */
public class JVMDemo {

    /**
     * JVM內存分配測試
     * 堆內存30M|Yong10M(Eden8M,from1M,to1M)|Old20M
     *初始堆大小:-Xms30m 最大堆大小:-Xmx30m
     * 讓虛擬機在發生內存溢出時 Dump 出當前的內存堆轉儲快照,以便分析用:-XX:+HeapDumpOnOutOfMemoryError
     * 打印 GC 信息:-XX:+PrintGCDetails
     * 永久代(方法區)的最大值:-XX:MaxPermSize=20M
     */
    byte[] a = new byte[1024*1024*1];//1M
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        int i =1;
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        System.out.println("初始化堆的當前內存使用量:"+memoryMXBean.getHeapMemoryUsage());
        System.out.println("初始化非堆內存的當前內存使用量:"+memoryMXBean.getNonHeapMemoryUsage());
        while (true){
            list.add(new JVMDemo());
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            System.out.println("非堆內存的當前內存使用量:"+memoryMXBean.getNonHeapMemoryUsage());
            System.out.println("堆內存的當前內存使用量:"+memoryMXBean.getHeapMemoryUsage());
            System.out.println(i++);
        }
    }
}

配置JVM:

-XX:MaxPermSize=20M -Xms30m -Xmx30m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:HeapDumpPath=C:\Users\Administrator\Desktop\error_logs

控制檯輸出:

初始化堆的當前內存使用量:init = 31457280(30720K) used = 2083952(2035K) committed = 30408704(29696K) max = 30408704(29696K)

可以看到我給設置了30M堆內存,初始化的時候已經被使用了2035k了,最大可使用的堆內存爲29M,因爲新生代from區與to區二個只能二選一,必須有一個爲空的,所以最大可使用29M。

堆內存的當前內存使用量:init = 31457280(30720K) used = 3302664(3225K) committed = 30408704(29696K) max = 30408704(29696K)
1

進入循環,可以看到增加了1190k

堆內存的當前內存使用量:init = 31457280(30720K) used = 4351256(4249K) committed = 30408704(29696K) max = 30408704(29696K)
2

這次增加了1024k,也就是1M

堆內存的當前內存使用量:init = 31457280(30720K) used = 5399848(5273K) committed = 30408704(29696K) max = 30408704(29696K)
3

增加了1M

堆內存的當前內存使用量:init = 31457280(30720K) used = 6616232(6461K) committed = 30408704(29696K) max = 30408704(29696K)
4

增加了1188k

堆內存的當前內存使用量:init = 31457280(30720K) used = 7832592(7649K) committed = 30408704(29696K) max = 30408704(29696K)
5

增加了1188k,現在已經7649k了,離8m(8192k)只有543k的內存空間了

[GC (Allocation Failure) [PSYoungGen: 7649K->984K(9216K)] 7649K->6112K(29696K), 0.0019481 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

然後開始新生代gc,新生代已使用7649k,gc之後新生代已使用984k,新生代總大小9M,GC前Java堆已使用容量7649k,GC後Java堆已使用容量6112k,總堆的大小29M。

堆內存的當前內存使用量:init = 31457280(30720K) used = 7454896(7280K) committed = 30408704(29696K) max = 30408704(29696K)
6

從6112k,增加了1168k,到7280k

堆內存的當前內存使用量:init = 31457280(30720K) used = 8503488(8304K) committed = 30408704(29696K) max = 30408704(29696K)
7

增加了1M

堆內存的當前內存使用量:init = 31457280(30720K) used = 9552080(9328K) committed = 30408704(29696K) max = 30408704(29696K)
8

增加了1M

堆內存的當前內存使用量:init = 31457280(30720K) used = 10600672(10352K) committed = 30408704(29696K) max = 30408704(29696K)
9

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 11649264(11376K) committed = 30408704(29696K) max = 30408704(29696K)
10

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 12697856(12400K) committed = 30408704(29696K) max = 30408704(29696K)
11

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 13746448(13424K) committed = 30408704(29696K) max = 30408704(29696K)
12

增加了1M
[GC (Allocation Failure) [PSYoungGen: 8296K->1000K(9216K)] 13424K->13296K(29696K), 0.0016737 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

開始新生代gc,新生代已使用8296k,gc之後新生代已使用1000k,新生代總大小9M,GC前Java堆已使用容量13424k,GC後Java堆已使用容量13296k,總堆的大小29M。
堆內存的當前內存使用量:init = 31457280(30720K) used = 14902416(14553K) committed = 30408704(29696K) max = 30408704(29696K)
13

從13296k增加了1257k到14553k

堆內存的當前內存使用量:init = 31457280(30720K) used = 15951008(15577K) committed = 30408704(29696K) max = 30408704(29696K)
14

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 16999600(16601K) committed = 30408704(29696K) max = 30408704(29696K)
15

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 18048192(17625K) committed = 30408704(29696K) max = 30408704(29696K)
16

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 19096784(18649K) committed = 30408704(29696K) max = 30408704(29696K)
17

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 20145376(19673K) committed = 30408704(29696K) max = 30408704(29696K)
18

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 21193968(20697K) committed = 30408704(29696K) max = 30408704(29696K)
19

增加了1M
[GC (Allocation Failure) [PSYoungGen: 8401K->1016K(9216K)] 20697K->20488K(29696K), 0.0015551 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

開始新生代gc,新生代已使用8401k,gc之後新生代已使用1016k,新生代總大小9M,GC前Java堆已使用容量20697k,GC後Java堆已使用容量20488k,總堆的大小29M,剩餘9208k,小於新生代9216k,觸發FullGC


[Full GC (Ergonomics) [PSYoungGen: 1016K->0K(9216K)] [ParOldGen: 19472K->20243K(20480K)] 20488K->20243K(29696K), [Metaspace: 3416K->3416K(1056768K)], 0.0097494 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

新生代gc,新生代已使用1016k,gc之後新生代已使用0k,新生代總大小9M

老年代gc,老年代已使用19472k,gc之後老年代已使用20243k,老年代總大小20M


堆內存的當前內存使用量:init = 31457280(30720K) used = 21935136(21421K) committed = 30408704(29696K) max = 30408704(29696K)
20

增加了1178k
堆內存的當前內存使用量:init = 31457280(30720K) used = 22983728(22445K) committed = 30408704(29696K) max = 30408704(29696K)
21

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 24032320(23469K) committed = 30408704(29696K) max = 30408704(29696K)
22

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 25080912(24493K) committed = 30408704(29696K) max = 30408704(29696K)
23

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 26129504(25517K) committed = 30408704(29696K) max = 30408704(29696K)
24


堆內存的當前內存使用量:init = 31457280(30720K) used = 27178096(26541K) committed = 30408704(29696K) max = 30408704(29696K)
25

增加了1M
堆內存的當前內存使用量:init = 31457280(30720K) used = 28226688(27565K) committed = 30408704(29696K) max = 30408704(29696K)
26

增加了1M
[Full GC (Ergonomics) [PSYoungGen: 7321K->7168K(9216K)] [ParOldGen: 20243K->20243K(20480K)] 27565K->27411K(29696K), [Metaspace: 3419K->3419K(1056768K)], 0.0087983 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

新生代gc,新生代已使用7321K,gc之後新生代已使用7168K,新生代總大小9M

老年代gc,老年代已使用20243K,gc之後老年代已使用20243K,老年代總大小20M
[Full GC (Allocation Failure) [PSYoungGen: 7168K->7168K(9216K)] [ParOldGen: 20243K->20225K(20480K)] 27411K->27394K(29696K), [Metaspace: 3419K->3419K(1056768K)], 0.0091156 secs] [Times: user=0.11 sys=0.00, real=0.02 secs]

新生代gc,新生代已使用7168K,gc之後新生代已使用7168K,新生代總大小9M

老年代gc,老年代已使用20243K,gc之後老年代已使用20225K,老年代總大小20M

三種時間類型:

  • real: 程序從開始到結束所用的時鐘時間。這個時間包括其他進程使用的時間片和進程阻塞的時間(比如等待 I/O 完成)。
  • user:進程執行用戶態代碼(核心之外)所使用的時間。這是執行此進程所使用的實際 CPU 時間,其他進程和此進程阻塞的時間並不包括在內。在垃圾收集的情況下,表示 GC 線程執行所使用的 CPU 總時間。
  • sys:進程在內核態消耗的 CPU 時間,即在內核執行系統調用或等待系統事件所使用的 CPU 時間。
  • user + sys 時間告訴我們程序執行實際使用的 CPU 時間。注意這裏指所有的 CPU,因此如果在進程裏有多個線程的話,這個時間可能會超過 real 所表示的時鐘時間。如果出現user + sys 時間的和比 real 時間要大,這主要是因爲日誌時間是從 JVM 中獲得的,而這個 JVM 在多核的處理器上被配置了多個 GC 線程,由於多個線程並行地執行 GC,因此整個 GC 工作被這些線程共享,最終導致實際的時鐘時間(real)小於總的 CPU 時間(user + sys)。

在做性能優化時,我們一般採用 real 時間來優化程序。因爲最終用戶只關心點擊頁面發出請求到頁面上展示出內容所花的時間,也就是響應時間,而不關心你到底使用了多少個 GC 線程或者處理器。但並不是說 sys 和 user 兩個時間不重要,當我們想通過增加 GC 線程或者 CPU 數量來減少 GC 停頓時間時,可以參考這兩個時間。

所以可以根據使用(1秒 - real佔用的時間)= 休眠的時間,這樣休眠的時間加上GC使用的時間(real時間)就可以就可以設計出剛好一秒堆溢出

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid13144.hprof ...
Heap dump file created [28907711 bytes in 0.034 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at common.StringTool.<init>(StringTool.java:206)
    at common.JVMDemo.main(JVMDemo.java:30)
Heap
 PSYoungGen      total 9216K, used 7406K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 90% used [0x00000000ff600000,0x00000000ffd3bb20,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 20480K, used 20225K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)
  object space 20480K, 98% used [0x00000000fe200000,0x00000000ff5c07e8,0x00000000ff600000)
 Metaspace       used 3451K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 370K, capacity 388K, committed 512K, reserved 1048576K
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=20M; support was removed in 8.0

Process finished with exit code 1

 

問題:爲啥每次加的都不是1M呢?

解釋:每次加的都不是1M,原因一:ArrayList擴容影響的,通過ArrrayList空參構造器創建對象。底層創建一個長度爲10的數組,當我們向數組中添加11個元素時,底層會進行擴容,擴容爲原來的1.5倍(創建一個新的數組,長度爲原數組長度的1.5倍,將原數組複製到新數組中)。改造ArrayList,驗證想法:

運行之後,控制檯打印:


初始化堆的當前內存使用量:init = 31457280(30720K) used = 3089664(3017K) committed = 30408704(29696K) max = 30408704(29696K)
堆內存的當前內存使用量:init = 31457280(30720K) used = 4138256(4041K) committed = 30408704(29696K) max = 30408704(29696K)
1
堆內存的當前內存使用量:init = 31457280(30720K) used = 5186848(5065K) committed = 30408704(29696K) max = 30408704(29696K)
2
堆內存的當前內存使用量:init = 31457280(30720K) used = 6235440(6089K) committed = 30408704(29696K) max = 30408704(29696K)
3
堆內存的當前內存使用量:init = 31457280(30720K) used = 7284032(7113K) committed = 30408704(29696K) max = 30408704(29696K)
4
堆內存的當前內存使用量:init = 31457280(30720K) used = 8332624(8137K) committed = 30408704(29696K) max = 30408704(29696K)
5
[GC (Allocation Failure) [PSYoungGen: 8137K->1023K(9216K)] 8137K->6252K(29696K), 0.0069038 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
堆內存的當前內存使用量:init = 31457280(30720K) used = 7613624(7435K) committed = 30408704(29696K) max = 30408704(29696K)
6
堆內存的當前內存使用量:init = 31457280(30720K) used = 8913912(8704K) committed = 30408704(29696K) max = 30408704(29696K)
7
堆內存的當前內存使用量:init = 31457280(30720K) used = 9962504(9729K) committed = 30408704(29696K) max = 30408704(29696K)
8
堆內存的當前內存使用量:init = 31457280(30720K) used = 11011096(10753K) committed = 30408704(29696K) max = 30408704(29696K)
9
堆內存的當前內存使用量:init = 31457280(30720K) used = 12059688(11777K) committed = 30408704(29696K) max = 30408704(29696K)
10
堆內存的當前內存使用量:init = 31457280(30720K) used = 13108280(12801K) committed = 30408704(29696K) max = 30408704(29696K)
11
堆內存的當前內存使用量:init = 31457280(30720K) used = 14156872(13825K) committed = 30408704(29696K) max = 30408704(29696K)
12
[GC (Allocation Failure) [PSYoungGen: 8596K->920K(9216K)] 13825K->13324K(29696K), 0.0025148 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
堆內存的當前內存使用量:init = 31457280(30720K) used = 14855728(14507K) committed = 30408704(29696K) max = 30408704(29696K)
13
堆內存的當前內存使用量:init = 31457280(30720K) used = 15904320(15531K) committed = 30408704(29696K) max = 30408704(29696K)
14
堆內存的當前內存使用量:init = 31457280(30720K) used = 16952912(16555K) committed = 30408704(29696K) max = 30408704(29696K)
15
堆內存的當前內存使用量:init = 31457280(30720K) used = 18085408(17661K) committed = 30408704(29696K) max = 30408704(29696K)
16
堆內存的當前內存使用量:init = 31457280(30720K) used = 19134000(18685K) committed = 30408704(29696K) max = 30408704(29696K)
17
堆內存的當前內存使用量:init = 31457280(30720K) used = 20182592(19709K) committed = 30408704(29696K) max = 30408704(29696K)
18
堆內存的當前內存使用量:init = 31457280(30720K) used = 21231184(20733K) committed = 30408704(29696K) max = 30408704(29696K)
19
[GC (Allocation Failure) [PSYoungGen: 8328K->1000K(9216K)] 20733K->20580K(29696K), 0.0023324 secs] [Times: user=0.06 sys=0.03, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1000K->0K(9216K)] [ParOldGen: 19580K->20310K(20480K)] 20580K->20310K(29696K), [Metaspace: 3180K->3180K(1056768K)], 0.0108207 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
堆內存的當前內存使用量:init = 31457280(30720K) used = 22009368(21493K) committed = 30408704(29696K) max = 30408704(29696K)
20
堆內存的當前內存使用量:init = 31457280(30720K) used = 23057960(22517K) committed = 30408704(29696K) max = 30408704(29696K)
21
堆內存的當前內存使用量:init = 31457280(30720K) used = 24106552(23541K) committed = 30408704(29696K) max = 30408704(29696K)
22
堆內存的當前內存使用量:init = 31457280(30720K) used = 25155144(24565K) committed = 30408704(29696K) max = 30408704(29696K)
23
堆內存的當前內存使用量:init = 31457280(30720K) used = 26203736(25589K) committed = 30408704(29696K) max = 30408704(29696K)
24
堆內存的當前內存使用量:init = 31457280(30720K) used = 27252328(26613K) committed = 30408704(29696K) max = 30408704(29696K)
25
堆內存的當前內存使用量:init = 31457280(30720K) used = 28300920(27637K) committed = 30408704(29696K) max = 30408704(29696K)
26
[Full GC (Ergonomics) [PSYoungGen: 7326K->7168K(9216K)] [ParOldGen: 20310K->20310K(20480K)] 27637K->27478K(29696K), [Metaspace: 3182K->3182K(1056768K)], 0.0042343 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 7168K->7168K(9216K)] [ParOldGen: 20310K->20296K(20480K)] 27478K->27464K(29696K), [Metaspace: 3182K->3182K(1056768K)], 0.0078410 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to C:\Users\Administrator\Desktop\error_logs ...
Unable to create C:\Users\Administrator\Desktop\error_logs: No such file or directory
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at utils.OneM.<init>(OneM.java:9)
	at utils.JVMDemo.main(JVMDemo.java:37)
Heap
 PSYoungGen      total 9216K, used 7409K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 90% used [0x00000000ff600000,0x00000000ffd3c738,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 20480K, used 20296K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)
  object space 20480K, 99% used [0x00000000fe200000,0x00000000ff5d20b0,0x00000000ff600000)
 Metaspace       used 3214K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 346K, capacity 392K, committed 512K, reserved 1048576K
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
Disconnected from the target VM, address: '127.0.0.1:65390', transport: 'socket'
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=20M; support was removed in 8.0

Process finished with exit code 1

可以看到基本都是1M了

問題2:爲什麼同一程序所花費的時間都不同?

解釋:同樣的程序每次運行的時間不一樣,原因是:

                        1、與操作系統的調度有關

                        2、現在的CPU支持動態調頻

所以每次執行程序運行的時間都不同

以上若有任何疑問或者錯誤歡迎在評論下面指出,我會及時更改

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