2020-11-02 | Java | Unlock

JVM-day01

内存结构

1.程序计数器:

JVM-day01-01.png

1.1 定义

Program Counter Register 程序计数器(寄存器)

  1. 作用是记住下一条jvm指令的执行地址
  2. 特点:
  3. 1 线程私有
  4. 2 不会存在内存溢出

1.2 作用

JVM-day01-02.png

2.虚拟机栈:

JVM-day01-03.png

2.1 定义

Java Virtual Machine Stacks (Java虚拟机栈)

  1. 每个线程运行时所需要的内存,称为虚拟机栈
  2. 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  3. 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

问题思考:

  1. 垃圾回收是否涉及栈内存?

    不需要,每次方法调用产生栈帧内存,方法执行完成后,都会被弹出栈,被自动回收掉。

  2. 栈内存分配越大越好吗?

    -Xss size: 默认1024KB。栈内存越大,线程数越少。

  3. 方法内的局部变量是否线程安全?

    3.1 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的

    3.2 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

2.2 栈内存溢出 StackOverflowError

  1. 栈帧过多导致栈内存溢出
  2. 栈帧过大导致栈内存溢出

2.3 线程运行诊断

  1. CPU占用过多
  2. 程序运行很长时间没有结果

定位:

  1. 用top定位哪个进程对CPU的占用过高
  2. ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的CPU占用过高)
  3. jstack 进程id (可以根据线程id找到有问题的线程,10进制的进程id转成16进制,进一步定位到问题代码的源码行号)

3.本地方法栈

JVM-day01-04.png

4.堆

JVM-day01-05.png

4.1 定义

Heap 堆 : 通过new关键字,创建对象都会使用堆内存

特点:

  1. 线程共享,堆中对象都需要考虑线程安全的问题
  2. 有垃圾回收机制

4.2 堆内存溢出 OutofMemoryError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true){
list.add(a);
a = a + a;
i++;
}
}catch (Throwable throwable){
throwable.printStackTrace();
System.out.println(i);
}
}

运行结果:

1
2
3
4
5
6
7
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at pub.iyu.se.jvmday.day01.TestDemo01.main(TestDemo01.java:20)
26

4.3 堆内存诊断

  1. jps工具:查看当前系统中有哪些java进程
  2. jmap工具:查看堆内存占用情况 jmap -heap 进程id
  3. jconsole工具:图形界面,多功能的监测工具,可以连续监测
  4. jvisualvm工具:更强大的图形界面
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws InterruptedException {
System.out.println("1...");
Thread.sleep(30000);
byte[] array = new byte[1024*1024*10];
System.out.println("2...");
Thread.sleep(30000);
array = null;
System.gc();
System.out.println("3...");
Thread.sleep(1000000L);
}

运行方法后,在控制台输入

1
jps

显示如下结果:

1
2
3
12140 Jps
14380 Launcher
7644 TestDemo02

之后当输出如下内容时:

1
1...

在控制台输入

1
jmap -heap 7644

这时候可以看到显示的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Heap Usage:
PS Young Generation
Eden Space:
capacity = 67108864 (64.0MB)
used = 6718152 (6.406929016113281MB)
free = 60390712 (57.59307098388672MB)
10.010826587677002% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 179306496 (171.0MB)
used = 0 (0.0MB)
free = 179306496 (171.0MB)
0.0% used

当输出如下内容时:

1
2...

在控制台输入

1
jmap -heap 7644

这时候可以看到显示的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Heap Usage:
PS Young Generation
Eden Space:
capacity = 67108864 (64.0MB)
used = 17203928 (16.406944274902344MB)
free = 49904936 (47.593055725097656MB)
25.635850429534912% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 179306496 (171.0MB)
used = 0 (0.0MB)
free = 179306496 (171.0MB)
0.0% used

当输出如下内容时:

1
3...

在控制台输入

1
jmap -heap 7644

这时候可以看到显示的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Heap Usage:
PS Young Generation
Eden Space:
capacity = 67108864 (64.0MB)
used = 1342200 (1.2800216674804688MB)
free = 65766664 (62.71997833251953MB)
2.0000338554382324% used
From Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
To Space:
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation
capacity = 179306496 (171.0MB)
used = 1345272 (1.2829513549804688MB)
free = 177961224 (169.71704864501953MB)
0.7502639502809759% used

对照上面的代码,可以看到堆中内存的占用和回收情况。

重新运行代码,并在终端上输入

1
jconsole

这时候,会打开jconsole的图形界面,观察一段时间,可以看到如下的结果:

JVM-day01-06.png

问题:垃圾回收后,内存占用仍然很高

5.方法区

JVM-day01-07.png

5.1 定义

JVM-day01-08.png

参考资料:JVM规范-方法区定义

5.2 组成

JVM-day01-09.png

5.3 方法区内存溢出

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//继承ClassLoader 可以用来加载类的二进制字节码
public class TestDemo03 extends ClassLoader{
public static void main(String[] args) {
int j = 0;
try {
TestDemo03 testDemo03 = new TestDemo03();
for (int i = 0; i < 10000; i++,j++) {
//ClassWriter 作用是生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
// visit方法 (版本号,方法修饰符public,类名,包名,类的父类,实现的接口名称)
classWriter.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
byte[] code = classWriter.toByteArray();
testDemo03.defineClass("Class"+i,code,0,code.length);
}
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(j);
}
}
}
  1. java8以前会导致永久代内存溢出

演示永久代内存溢出java.lang.OutofMemoryError: PermGen space

1
-XX:MaxPermSize=10m
  1. java8以后会导致元空间内存溢出

由于java8后元空间在物理内存中,这里演示元空间内存溢出java.lang.OutofMemoryError: Metaspace

1
-XX:MaxMetaspaceSize=10m

结果如下:

1
2
3
4
5
6
3331
Exception in thread "main" java.lang.OutOfMemoryError: Compressed class space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
at pub.iyu.se.jvmday.day01.TestDemo03.main(TestDemo03.java:25)

实际场景

动态产生class并加载的场景很多:spring,mybatis…

cglib

5.4 运行时常量池

  1. 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息

  2. 运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

1
2
3
4
5
6
//二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class TestDemo04 {
public static void main(String[] args) {
System.out.println("hello niko");
}
}

使用javap指令(javap能对给定的class文件提供的字节代码进行反编译)

1
javap -v TestDemo04.class

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Classfile /E:/TestJava/target/classes/pub/iyu/se/jvmday/day01/TestDemo04.class
Last modified 2020-11-02; size 610 bytes
MD5 checksum 98f958bafd4c5329e4a8734cac14cf8a
Compiled from "TestDemo04.java"
public class pub.iyu.se.jvmday.day01.TestDemo04
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // hello niko
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // pub/iyu/se/jvmday/day01/TestDemo04
#6 = Class #28 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lpub/iyu/se/jvmday/day01/TestDemo04;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 TestDemo04.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 hello niko
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 pub/iyu/se/jvmday/day01/TestDemo04
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
{
public pub.iyu.se.jvmday.day01.TestDemo04();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpub/iyu/se/jvmday/day01/TestDemo04;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello niko
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 13: 0
line 14: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "TestDemo04.java"

5.5 String Table

思考如下代码的打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();

System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);

String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();

System.out.println(x1 == x2);

运行结果如下:

1
2
3
4
false
true
true
false

1 String Table 特性

  1. 常量池中的字符串仅是符号,第一次用到时才变为对象

  2. 利用串池的机制,来避免重复创建字符串对象

  3. 字符串变量拼接的原理是StringBuilder(1.8)

  4. 字符串常量拼接的原理是编译期优化

  5. 可以使用intern方法,主动将串池中还没有的字符串对象放入串池

    1. java8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
    2. java6将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份放入串池,会把串池中的对象返回

2 String Table 位置

  1. java6 String Table在永久代PermGen中
  2. java8 String Table在堆Heap中

3 String Table 垃圾回收

4 String Table 性能调优

  1. 调整 -XX:StringTableSize=桶个数
  2. 考虑将字符串对象是否入池(串池)

6.直接内存

6.1 定义

Direct Memory 属于操作系统内存

  1. 常见于NIO操作时,用于数据缓冲区
  2. 分配回收成本较高,但读写性能高
  3. 不受JVM内存回收管理

JVM-day01-10.png

6.2 分配和回收原理

  1. 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法
  2. ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

评论加载中