运行时数据区 - 方法区

① 栈、堆、方法区的交互关系
Person person = new Person();

//Person => .class方法区
//person => 栈
//new Person() => 堆空间

Java栈空间中的一个Slot存储了personreference引用,指向堆空间中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对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    • **这部分引用策略很难同时满足,所以要具体情况具体分析。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值