深入浅出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收集器: G1将Java堆划分为多个大小相等的独立区域(Region),虽保留新生代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。分配大对象(直接进Humongous区,专门存放短期巨型对象,不用直接进老年代,避免Full GC的大量开销)不会因为无法找到连续空间而提前触发下一次GC
和上面的逻辑差不多,不过有一个过程是筛选回收,该收集器用户可以指定回收时间,所以jvm会判断回收成本并执行回收计划,来优先回收哪些对象
- 点赞
- 收藏
- 关注作者
评论(0)