深入浅出jvm

jvm内存模型

  主要包含类加载器、jvm内存、字节码执行引擎、GC;

 

 

 类加载器

  类加载器主要包含:应用程序加载器、扩展类加载器、启动类加载器。

    启动类加载器:主要进行加载java核心类,例如:rt.jar包下的类。

    扩展类加载器:主要进行加载java中ext包下的类。

    应用程序类加载器:主要加载我们自己写的java类。

  类加载机制:双亲委派机制和全盘负责委托机制。

    双签委派机制:当我们自己的java类加载的时候会查看是否有父级加载器,如果有委托父级,直道由启动类加载器加载,启动类加载器加载后,加载了核心类发现加载不了我们的类,所以又返回给子级加载,直到由应用程序类加载器加载。

    全盘负责委托机制:当一个加载器加载的时候如果没有显性的让另一个加载器加载,则当前的加载器都会全部加载。

  如果想打破双亲委派机制,也相对来说简单一些,我们可以查看底层源码:

 1 protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             Class<?> c = findLoadedClass(name);
 7             if (c == null) {
 8                 long t0 = System.nanoTime();
 9                 try {
10                     if (parent != null) {
11                         c = parent.loadClass(name, false);
12                     } else {
13                         c = findBootstrapClassOrNull(name);
14                     }
15                 } catch (ClassNotFoundException e) {
16                     // ClassNotFoundException thrown if class not found
17                     // from the non-null parent class loader
18                 }
19 
20                 if (c == null) {
21                     // If still not found, then invoke findClass in order
22                     // to find the class.
23                     long t1 = System.nanoTime();
24                     c = findClass(name);
25 
26                     // this is the defining class loader; record the stats
27                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
28                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
29                     sun.misc.PerfCounter.getFindClasses().increment();
30                 }
31             }
32             if (resolve) {
33                 resolveClass(c);
34             }
35             return c;
36         }
37     }

    我们可以看见有一个代码在查找parent.loadClass,所以我们需要重写一下loadclass、findclass方法即可。

jvm内存

 

 

 

   我们理解一下操作数栈与局部变量表,有一个简单demo:

 1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5 
 6 package com.tuling;
 7 
 8 public class Main {
 9     public Main() {
10     }
11 
12     public int add() {
13         int a = 1;
14         int b = 2;
15         int c = (a + b) * 10;
16         return c;
17     }
18 
19     public static void main(String[] args) {
20         Main main = new Main();
21         main.add();
22         System.out.println("aaa");
23     }
24 }

  我们进入main.class进行javap -c反编译。然后会得到这样一些代码:

  1 Classfile /E:/ЙљПЁгъ/01java-vip/tuling-vip-spring/springannopriciple01/target/test-classes/com/tuling/Main.class
  2   Last modified 2019-9-8; size 714 bytes
  3   MD5 checksum 316510b260c590e9dd45038da671e84e
  4   Compiled from "Main.java"
  5 public class com.tuling.Main
  6   minor version: 0
  7   major version: 52
  8   flags: ACC_PUBLIC, ACC_SUPER
  9 Constant pool:
 10    #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
 11    #2 = Class              #29            // com/tuling/Main
 12    #3 = Methodref          #2.#28         // com/tuling/Main."<init>":()V
 13    #4 = Methodref          #2.#30         // com/tuling/Main.add:()I
 14    #5 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
 15    #6 = String             #33            // aaa
 16    #7 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
 17    #8 = Class              #36            // java/lang/Object
 18    #9 = Utf8               <init>
 19   #10 = Utf8               ()V
 20   #11 = Utf8               Code
 21   #12 = Utf8               LineNumberTable
 22   #13 = Utf8               LocalVariableTable
 23   #14 = Utf8               this
 24   #15 = Utf8               Lcom/tuling/Main;
 25   #16 = Utf8               add
 26   #17 = Utf8               ()I
 27   #18 = Utf8               a
 28   #19 = Utf8               I
 29   #20 = Utf8               b
 30   #21 = Utf8               c
 31   #22 = Utf8               main
 32   #23 = Utf8               ([Ljava/lang/String;)V
 33   #24 = Utf8               args
 34   #25 = Utf8               [Ljava/lang/String;
 35   #26 = Utf8               SourceFile
 36   #27 = Utf8               Main.java
 37   #28 = NameAndType        #9:#10         // "<init>":()V
 38   #29 = Utf8               com/tuling/Main
 39   #30 = NameAndType        #16:#17        // add:()I
 40   #31 = Class              #37            // java/lang/System
 41   #32 = NameAndType        #38:#39        // out:Ljava/io/PrintStream;
 42   #33 = Utf8               aaa
 43   #34 = Class              #40            // java/io/PrintStream
 44   #35 = NameAndType        #41:#42        // println:(Ljava/lang/String;)V
 45   #36 = Utf8               java/lang/Object
 46   #37 = Utf8               java/lang/System
 47   #38 = Utf8               out
 48   #39 = Utf8               Ljava/io/PrintStream;
 49   #40 = Utf8               java/io/PrintStream
 50   #41 = Utf8               println
 51   #42 = Utf8               (Ljava/lang/String;)V
 52 {
 53   public com.tuling.Main();
 54     descriptor: ()V
 55     flags: ACC_PUBLIC
 56     Code:
 57       stack=1, locals=1, args_size=1
 58          0: aload_0
 59          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 60          4: return
 61       LineNumberTable:
 62         line 10: 0
 63       LocalVariableTable:
 64         Start  Length  Slot  Name   Signature
 65             0       5     0  this   Lcom/tuling/Main;
 66 
 67   public int add();
 68     descriptor: ()I
 69     flags: ACC_PUBLIC
 70     Code:
 71       stack=2, locals=4, args_size=1
 72          0: iconst_1
 73          1: istore_1
 74          2: iconst_2
 75          3: istore_2
 76          4: iload_1
 77          5: iload_2
 78          6: iadd
 79          7: bipush        10
 80          9: imul
 81         10: istore_3
 82         11: iload_3
 83         12: ireturn
 84       LineNumberTable:
 85         line 13: 0
 86         line 14: 2
 87         line 15: 4
 88         line 16: 11
 89       LocalVariableTable:
 90         Start  Length  Slot  Name   Signature
 91             0      13     0  this   Lcom/tuling/Main;
 92             2      11     1     a   I
 93             4       9     2     b   I
 94            11       2     3     c   I
 95 
 96   public static void main(java.lang.String[]);
 97     descriptor: ([Ljava/lang/String;)V
 98     flags: ACC_PUBLIC, ACC_STATIC
 99     Code:
100       stack=2, locals=2, args_size=1
101          0: new           #2                  // class com/tuling/Main
102          3: dup
103          4: invokespecial #3                  // Method "<init>":()V
104          7: astore_1
105          8: aload_1
106          9: invokevirtual #4                  // Method add:()I
107         12: pop
108         13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
109         16: ldc           #6                  // String aaa
110         18: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
111         21: return
112       LineNumberTable:
113         line 20: 0
114         line 21: 8
115         line 22: 13
116         line 23: 21
117       LocalVariableTable:
118         Start  Length  Slot  Name   Signature
119             0      22     0  args   [Ljava/lang/String;
120             8      14     1  main   Lcom/tuling/Main;
121 }
122 SourceFile: "Main.java"

  我们只看add方法的代码:需要用到jvm指令集,自己可以上网搜具体的代码;

  iconst_1:将int型1推送至栈顶

  istore_1:将栈顶int型数值存入第1个本地变量

  这里的栈就是操作数栈。

GC算法:标记-清除、标记-整理算法、复制算法、分代算法

  标记-清除:算法分为标记清除阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法。

  标记-整理:标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。

 

  复制:它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

为什么要GC呢?

  我们来看一下堆内存结构。

 

 

 如果自己想看自己运行的类的堆信息,可以利用jps,查找到自己类的id,然后jmap -heap id,就可以查看当前堆的信息,例如:

 1 Attaching to process ID 7964, please wait...
 2 Debugger attached successfully.
 3 Server compiler detected.
 4 JVM version is 25.73-b02
 5 
 6 using thread-local object allocation.
 7 Parallel GC with 4 thread(s)
 8 
 9 Heap Configuration:
10    MinHeapFreeRatio         = 0
11    MaxHeapFreeRatio         = 100
12    MaxHeapSize              = 2128609280 (2030.0MB)
13    NewSize                  = 44564480 (42.5MB)
14    MaxNewSize               = 709361664 (676.5MB)
15    OldSize                  = 89653248 (85.5MB)
16    NewRatio                 = 2
17    SurvivorRatio            = 8
18    MetaspaceSize            = 21807104 (20.796875MB)
19    CompressedClassSpaceSize = 1073741824 (1024.0MB)
20    MaxMetaspaceSize         = 17592186044415 MB
21    G1HeapRegionSize         = 0 (0.0MB)
22 
23 Heap Usage:
24 PS Young Generation
25 Eden Space:
26    capacity = 34078720 (32.5MB)
27    used     = 4771760 (4.5507049560546875MB)
28    free     = 29306960 (27.949295043945312MB)
29    14.002169095552885% used
30 From Space:
31    capacity = 5242880 (5.0MB)
32    used     = 0 (0.0MB)
33    free     = 5242880 (5.0MB)
34    0.0% used
35 To Space:
36    capacity = 5242880 (5.0MB)
37    used     = 0 (0.0MB)
38    free     = 5242880 (5.0MB)
39    0.0% used
40 PS Old Generation
41    capacity = 89653248 (85.5MB)
42    used     = 0 (0.0MB)
43    free     = 89653248 (85.5MB)
44    0.0% used

只要old区没有use满就不会触发full gc,年轻代存满之后触发young gc,并不会STW。现在我们看一下垃圾回收器有哪些

垃圾回收器

   种类:Serial收集器、ParNew收集器、Parallel Scavenge收集器、Serial Old收集器、Parallel Old收集器、CMS收集器、 G1收集器

Serial收集器:新生代采用复制算法,老年代采用标记-整理算法,利用单线程,直接在回收的过程中中断所有程序线程。

 

 

 

 ParNew收集器:新生代采用复制算法,老年代采用标记-整理算法。利用多线程处理,单指GC线程。应用程序还是中断的

 

 

 Parallel Scavenge收集器:新生代采用复制算法,老年代采用标记-整理算法。和ParNew收集器类似。

 

 

Serial Old收集器、Parallel Old收集器:是Serial收集器、Parallel Scavenge收集器的老年代版本。就相当于将老年代的算法单独提出来与其他收集器组合使用。

CMS收集器:开启GC时,直接将root的相连的节点拿到就可以,所以STW时间短。然后和应用程序争抢CPU,找到还能利用的对象进行标记,然后再次STW来标记并发的时候产生的新对象,最后清理没有标记的空间内存。

 

 

 

 

 G1收集器:G1Java堆划分为多个大小相等的独立区域(Region),虽保留新生代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。分配大对象(直接进Humongous区,专门存放短期巨型对象,不用直接进老年代,避免Full GC的大量开销)不会因为无法找到连续空间而提前触发下一次GC

 

 

 和上面的逻辑差不多,不过有一个过程是筛选回收,该收集器用户可以指定回收时间,所以jvm会判断回收成本并执行回收计划,来优先回收哪些对象

 

 ps:关注一下本人公众号,每周都有新更新哦!

posted @ 2019-09-10 13:55  努力的小雨  阅读(574)  评论(0编辑  收藏  举报