1、程序计数器
程序计数器可以看作是当前线程所执行的字节码的行号指示器,字节码解析器工作时会通过改变这个计数器的值来读取下一条要执行的字节码指令。
特点:
- 程序计数器是线程私有的
- 在JVM规范中唯一一个没有规定OOM情况的区域
2、虚拟机栈
每个线程运行时所需要的内存,称为虚拟机栈,虚拟机栈是用于存储与维护方法调用时所创建的栈帧,而栈帧则保存了局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表则保存了各种基本的数据类型和对线的引用。
特点:
- 虚拟机栈是线程私有的
- 如果请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常
- 如果栈在扩展时无法申请到足够的内存,将抛出OOM异常
问题辨析:
- 垃圾回收是否涉及栈内存? – 否,方法执行完成后就会被弹出栈
- 方法内的局部变量是否线程安全?
- –按情况讨论,如果方法内得局部变量没有逃离方法得作用范围,那么就是线程安全的
3、本地方法栈
本地方法栈与虚拟机栈所发挥的作用很类似,它们之间的区别就是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用Native方法服务。
4、Java堆
Java堆是被所有线程共享的一块内存空间,它的作用就是用来存放对象实例。Java堆是垃圾收集器管理的主要区域。
堆中的垃圾收集器采用的是分代收集算法,可以分为:新生代和老年代;再细致一点的有Eden区、From区、To区等。
从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer)。
5、方法区
方法区和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JVM规范中把方法区描述为堆的一个逻辑部分。当方法区无法满足内存分配需求时,将抛出OOM异常。
在JDK1.6时方法区定义在永久代,目的是为了垃圾回收器可以像管理Java堆一样管理这部分内存。
目前JDK1.8方法区规划到了本地内存(Native Memory)并且把原本放在永久代的字符串常量池移出到Java堆中。
6、运行时常量池
运行时常量池是方法区一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。v
当类被加载之后,类信息中的常量池中的数据会进入方法区的运行时常量池中存放。
可以通过 【javap -v 类文件路径 】查看类文件中的信息。
可以通过 【-XX:StringTableSize=桶个数】设置StringTable的桶个数来对项目进行优化。
如果需要将有大量重复的字符串存入内存中,那么最好intern()一下,以减少堆内存中实例化对象的个数。
$ javap -v out/production/data-structures-and-arithmetic/github/jasonpang23/leetcode/Hello.class
# 类的基本信息 Start
Classfile /E:/Development/Java Projects/algorithms-and-data-structures/out/production/data-structures-and-arithmetic/github/jasonpang23/le
etcode/Hello.class
Last modified 2020-4-2; size 574 bytes
MD5 checksum 6edae0ad3972f35354583cfc3f325d19
Compiled from "Hello.java"
public class github.jasonpang23.leetcode.Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
# 类的基本信息 End
# 常量池 存储编译生成的各种字面量和符号引用
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // hello world
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // github/jasonpang23/leetcode/Hello
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lgithub/jasonpang23/leetcode/Hello;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 Hello.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 hello world
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 github/jasonpang23/leetcode/Hello
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
# 类中方法的定义
{
# 默认的构造函数
public github.jasonpang23.leetcode.Hello();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lgithub/jasonpang23/leetcode/Hello;
# Main 方法
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
# 获取静态变量,通过#2到常量池中找到对应的符号引用
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
# 加载资源,通过#3到常量池中找到对应的符号引用
3: ldc #3 // String hello world
# 执行虚方法的调用,通过#4到常量池中找到对应的符号引用
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "Hello.java"
注意区分常量池与运行时常量池
常量池,就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等信息。
运行时常量池,当一个类被加载,它的常量池信息会被放入运行时常量池,并把里面的符号地址变为真实地址。
6_1、StringTable
String table又称为String pool,字符串常量池,其存在于堆中(jdk1.7之后改的)。最重要的一点,String table中存储的并不是String类型的对象,存储的而是指向String对象的索引,真实对象还是存储在堆中。
此外String table还存在一个hash表的特性,里面不存在相同的两个字符串。
此外String对象调用intern()方法时,会先在String table中查找是否存在与该对象相同的字符串,若存在直接返回String table中字符串的引用,若不存在则在String table中创建一个与该对象相同的字符串并返回该字符串对象的引用。
public class Hello {
public static void main(String[] args) {
String s1 = "a" ;
String s2 = "b" ;
String s3 = "ab" ;
String s4 = s1 + s2 ;
System.out.println(s3 == s4);
String s6 = "vg" ;
String s7 = new String("v") + new String("g") ;
String s8 = s7.intern() ;
System.out.println(s6 == s7);
System.out.println(s8 == s6);
}
}
public class Hello {
public static void main(String[] args) {
//s1 s2 s3在堆中初始化了3个实例对象,并且StringTable将这三个对象的引用进行存储
/**
* 0: ldc #2 // String a
* 2: astore_1
* 3: ldc #3 // String b
* 5: astore_2
* 6: ldc #4 // String ab
* 8: astore_3
*/
String s1 = "a" ;
String s2 = "b" ;
String s3 = "ab" ;
/** s4 的字节码分析
* 9: new #5 // class java/lang/StringBuilder
* 12: dup
* 13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
* 16: aload_1
* 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
* 20: aload_2
* 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
* 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
* 27: astore 4
* 29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
* 32: aload_3
* 33: aload 4
*/
// 通过s4字节码分析可以发现s4其实是这样的:
// new StringBuilder().append("a").append("b").toString() ;
// append中的a 和 b在StringTable中有该对象的引用所以之间通过该引用获取对象而不需要new对象
// 而这个toString() 方法实际是 : new String("ab") ;
String s4 = s1 + s2 ;
// 通过上面的分析可以很清楚的知道 s3 和 s4其实是在堆中的两个实例
// 而 == 比较的是s3和s4的引用是否相等,所以这里输出的结果是false
System.out.println(s3 == s4); //false
// 通过s5字节码分析可以发现,StringTable中存在ab这个字符串对象的引用,所以也不会new对象
// 而是从StringTable中直接返回 "ab"对象的引用
/** s5 的字节码分析
* 43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
* 46: ldc #4 // String ab
* 48: astore 5
*/
String s5 = "a" + "b"; // 编译时进行优化 s5 = "ab";
// 那么这里 == 比较的时引用 所以结果为 true
System.out.println(s3 == s5); // true
/**
*
* 64: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
* 67: ldc #11 // String vg
* 69: astore 6
* 71: new #5 // class java/lang/StringBuilder
* 74: dup
* 75: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
* 78: new #12 // class java/lang/String
* 81: dup
* 82: ldc #13 // String v
* 84: invokespecial #14 // Method java/lang/String."<init>":(Ljava/lang/String;)V
* 87: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
* 90: new #12 // class java/lang/String
* 93: dup
* 94: ldc #15 // String g
* 96: invokespecial #14 // Method java/lang/String."<init>":(Ljava/lang/String;)V
* 99: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
* 102: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
* 105: astore 7
*
*/
// 在堆中创建字符串实例"vg" 并且存储引用到StringTable 最后将引用返回给s6
String s6 = "vg" ;
// 在堆中创建字符串实例"v" 和 "g" 并且存储引用到StringTable
// 到这里就变得和s4一样:创建StringBuilder进行字符串拼接,然后new一个 "vg"的字符串实例(并没有引用StringTable中的实例)
// 现在堆中存在两个字符串"vg"实例
String s7 = new String("v") + new String("g") ;
String s8 = s7.intern() ; // 如果s7指向的字符串在StringTable中不存在,那么将s7引用的字符串实例放入到StringTable中并返回引用,否则直接返回
System.out.println(s6 == s7); //false
System.out.println(s8 == s6); //true
}
}
输出结果:
false
true
false
true
类文件信息
$ javap -v out/production/data-structures-and-arithmetic/github/jasonpang23/leetcode/Hello.class
Classfile /E:/Development/Java Projects/algorithms-and-data-structures/out/production/data-structures-and-arithmetic/github/jasonpang23/le
etcode/Hello.class
Last modified 2020-4-2; size 1389 bytes
MD5 checksum b7505e618679aa22cc5acd1a4f8d4281
Compiled from "Hello.java"
public class github.jasonpang23.leetcode.Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #18.#45 // java/lang/Object."<init>":()V
#2 = String #46 // a
#3 = String #47 // b
#4 = String #48 // ab
#5 = Class #49 // java/lang/StringBuilder
#6 = Methodref #5.#45 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#50 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#51 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #54.#55 // java/io/PrintStream.println:(Z)V
#11 = String #56 // vg
#12 = Class #57 // java/lang/String
#13 = String #58 // v
#14 = Methodref #12.#59 // java/lang/String."<init>":(Ljava/lang/String;)V
#15 = String #60 // g
#16 = Methodref #12.#61 // java/lang/String.intern:()Ljava/lang/String;
#17 = Class #62 // github/jasonpang23/leetcode/Hello
#18 = Class #63 // java/lang/Object
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 LocalVariableTable
#24 = Utf8 this
#25 = Utf8 Lgithub/jasonpang23/leetcode/Hello;
#26 = Utf8 main
#27 = Utf8 ([Ljava/lang/String;)V
#28 = Utf8 args
#29 = Utf8 [Ljava/lang/String;
#30 = Utf8 s1
#31 = Utf8 Ljava/lang/String;
#32 = Utf8 s2
#33 = Utf8 s3
#34 = Utf8 s4
#35 = Utf8 s5
#36 = Utf8 s6
#37 = Utf8 s7
#38 = Utf8 s8
#39 = Utf8 StackMapTable
#40 = Class #29 // "[Ljava/lang/String;"
#41 = Class #57 // java/lang/String
#42 = Class #64 // java/io/PrintStream
#43 = Utf8 SourceFile
#44 = Utf8 Hello.java
#45 = NameAndType #19:#20 // "<init>":()V
#46 = Utf8 a
#47 = Utf8 b
#48 = Utf8 ab
#49 = Utf8 java/lang/StringBuilder
#50 = NameAndType #65:#66 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = NameAndType #67:#68 // toString:()Ljava/lang/String;
#52 = Class #69 // java/lang/System
#53 = NameAndType #70:#71 // out:Ljava/io/PrintStream;
#54 = Class #64 // java/io/PrintStream
#55 = NameAndType #72:#73 // println:(Z)V
#56 = Utf8 vg
#57 = Utf8 java/lang/String
#58 = Utf8 v
#59 = NameAndType #19:#74 // "<init>":(Ljava/lang/String;)V
#60 = Utf8 g
#61 = NameAndType #75:#68 // intern:()Ljava/lang/String;
#62 = Utf8 github/jasonpang23/leetcode/Hello
#63 = Utf8 java/lang/Object
#64 = Utf8 java/io/PrintStream
#65 = Utf8 append
#66 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#67 = Utf8 toString
#68 = Utf8 ()Ljava/lang/String;
#69 = Utf8 java/lang/System
#70 = Utf8 out
#71 = Utf8 Ljava/io/PrintStream;
#72 = Utf8 println
#73 = Utf8 (Z)V
#74 = Utf8 (Ljava/lang/String;)V
#75 = Utf8 intern
{
public github.jasonpang23.leetcode.Hello();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lgithub/jasonpang23/leetcode/Hello;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=9, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_3
33: aload 4
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
46: ldc #4 // String ab
48: astore 5
50: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
53: aload_3
54: aload 5
56: if_acmpne 63
59: iconst_1
60: goto 64
63: iconst_0
64: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
67: ldc #11 // String vg
69: astore 6
71: new #5 // class java/lang/StringBuilder
74: dup
75: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
78: new #12 // class java/lang/String
81: dup
82: ldc #13 // String v
84: invokespecial #14 // Method java/lang/String."<init>":(Ljava/lang/String;)V
87: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
90: new #12 // class java/lang/String
93: dup
94: ldc #15 // String g
96: invokespecial #14 // Method java/lang/String."<init>":(Ljava/lang/String;)V
99: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
102: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
105: astore 7
107: aload 7
109: invokevirtual #16 // Method java/lang/String.intern:()Ljava/lang/String;
112: astore 8
114: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
117: aload 6
119: aload 7
121: if_acmpne 128
124: iconst_1
125: goto 129
128: iconst_0
129: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
132: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
135: aload 8
137: aload 6
139: if_acmpne 146
142: iconst_1
143: goto 147
146: iconst_0
147: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
150: return
LineNumberTable:
line 15: 0
line 16: 3
line 17: 6
line 39: 9
line 42: 29
line 55: 46
line 57: 50
line 83: 67
line 87: 71
line 88: 107
line 89: 114
line 90: 132
line 96: 150
LocalVariableTable:
Start Length Slot Name Signature
0 151 0 args [Ljava/lang/String;
3 148 1 s1 Ljava/lang/String;
6 145 2 s2 Ljava/lang/String;
9 142 3 s3 Ljava/lang/String;
29 122 4 s4 Ljava/lang/String;
50 101 5 s5 Ljava/lang/String;
71 80 6 s6 Ljava/lang/String;
107 44 7 s7 Ljava/lang/String;
114 37 8 s8 Ljava/lang/String;
StackMapTable: number_of_entries = 8
frame_type = 255 /* full_frame */
offset_delta = 42
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 255 /* full_frame */
offset_delta = 63
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 80 /* same_locals_1_stack_item */
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/
String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "Hello.java"
7、直接内存
直接内存不属于JVM运行时数据区的一部分,不受JVM内存回收管理,主要使用在NIO中作为一种本地内存和Java堆内存的中间缓存区。底层使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法
ByteBuffer的实现类内存,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandle线程通过Cleaner的clean方法调用freeMeomery来释放直接内存