JVM基础篇-方法区与运行时常量池
方法区
Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码的存储区或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法,方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但简单的实现可以选择不进行垃圾收集或压缩它。本规范不强制要求方法区的位置或用于管理编译代码的策略。方法区可以是固定大小的,或者可以根据计算的需要来扩展,并且如果不需要更大的方法区,则可以收缩。方法区的内存不需要是连续的。
Java虚拟机实现可以为程序员或用户提供对方法区的初始大小的控制,以及在方法区大小变化的情况下,对最大和最小方法区大小的控制。
如果方法区中的内存无法满足分配请求,Java 虚拟机将抛出一个OutOfMemoryError
参考文档
方法区jdk1.6实现
- 在jdk1.6中方法区在jvm中是通过永久代实现的,包含运行时常量池、类字节码信息、类加载器
方法区jdk1.8实现
- jdk1.8中方法区实现改为元空间实现,而元空间占用的内存不再是JVM内存,而是本地内存,需要注意的是字符串常量池
StringTable
被移至堆内存中
方法区内存溢出
1.8 以前会导致永久代内存溢出
-
异常类型java.lang.OutOfMemoryError: PermGen space
-
设置永久代内存大小:
-XX:MaxPermSize=8m
1.8 之后会导致元空间内存溢出
-
异常类型java.lang.OutOfMemoryError: Metaspace
-
设置元空间内存大小
-XX:MaxMetaspaceSize=8m
示例代码
package com.vmware.stack;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* @apiNote 演示元空间内存溢出
* -XX:MaxMetaspaceSize=8m 设置元空间大小 1.8之后
* -XX:MaxPermSize=8m 设置永久代大小 1.8之前
*/
public class Demo8 extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
Demo8 test = new Demo8();
for (int i = 0; i < 10000; i++,j++) {
//ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
//版本号 public 类名 包名 父类 接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//返回byte[]类二进制字节码
byte[] code = cw.toByteArray();
//执行类加载
test.defineClass("Class" + i, code, 0, code.length);//Class对象
}
}finally {
System.out.println(j);
}
}
}
场景
- spring
- mybatis
- …
这些框架在运行期间会生成大量的代理类,可能会引起内存不足
二进制字节码的组成
- 类基本信息
- 常量池
- 类方法定义,包含了虚拟机指令
package com.vmware.stack;
public class Demo9 {
public static void main(String[] args) {
System.out.println("hello world");
}
}
反编译:javap -v xxx.class
-------------------------------------------------------------------类基本信息
Classfile /home/ubuntu/Desktop/worker/jvm/target/classes/com/vmware/stack/Demo9.class
Last modified Aug 1, 2023; size 552 bytes
SHA-256 checksum b17f11f1b24fba016d8e0cdfb28ccef1cb9c7000e23c67e11b29fb068b9556b0
Compiled from "Demo9.java"
public class com.vmware.stack.Demo9
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // com/vmware/stack/Demo9
super_class: #6 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
--------------------------------------------------------------------常量池
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 // com/vmware/stack/Demo9
#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 Lcom/vmware/stack/Demo9;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 Demo9.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 com/vmware/stack/Demo9
#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 com.vmware.stack.Demo9();
descriptor: ()V
flags: (0x0001) 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 Lcom/vmware/stack/Demo9;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) 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 world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "Demo9.java"
运行时常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址