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

創(chuàng)建了一個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內(nèi)存分配測試
         * 堆內(nèi)存30M|Yong10M(Eden8M,from1M,to1M)|Old20M
         *初始堆大小:-Xms30m 最大堆大小:-Xmx30m
         * 讓虛擬機在發(fā)生內(nèi)存溢出時 Dump 出當前的內(nèi)存堆轉儲快照,以便分析用:-XX:+HeapDumpOnOutOfMemoryError
         * 打印 GC 信息:-XX:+PrintGCDetails
         * 永久代(方法區(qū))的最大值:-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("初始化堆的當前內(nèi)存使用量:"+memoryMXBean.getHeapMemoryUsage());
            System.out.println("初始化非堆內(nèi)存的當前內(nèi)存使用量:"+memoryMXBean.getNonHeapMemoryUsage());
            while (true){
                list.add(new JVMDemo());
                try {
                    Thread.sleep(40);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    //            System.out.println("非堆內(nèi)存的當前內(nèi)存使用量:"+memoryMXBean.getNonHeapMemoryUsage());
                System.out.println("堆內(nèi)存的當前內(nèi)存使用量:"+memoryMXBean.getHeapMemoryUsage());
                System.out.println(i++);
            }
        }
    }

配置JVM:

 

控制臺輸出:

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

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

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

進入循環(huán),可以看到增加了1190k

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

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

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

增加了1M

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

增加了1188k

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

增加了1188k,現(xiàn)在已經(jīng)7649k了,離8m(8192k)只有543k的內(nèi)存空間了

[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。

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

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

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

增加了1M

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

增加了1M

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

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

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

增加了1M
堆內(nèi)存的當前內(nèi)存使用量: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。
堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 14902416(14553K) committed = 30408704(29696K) max = 30408704(29696K)
13

從13296k增加了1257k到14553k

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

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

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

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

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

增加了1M
堆內(nèi)存的當前內(nèi)存使用量: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,觸發(fā)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


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

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

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

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

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


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

增加了1M
堆內(nèi)存的當前內(nèi)存使用量: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:進程執(zhí)行用戶態(tài)代碼(核心之外)所使用的時間。這是執(zhí)行此進程所使用的實際 CPU 時間,其他進程和此進程阻塞的時間并不包括在內(nèi)。在垃圾收集的情況下,表示 GC 線程執(zhí)行所使用的 CPU 總時間。
    sys:進程在內(nèi)核態(tài)消耗的 CPU 時間,即在內(nèi)核執(zhí)行系統(tǒng)調(diào)用或等待系統(tǒng)事件所使用的 CPU 時間。
    user + sys 時間告訴我們程序執(zhí)行實際使用的 CPU 時間。注意這里指所有的 CPU,因此如果在進程里有多個線程的話,這個時間可能會超過 real 所表示的時鐘時間。如果出現(xiàn)user + sys 時間的和比 real 時間要大,這主要是因為日志時間是從 JVM 中獲得的,而這個 JVM 在多核的處理器上被配置了多個 GC 線程,由于多個線程并行地執(zhí)行 GC,因此整個 GC 工作被這些線程共享,最終導致實際的時鐘時間(real)小于總的 CPU 時間(user + sys)。

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

所以可以根據(jù)使用(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空參構造器創(chuàng)建對象。底層創(chuàng)建一個長度為10的數(shù)組,當我們向數(shù)組中添加11個元素時,底層會進行擴容,擴容為原來的1.5倍(創(chuàng)建一個新的數(shù)組,長度為原數(shù)組長度的1.5倍,將原數(shù)組復制到新數(shù)組中)。改造ArrayList,驗證想法:

運行之后,控制臺打印:

     
    初始化堆的當前內(nèi)存使用量:init = 31457280(30720K) used = 3089664(3017K) committed = 30408704(29696K) max = 30408704(29696K)
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 4138256(4041K) committed = 30408704(29696K) max = 30408704(29696K)
    1
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 5186848(5065K) committed = 30408704(29696K) max = 30408704(29696K)
    2
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 6235440(6089K) committed = 30408704(29696K) max = 30408704(29696K)
    3
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 7284032(7113K) committed = 30408704(29696K) max = 30408704(29696K)
    4
    堆內(nèi)存的當前內(nèi)存使用量: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]
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 7613624(7435K) committed = 30408704(29696K) max = 30408704(29696K)
    6
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 8913912(8704K) committed = 30408704(29696K) max = 30408704(29696K)
    7
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 9962504(9729K) committed = 30408704(29696K) max = 30408704(29696K)
    8
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 11011096(10753K) committed = 30408704(29696K) max = 30408704(29696K)
    9
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 12059688(11777K) committed = 30408704(29696K) max = 30408704(29696K)
    10
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 13108280(12801K) committed = 30408704(29696K) max = 30408704(29696K)
    11
    堆內(nèi)存的當前內(nèi)存使用量: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]
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 14855728(14507K) committed = 30408704(29696K) max = 30408704(29696K)
    13
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 15904320(15531K) committed = 30408704(29696K) max = 30408704(29696K)
    14
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 16952912(16555K) committed = 30408704(29696K) max = 30408704(29696K)
    15
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 18085408(17661K) committed = 30408704(29696K) max = 30408704(29696K)
    16
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 19134000(18685K) committed = 30408704(29696K) max = 30408704(29696K)
    17
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 20182592(19709K) committed = 30408704(29696K) max = 30408704(29696K)
    18
    堆內(nèi)存的當前內(nèi)存使用量: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]
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 22009368(21493K) committed = 30408704(29696K) max = 30408704(29696K)
    20
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 23057960(22517K) committed = 30408704(29696K) max = 30408704(29696K)
    21
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 24106552(23541K) committed = 30408704(29696K) max = 30408704(29696K)
    22
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 25155144(24565K) committed = 30408704(29696K) max = 30408704(29696K)
    23
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 26203736(25589K) committed = 30408704(29696K) max = 30408704(29696K)
    24
    堆內(nèi)存的當前內(nèi)存使用量:init = 31457280(30720K) used = 27252328(26613K) committed = 30408704(29696K) max = 30408704(29696K)
    25
    堆內(nèi)存的當前內(nèi)存使用量: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、與操作系統(tǒng)的調(diào)度有關

                        2、現(xiàn)在的CPU支持動態(tài)調(diào)頻

所以每次執(zhí)行程序運行的時間都不同

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