继上文:深入栈帧
方法区(Method Area)是什么?
方法区(Method Area)属于jvm运行时数据区的一块,也是跟堆一样被所有线程共享,并且方法区在物理层面是属于堆中的一块。方法区在JVM启动的时候被创建。
特性:
不连续;
JVM启动时被创建;
会抛出内存溢出;
方法区、栈、堆之间的关系?
方法区内部有什么 ?
方法区中主要为:类信息和运行时常量池;
类信息包含:类型信息、域信息、方法信息
运行时常量池(Runtime Constant Pool):运行时常量池、JIT代码缓存
类型信息
对每个加载的类型(类Class、接口 interface、枚举enum、注解 annotation),JM必须在方法区中存储以下类型信息:
这个类型的完整有效名称(全名 = 包名.类名)
这个类型直接父类的完整有效名(对于 interface或是java.lang. Object,都没有父类)
这个类型的修饰符( public, abstract,final的某个子集)
这个类型直接接口的一个有序列表
域信息
域信息,即为类的属性,成员变量
JVM必须在方法区中保存类所有的成员变量相关信息及声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(pυblic、private、protected、static、final、volatile、transient的某个子集)
方法信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
方法名称方法的返回类型(或void)
方法参数的数量和类型(按顺序)
方法的修饰符public、private、protected、static、final、synchronized、native,、abstract的一个子集
方法的字节码bytecodes、操作数栈、局部变量表及大小( abstract和native方法除外)
异常表( abstract和 native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
non-fianl的类变量
静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分
类变量被类的所有实例共享,即使没有实例时你也可以访问它
常量池
存放编译期间生成的各种字面量与符号引用,包含内容有:数量值、字符串值、类引用、字段引用、方法引用
运行时常量池
当类加载后会将常量池表的内容加载到运行时常量池中,所以常量池表在运行时的表现形式。
参考:https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.5.5
//javap命令
javap -v JVMDebug.class
//类信息
Classfile /D:/ideaWorkSpace/jdk8/src/main/java/com/jvm/JVMDebug.class
Last modified 2021-4-6; size 611 bytes
MD5 checksum 868a6b188641f869d297950f67c1b34f
Compiled from "JVMDebug.java"
public class com.jvm.JVMDebug
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER // 类权限修饰符
//常量池
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = Integer 100000
#3 = Methodref #4.#25 // com/jvm/JVMDebug.doubleValue:(I)I
#4 = Class #26 // com/jvm/JVMDebug
#5 = Methodref #4.#27 // com/jvm/JVMDebug.calcSum:()J
#6 = Class #28 // java/lang/Object
#7 = Utf8 NUM
#8 = Utf8 I
#9 = Utf8 ConstantValue
#10 = Integer 15000
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 doubleValue
#16 = Utf8 (I)I
#17 = Utf8 StackMapTable
#18 = Utf8 calcSum
#19 = Utf8 ()J
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 SourceFile
#23 = Utf8 JVMDebug.java
#24 = NameAndType #11:#12 // "<init>":()V
#25 = NameAndType #15:#16 // doubleValue:(I)I
#26 = Utf8 com/jvm/JVMDebug
#27 = NameAndType #18:#19 // calcSum:()J
#28 = Utf8 java/lang/Object
//方法信息被加载到方法区
{
//域名称
public static final int NUM;
//域类型
descriptor: I
//域权限
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 15000
public com.jvm.JVMDebug();
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 8: 0
public static int doubleValue(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ldc #2 // int 100000
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: iload_0
15: iconst_2
16: imul
17: ireturn
LineNumberTable:
line 13: 0
line 14: 14
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 11
public static long calcSum();
descriptor: ()J
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=0
0: lconst_0
1: lstore_0
2: iconst_1
3: istore_2
4: iload_2
5: bipush 100
7: if_icmpgt 24
10: lload_0
11: iload_2
12: invokestatic #3 // Method doubleValue:(I)I
15: i2l
16: ladd
17: lstore_0
18: iinc 2, 1
21: goto 4
24: lload_0
25: lreturn
LineNumberTable:
line 18: 0
line 19: 2
line 20: 10
line 19: 18
line 22: 24
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 4
locals = [ long, int ]
frame_type = 250 /* chop */
offset_delta = 19
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: sipush 15000
6: if_icmpge 19
9: invokestatic #5 // Method calcSum:()J
12: pop2
13: iinc 1, 1
16: goto 2
19: return
LineNumberTable:
line 26: 0
line 27: 9
line 26: 13
line 30: 19
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 2
locals = [ int ]
frame_type = 250 /* chop */
offset_delta = 16
}
SourceFile: "JVMDebug.java"
注意:
jdk8之前方法实现是永久代,jdk8开始方法区实现是元空间。
StringTable 触发Full GC才会回收,所以会导致老年代的空间不足,所以jdk7被移到堆空间中,能及时回收空间。
方法区的内存溢出 ?
jvm配置:-XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
package com.jvm.outofmemoryerror;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* @author: csh
* @Date: 2021/4/6 14:09
* @Description:方法区内存溢出
*/
public class Ares extends ClassLoader {
public static void main(String[] args) {
int i = 0;
Ares ares = new Ares();
while (true) {
i++;
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
byte[] code = cw.toByteArray();
ares.defineClass("Class"+i,code,0,code.length);
System.out.println(i);
}
}
}
最后
方法区也是存在垃圾回收的,这块等到垃圾回收统一了解。方法区是线程共享,存储已经被jvm加载的类信息、常量、静态变量等等,在jdk8之前统称永久代,jdk8开始称为元空间,区别在于元空间不在虚拟机设置的内存中,并且相应的内存结构也调整了。
参考:
https://blog.csdn.net/zuodaoyong/article/details/107031191
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4
https://blog.csdn.net/qq_43040688/article/details/104982648