① 栈、堆、方法区的交互关系
Person person = new Person();
//Person => .class方法区
//person => 栈
//new Person() => 堆空间
Java
栈空间中的一个Slot
存储了person
的reference
引用,指向堆空间中Person
的一个实例,然后堆空间中的Person
实例中存在一个对象类型数据的指针,指向方法区中的对象数据类型。
② 方法区的理解
《Java虚拟机规范》
中明确说明:尽管所有的方法区在逻辑上属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者垃圾压缩。但对于HotSpotJVM
来说,方法区还有一个别名叫做Non-Heap
,目的就是和堆分开。
所以,方法区看作一块独立于Java堆
的实现。
- 方法区
(Method Area)
与Java堆
一样,是各个线程共享的内存区域。 - 方法区在
JVM
启动时就被创建,并且它的实际物理内存和堆一样都可以是不连续的。 - 方法区的大小,和堆一样,可以选择固定大小或拓展大小。
- 方法区决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,同样会抛出内存溢出异常:
java.lang.OutOfMemoryError: Metaspace
。元空间内存溢出。 - 关闭
JVM
就会释放这个区域。
- 元空间和永久代最大的区别在于:元空间不在虚拟机中设置的内存中,而是使用本地内存。
- 永久代、元空间二者改变的并不只是名字,内部结构也调整了。
- 如果方法区无法满足新的内存分配需求是,将报出
OOM
。
③ 设置方法区大小与OOM
JDK8
及以后:
- 元空间大小可以使用
-XX:MetaspaceSize
和-XX:MaxMetaspaceSize
指定。 - 默认值依赖于平台。
windows
下,元空间是21M
,最大为-1
即没有限制。 - 当元空间内内存触及到
MetaspaceSize
时,就会进行Full GC
,从而释放掉一些不用的类,这些类加载器也不再存活。在释放后,如果元空间内存低于MetaspaceSize
则会降低MetaspaceSize
值,如果高于,则提高该值。 - 所以在生产环境中,为了避免频繁
Full GC
,一般会将MetaspaceSize
设置为一个较高的值。
④ 方法区的内部结构
方法区存储什么?
- 类型信息、域
(Field)
信息、方法信息
**类型信息:**对每个加载的类型例如:class、interface、enum、annotation
,父类的完整有效名例如java.lang.Object
,类修饰符例如public、final
,实现类的有序列表。
**域信息:**域名称、域类型、域修饰符。
**方法信息:**方法名称、返回值类型、参数数量和类型(按顺序)
、方法修饰符、方法字节码、操作数栈和局部变量表大小(abstract和native)方法除外
、异常表,每个异常处理开始、结束位置。
- 常量。
- 静态变量。
- 即时编译器编译后的代码缓存。
步骤:Class File -> 经过ClassLoader加载到方法区 所以方法区存Class里的各种信息以及它是被哪个加载器加载的
。
由于方法区里存的是由类加载器加载对应的class
文件得到的,所以可以通过字节码文件来查看方法区内容。
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @Author HE LONG CAN
* @Description TODO
* @Date 2022-02-21 16:22:56
*/
public class Main extends Object implements Comparable<Main>,Serializable{
private static final String str = "aabbcc";
private static int type = 0;
private final int age = 15;
@Override
public int compareTo(Main o) {
return 0;
}
public void test01(int a) {
int result = a;
}
public static int test02(boolean flag) {
if (flag) {
try {
throw new Exception();
}catch (Exception e) {
e.printStackTrace();
}
}
return 1;
}
}
使用javap -v -p Main.class > test.txt
编译到txt
文件里。
Classfile /F:/临时代码/out/production/临时代码/Main.class
Last modified 2022-3-6; size 1148 bytes
MD5 checksum 3cdfec4cd0be7bcb68b76b321f1e85d9
Compiled from "Main.java"
//类的类型信息
public class Main extends java.lang.Object implements java.lang.Comparable<Main>, java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#48 // java/lang/Object."<init>":()V
#2 = Fieldref #6.#49 // Main.age:I
#3 = Class #50 // java/lang/Exception
#4 = Methodref #3.#48 // java/lang/Exception."<init>":()V
#5 = Methodref #3.#51 // java/lang/Exception.printStackTrace:()V
#6 = Class #52 // Main
#7 = Methodref #6.#53 // Main.compareTo:(LMain;)I
#8 = Fieldref #6.#54 // Main.type:I
#9 = Class #55 // java/lang/Object
#10 = Class #56 // java/lang/Comparable
#11 = Class #57 // java/io/Serializable
#12 = Utf8 str
#13 = Utf8 Ljava/lang/String;
#14 = Utf8 ConstantValue
#15 = String #58 // aabbcc
#16 = Utf8 type
#17 = Utf8 I
#18 = Utf8 age
#19 = Integer 15
#20 = Utf8 <init>
#21 = Utf8 ()V
#22 = Utf8 Code
#23 = Utf8 LineNumberTable
#24 = Utf8 LocalVariableTable
#25 = Utf8 this
#26 = Utf8 LMain;
#27 = Utf8 compareTo
#28 = Utf8 (LMain;)I
#29 = Utf8 o
#30 = Utf8 test01
#31 = Utf8 (I)V
#32 = Utf8 a
#33 = Utf8 result
#34 = Utf8 test02
#35 = Utf8 (Z)I
#36 = Utf8 e
#37 = Utf8 Ljava/lang/Exception;
#38 = Utf8 flag
#39 = Utf8 Z
#40 = Utf8 StackMapTable
#41 = Class #50 // java/lang/Exception
#42 = Utf8 (Ljava/lang/Object;)I
#43 = Utf8 <clinit>
#44 = Utf8 Signature
#45 = Utf8 Ljava/lang/Object;Ljava/lang/Comparable<LMain;>;Ljava/io/Serializable;
#46 = Utf8 SourceFile
#47 = Utf8 Main.java
#48 = NameAndType #20:#21 // "<init>":()V
#49 = NameAndType #18:#17 // age:I
#50 = Utf8 java/lang/Exception
#51 = NameAndType #59:#21 // printStackTrace:()V
#52 = Utf8 Main
#53 = NameAndType #27:#28 // compareTo:(LMain;)I
#54 = NameAndType #16:#17 // type:I
#55 = Utf8 java/lang/Object
#56 = Utf8 java/lang/Comparable
#57 = Utf8 java/io/Serializable
#58 = Utf8 aabbcc
#59 = Utf8 printStackTrace
{
//域信息
private static final java.lang.String str;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
ConstantValue: String aabbcc
private static int type;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC
private final int age;
descriptor: I
flags: ACC_PRIVATE, ACC_FINAL
ConstantValue: int 15
//构造器
public Main();
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 15
7: putfield #2 // Field age:I
10: return
LineNumberTable:
line 17: 0
line 20: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LMain;
public int compareTo(Main);
descriptor: (LMain;)I
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iconst_0
1: ireturn
LineNumberTable:
line 24: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this LMain;
0 2 1 o LMain;
//自定义的方法
public void test01(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
//栈深度 局部变量表深度
stack=1, locals=3, args_size=2
//字节码文件
0: iload_1
1: istore_2
2: return
LineNumberTable:
line 28: 0
line 29: 2
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 this LMain;
0 3 1 a I
2 1 2 result I
public static int test02(boolean);
descriptor: (Z)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iload_0
1: ifeq 17
4: new #3 // class java/lang/Exception
7: dup
8: invokespecial #4 // Method java/lang/Exception."<init>":()V
11: athrow
12: astore_1
13: aload_1
14: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
17: iconst_1
18: ireturn
//异常表 从字节码from行到to行如果没有出现异常则执行字节码target行,对应的为代码34行,35行,35行
Exception table:
from to target type
4 12 12 Class java/lang/Exception
LineNumberTable:
line 32: 0
line 34: 4
line 35: 12
line 36: 13
line 39: 17
LocalVariableTable:
Start Length Slot Name Signature
13 4 1 e Ljava/lang/Exception;
0 19 0 flag Z
StackMapTable: number_of_entries = 2
frame_type = 76 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 4 /* same */
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 #6 // class Main
5: invokevirtual #7 // Method compareTo:(LMain;)I
8: ireturn
LineNumberTable:
line 17: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LMain;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_0
1: putstatic #8 // Field type:I
4: return
LineNumberTable:
line 19: 0
}
Signature: #45 // Ljava/lang/Object;Ljava/lang/Comparable<LMain;>;Ljava/io/Serializable;
SourceFile: "Main.java"
注意:
final
变量会在编译的时候就赋值,详见上面字节码72~75
。
static
变量会在类加载准备环节设置默认值,在初始化阶段赋值。详见1.1.1类加载步骤
。
运行时常量池:
- 方法区,内部包含了运行时常量池。
- 字节码文件,内部包含了常量池。
(为了弄清运行时常量池,就要先弄清常量池)
。 - 字节码中常量池
->
加载器加载->
方法区中运行时常量池。
为什么需要常量池?
因为一个类的加载往往牵扯到要加载其父类或者调用到的很多类,会导致class
文件很大。所以常量池中以符号的形式保存这些间接引用,从而在运行时将间接引用转换成直接引用。
在字节码里:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 15
7: putfield #2 // Field age:I
10: return
#??
表示使用了常量池中的??
行常量。
常量池里存储的内容有:
- 数值
- 字符串值
- 类引用
- 字段引用
- 方法引用
常量池,相当于一张表,字节码指令根据这张表来找到要执行的类、方法、参数、字面量等信息。
⑤ 方法区的演变细节
https://www.bilibili.com/video/BV1PJ411n7xZ?p=97
⑥ 关于静态成员变量存在哪?
静态成员变量存在于堆中。
⑥ 方法区的垃圾回收
方法区的垃圾回收主要回收两个部分:
- 常量池中废弃的常量
- 字面量和符号引用,例如文本字符串、被声明为
final
的常量值、(类、方法、变量的名称,描述符)
。 HotSpot
虚拟机堆这部分的回收策略很明确,只要常量池中常量没有被任何地方引用,就可以被回收。
- 字面量和符号引用,例如文本字符串、被声明为
- 常量池中不再使用的类引用
- 该类所有实例都已经被回收,也就是
Java
堆内不存在该类以及其他任何派生子类的实例。 - 该类的类加载器已经被回收。
- 该类对应的
java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 - **这部分引用策略很难同时满足,所以要具体情况具体分析。
- 该类所有实例都已经被回收,也就是