方法区是什么
1、理解方法区
- 在以后JDK的演进中,方法区 和 堆 肯定是要分开的两个内存空间。具体原因官方给定的是要和JRockit 对齐(将JRockit和Hotspot合二为一)。
- 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
- 方法区生命周期和堆一样随着JVM的启动而创建,销毁而终止,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
- 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
- 方法区 主要存放的就是类信息,即我们在类加载时,需要将class字节码文件加载到内存并解析为元数据模板放入到 方法区,就是这个方法区。
2、方法区、栈、堆 关系
- Person:存放在元空间,也可以说方法区
- person:存放在Java栈的局部变量表中
- new Person():存放在Java堆中
3、方法区 在 JDK6、JDK7、JDK8演变
-
JDK6时 方法区称为 永久代,并且 静态变量存储在永久代上。
-
JDK7时 方法区称为 永久代,其内存大小由JVM分配,与堆内存一样 需要人为分配调整,否则超出OOM。字符串常量池,静态变量保存在堆中
-
JDK8时 方法区修改为 元空间 ,彻底的和堆区划分开,并直接使用物理内存,相交 之前的永久代,oom出现几率变小,并且其内部结构也发生了变化。类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中。
-
所有的对象实体(不论是否static)都存储在堆中
-
所有的成员变量(不论是否基本数据类型和引用类型)实体都存放在堆中,作为对象的一部分
-
局部变量如果是基本数据类型则直接将实体放在局部变量表,如果是引用类型,将引用存放在 栈,实体存放在堆。
-
字符串常量池,静态变量保存在堆中,占比很大放在老年代也有GC不过得等到FGC,而堆中Major GC即可回收
-
元空间主要保存
类信息
4、设置方法区大小与OOM
方法区的大小不必是固定的,JVM可以根据应用的需要动态调整。
方法区在JDK8后最好指定内存大小,并且为其指定相对较高的值。 和堆 初始值和最大值设置成一样一个道理,减少其动态调整带来的性能消耗
JDK7
- -XX:Permsize来设置永久代初始分配空间。默认值是20.75M
- -XX:MaxPermsize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M
- 当JVM加载的类信息容量超过了这个值,会报异常 OutofMemoryError:PermGen space。
JDK8
- -XX:MetaspaceSize来设置元数据区初始分配空间。默认值是21M
- -XX:MaxMetaspaceSize来设定元数据区最大可分配空间,默认值是-1,即没有限制
- 最大值为-1,如果消耗完物理内存则也会报异常 OutofMemoryError:PermGen space。
方法区内部信息
1、 类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVm必须在方法区中存储以下类型信息:
- 这个类型的完整有效名称(全名=包名.类名)
- 这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
- 这个类型的修饰符(public,abstract,final的某个子集)
- 这个类型直接接口的一个有序列表
2、域信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
3、方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
- 方法名称
- 方法的返回类型(或void)
- 方法参数的数量和类型(按顺序)
- 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
- 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
- 异常表(abstract和native方法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
如下是一个类的字节码文件:
Classfile /E:/projects/我的练习项目/my_springcloud/cloud-exercise/target/classes/com/gao/JVM/MethodInnerStructure.class
Last modified 2023-10-26; size 1680 bytes
MD5 checksum fff8daab4df533e91f11820420ee446e
Compiled from "MethodInnerStructure.java"
// 类型信息
public class com.gao.JVM.MethodInnerStructure extends java.lang.Object implements java.lang.Comparable<java.lang.String>, java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #18.#57 // java/lang/Object."<init>":()V
#2 = Fieldref #17.#58 // com/gao/JVM/MethodInnerStructure.num:I
#3 = Class #59 // java/lang/Exception
#4 = Methodref #3.#60 // java/lang/Exception.printStackTrace:()V
#5 = Fieldref #61.#62 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Class #63 // java/lang/StringBuilder
#7 = Methodref #6.#57 // java/lang/StringBuilder."<init>":()V
#8 = String #64 // count =
#9 = Methodref #6.#65 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#10 = Methodref #6.#66 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#11 = Methodref #6.#67 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#12 = Methodref #68.#69 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Class #70 // java/lang/String
#14 = Methodref #17.#71 // com/gao/JVM/MethodInnerStructure.compareTo:(Ljava/lang/String;)I
#15 = String #72 // 测试方法的内部结构
#16 = Fieldref #17.#73 // com/gao/JVM/MethodInnerStructure.str:Ljava/lang/String;
#17 = Class #74 // com/gao/JVM/MethodInnerStructure
#18 = Class #75 // java/lang/Object
#19 = Class #76 // java/lang/Comparable
#20 = Class #77 // java/io/Serializable
#21 = Utf8 serialVersionUID
#22 = Utf8 J
#23 = Utf8 ConstantValue
#24 = Long 2682528963354033461l
#26 = Utf8 str
#27 = Utf8 Ljava/lang/String;
#28 = Utf8 num
#29 = Utf8 I
#30 = Utf8 <init>
#31 = Utf8 ()V
#32 = Utf8 Code
#33 = Utf8 LineNumberTable
#34 = Utf8 LocalVariableTable
#35 = Utf8 this
#36 = Utf8 Lcom/gao/JVM/MethodInnerStructure;
#37 = Utf8 test2
#38 = Utf8 (I)I
#39 = Utf8 value
#40 = Utf8 e
#41 = Utf8 Ljava/lang/Exception;
#42 = Utf8 cal
#43 = Utf8 result
#44 = Utf8 StackMapTable
#45 = Class #59 // java/lang/Exception
#46 = Utf8 test1
#47 = Utf8 count
#48 = Utf8 compareTo
#49 = Utf8 (Ljava/lang/String;)I
#50 = Utf8 o
#51 = Utf8 (Ljava/lang/Object;)I
#52 = Utf8 <clinit>
#53 = Utf8 Signature
#54 = Utf8 Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
#55 = Utf8 SourceFile
#56 = Utf8 MethodInnerStructure.java
#57 = NameAndType #30:#31 // "<init>":()V
#58 = NameAndType #28:#29 // num:I
#59 = Utf8 java/lang/Exception
#60 = NameAndType #78:#31 // printStackTrace:()V
#61 = Class #79 // java/lang/System
#62 = NameAndType #80:#81 // out:Ljava/io/PrintStream;
#63 = Utf8 java/lang/StringBuilder
#64 = Utf8 count =
#65 = NameAndType #82:#83 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#66 = NameAndType #82:#84 // append:(I)Ljava/lang/StringBuilder;
#67 = NameAndType #85:#86 // toString:()Ljava/lang/String;
#68 = Class #87 // java/io/PrintStream
#69 = NameAndType #88:#89 // println:(Ljava/lang/String;)V
#70 = Utf8 java/lang/String
#71 = NameAndType #48:#49 // compareTo:(Ljava/lang/String;)I
#72 = Utf8 测试方法的内部结构
#73 = NameAndType #26:#27 // str:Ljava/lang/String;
#74 = Utf8 com/gao/JVM/MethodInnerStructure
#75 = Utf8 java/lang/Object
#76 = Utf8 java/lang/Comparable
#77 = Utf8 java/io/Serializable
#78 = Utf8 printStackTrace
#79 = Utf8 java/lang/System
#80 = Utf8 out
#81 = Utf8 Ljava/io/PrintStream;
#82 = Utf8 append
#83 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#84 = Utf8 (I)Ljava/lang/StringBuilder;
#85 = Utf8 toString
#86 = Utf8 ()Ljava/lang/String;
#87 = Utf8 java/io/PrintStream
#88 = Utf8 println
#89 = Utf8 (Ljava/lang/String;)V
{
// 域信息
private static final long serialVersionUID;
descriptor: J
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
ConstantValue: long 2682528963354033461l
private static java.lang.String str;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
public int num;
descriptor: I
flags: ACC_PUBLIC
// 方法信息
public com.gao.JVM.MethodInnerStructure();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 10
7: putfield #2 // Field num:I
10: return
LineNumberTable:
line 11: 0
line 15: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/gao/JVM/MethodInnerStructure;
public static int test2(int);
descriptor: (I)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 30
4: istore_2
5: iload_2
6: iload_0
7: idiv
8: istore_1
9: goto 17
12: astore_2
13: aload_2
14: invokevirtual #4 // Method java/lang/Exception.printStackTrace:()V
17: iload_1
18: ireturn
// 异常信息表
Exception table:
from to target type
2 9 12 Class java/lang/Exception
LineNumberTable:
line 18: 0
line 20: 2
line 21: 5
line 24: 9
line 22: 12
line 23: 13
line 25: 17
LocalVariableTable:
Start Length Slot Name Signature
5 4 2 value I
13 4 2 e Ljava/lang/Exception;
0 19 0 cal I
2 17 1 result I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ int, int ]
stack = [ class java/lang/Exception ]
frame_type = 4 /* same */
public void test1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: bipush 20
2: istore_1
3: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
6: new #6 // class java/lang/StringBuilder
9: dup
10: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
13: ldc #8 // String count =
15: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: iload_1
19: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
22: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: return
LineNumberTable:
line 31: 0
line 32: 3
line 33: 28
LocalVariableTable:
Start Length Slot Name Signature
0 29 0 this Lcom/gao/JVM/MethodInnerStructure;
3 26 1 count I
public int compareTo(java.lang.String);
descriptor: (Ljava/lang/String;)I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iconst_0
1: ireturn
LineNumberTable:
line 37: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/gao/JVM/MethodInnerStructure;
0 2 1 o Ljava/lang/String;
public int compareTo(java.lang.Object);
descriptor: (Ljava/lang/Object;)I
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #13 // class java/lang/String
5: invokevirtual #14 // Method compareTo:(Ljava/lang/String;)I
8: ireturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/gao/JVM/MethodInnerStructure;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #15 // String 测试方法的内部结构
2: putstatic #16 // Field str:Ljava/lang/String;
5: return
LineNumberTable:
line 13: 0
}
Signature: #54 // Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
SourceFile: "MethodInnerStructure.java"
4、运行时常量池
-
运行时常量池和常量池表不是一个东西,常量池表即 jclasslib 编译字节码文件后的常量池数据,保存在class文件中。而运行时常量池是相对于整个系统而言的常量池表,属于方法区。
-
运行时的常量池,包含了各种字面量、域和方法的引用。
-
我们在 JVM虚拟机栈中说到虚拟机栈 --> 栈帧 --> 运行时常量池符号引用。
Java程序运行时需要JRE运行时环境,其包含了很多核心类库,当JVM执行引擎解析一个class字节码文件时,其中包含了很多的核心类库引用以及一些常量、方法、字段、类引用信息。我们不能为每个字节码文件都写入其引用信息。
方法区垃圾回收
方法区的垃圾回收,之前在堆中讲过,只有FGC才会触发方法区的垃圾收集,而触发FGC的条件有很多,包括system.gc、堆满、方法区满
有些人认为方法区(如Hotspot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如JDK11时期的zGC收集器就不支持类卸载)。
一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。
方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。