JVM内存结构笔记03-方法区
文章目录
- 方法区
- 1.定义
- 2.组成
- 方法区与永久代和元空间的关系
- 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
- 3.方法区常用参数
- 4.运行时常量池
- 查看class文件
方法区
1.定义
- 方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。
- 当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。
- 方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
注意:在不同的虚拟机实现上,方法区的实现是不同的。
2.组成
- 1.8之前的版本中,方法区用永久代来进行实现,永久代中包含类信息、运行时常量池。
- 1.8之后永久代被废弃,变为在元空间中,元空间在本地内存(操作系统的内存)中。
方法区与永久代和元空间的关系
方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。
为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
在《深入理解 Java 虚拟机》第三版中解释到:
原因:
- 整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整(也就是受到 JVM 内存的限制),而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
- 元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
- 在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
3.方法区常用参数
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是本地内存。下面是一些常用参数:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
当元空间溢出时会得到如下错误:java.lang.OutOfMemoryError: MetaSpace
可以使用 -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。
-XX:MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
4.运行时常量池
运行时常量池是方法区的一部分。也就是说,当JVM加载一个类文件时,会将该类的常量池信息(常量池表)加载到方法区内的运行时常量池(内存)中。
在Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table) 。
字面量:字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。字面量包括整数、浮点数和字符串字面量。如:
String str = "hello";
final int num = 10;
其中 “hello” 和 10 就属于字面量,会存储在运行时常量池中。
符号引用:常见的符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法符号。
《深入理解 Java 虚拟机》第三版 对符号引用和直接引用的解释如下:
- 常量池表会在类加载后存放到方法区的运行时常量池中。
- 运行时常量池的功能类似于传统编程语言的符号表,尽管它包含了比典型符号表更广泛的数据。
- 既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。
查看class文件
代码
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {public static void main(String[] args) {System.out.println("hello world");}
}
通过反编译查看以上代码的.class文件
PS D:\Test\jvm\out\production\jvm\cn\qf> javap -v HelloWorld.class
Classfile /D:/Test/jvm/out/production/jvm/cn/qf/HelloWorld.classLast modified 2024-12-6; size 567 bytesMD5 checksum 8efebdac91aa496515fa1c161184e354Compiled from "HelloWorld.java"
public class cn.qf.HelloWorldminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
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 // cn/qf//HelloWorld#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 Lcn/qf//HelloWorld;#14 = Utf8 main#15 = Utf8 ([Ljava/lang/String;)V#16 = Utf8 args#17 = Utf8 [Ljava/lang/String;#18 = Utf8 SourceFile#19 = Utf8 HelloWorld.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 cn/qf/HelloWorld#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 cn.qf.HelloWorld();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcn/qf/HelloWorld;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #3 // String hello world5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 args [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"
翻到最下面
#2、#3、#4在Constant pool中查看定义
如#3在Constant pool对应#23
Constant pool:...#3 = String #23 // hello world...#23 = Utf8 hello world...
常量池的作用:给指令提供常量符号,根据常量符合以查表的方式去找到他们。
相关文章:
JVM内存结构笔记01-运行时数据区域
JVM内存结构笔记02-堆
JVM内存结构笔记03-方法区
JVM内存结构笔记04-字符串常量池