jvm方法区学习
- 方法区定义
- 运行时常量池和常量池
- 常量池
- 运行时常量池
方法区定义
方法区和堆差不多,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,就会抛出内存溢出错误:Java.lang.OutOfMemoryErro:MetaSpace 或Java.lang.OutOfMemoryErro:PermGen space。
在JDK1.6和以前的版本中方法区是在永久代 PermGen space
中,在JDK1.8中方法区是在元空间MetaSpace
的本地内存里
运行时常量池和常量池
常量池
先看一个简单的案例
// 一个类的二进制字节码包括了(类的基本信息,常量池,类的方法定义,虚拟机指令)
public class Test2 {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}
把它编译成class文件后反编译回来(反编译指令: javap -v ***.class)
Last modified 2021-2-21; size 573 bytes
MD5 checksum 0e47a72370577c678966d2f846bb6fec
Compiled from "Test2.java"
public class com.cdyl.api.Test2
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 // HelloWorld
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // com/cdyl/api/Test2
#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 Lcom/cdyl/api/Test2;
#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 Test2.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 HelloWorld
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 com/cdyl/api/Test2
#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 com.cdyl.api.Test2();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/cdyl/api/Test2;
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 HelloWorld
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "Test2.java"
Constant pool就是类的常量池,常量池的数据如何使用就得看虚拟机指令了
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 HelloWorld
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
第一句getstatic #2
获取一个静态变量指向了 #2
地址,我们去常量池中去查找#2
发现又指向了#22
和#23
#22的class 说明指向了#29的类, #23的NameAndType是说明找一个为#30的变量和#31的类型
这一串指令最终就拼成了我们写的System.out
第二句ldc #3
加载一个为#3的变量,我们去常量池中找#3
发现#3
->#24
string 说明是个字符串
最终找到#24
是一个字符串的 HelloWorld
继续往下的指令invokevirtual #4
进行一次方法调用
#4->#26#25
#25说明了调用的是哪个类, #26说明方法名和类型
#26-> #33#34
,找到了println方法,这时我们的一整句代码就齐了
System.out.println("HelloWorld");
最后一句指令 return
说明方法执行完毕
总结: 常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
运行时常量池
常量池是一个*.class文件中的,当该类被加载,它的常量池信息就会被放入运行时常量池,并把里面的符号地址(#1,#2等)变为真实的内存地址