当前位置: 首页 > article >正文

JVM内存结构笔记03-方法区

文章目录

  • 方法区
    • 1.定义
    • 2.组成
      • 方法区与永久代和元空间的关系
      • 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
    • 3.方法区常用参数
    • 4.运行时常量池
      • 常量池
      • 运行时常量池定义
      • 查看class文件


在这里插入图片描述

方法区

1.定义

  • 方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。
  • 当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。
  • 方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

注意:在不同的虚拟机实现上,方法区的实现是不同的。

2.组成

在这里插入图片描述

  • 1.8之前的版本中,方法区用永久代来进行实现,永久代中包含类信息、运行时常量池。
  • 1.8之后永久代被废弃,变为在元空间中,元空间在本地内存(操作系统的内存)中。

方法区与永久代和元空间的关系

方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。
在这里插入图片描述

为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

在《深入理解 Java 虚拟机》第三版中解释到:
在这里插入图片描述
原因:

  1. 整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整(也就是受到 JVM 内存的限制),而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
  2. 元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
  3. 在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
  4. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

3.方法区常用参数

JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。

-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。

JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是本地内存。下面是一些常用参数:

-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。

当元空间溢出时会得到如下错误:java.lang.OutOfMemoryError: MetaSpace
可以使用 -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。
-XX:MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。

4.运行时常量池

常量池

常量池(Constant Pool) 是 Java 类文件(.class 文件)中的一个重要组成部分,每个类文件(.class 文件)都有一个独立的常量池(Constant Pool)。常量池中存储了编译期生成的各种字面量和符号引用。
字面量如文本字符串、被声明为 final 的常量值等;
符号引用则包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符等。

运行时常量池定义

在Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table) 。

**运行时常量池是方法区的一部分。也就是说,当JVM加载一个类文件时,会将该类的常量池信息(常量池表)加载到方法区内的运行时常量池中。

字面量:字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。字面量包括整数、浮点数和字符串字面量。如:

String str = "hello";
final int num = 10;

其中 “hello” 和 10 就属于字面量,会存储在运行时常量池中。

符号引用:常见的符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法符号。

《深入理解 Java 虚拟机》第三版 对符号引用和直接引用的解释如下:
在这里插入图片描述

  • 常量池表会在类加载后存放到方法区的运行时常量池中
  • 运行时常量池的功能类似于传统编程语言的符号表,尽管它包含了比典型符号表更广泛的数据。
  • 既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。

查看class文件

代码

// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

通过反编译查看以上代码的.class文件

PS D:\Test\jvm\out\production\jvm\cn\qf> javap -v HelloWorld.class
Classfile /D:/Test/jvm/out/production/jvm/cn/qf/HelloWorld.class
  Last modified 2024-12-6; size 567 bytes
  MD5 checksum 8efebdac91aa496515fa1c161184e354
  Compiled from "HelloWorld.java"
public class cn.qf.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
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            // cn/qf//HelloWorld
   #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               Lcn/qf//HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.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               cn/qf/HelloWorld
  #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 cn.qf.HelloWorld();
    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 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/qf/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: 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 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

翻到最下面
在这里插入图片描述
#2、#3、#4在Constant pool中查看定义
如#3在Constant pool对应#23

Constant pool:
   ...
   #3 = String             #23            // hello world
   ...
   #23 = Utf8               hello world
   ...

常量池的作用:给指令提供常量符号,根据常量符合以查表的方式去找到他们。


相关文章:
JVM内存结构笔记01-运行时数据区域
JVM内存结构笔记02-堆
JVM内存结构笔记03-方法区
JVM内存结构笔记04-字符串常量池
JVM内存结构笔记05-直接内存
JVM中常量池和运行时常量池、字符串常量池三者之间的关系


http://www.kler.cn/a/587733.html

相关文章:

  • Mysql查看执行计划、explain关键字详解(超详细)
  • linux在 Ubuntu 系统中设置服务器时间
  • WPF跨平台开发探讨:借助相关技术实现多平台应用
  • C++——STL 常用的排序算法
  • uniapp-x js 限制
  • AUTOSAR 网络安全 架构
  • Spring Boot + InfluxDB 批量写入(同步、异步、重试机制)
  • AI自动文献综述——python先把知网的文献转excel
  • EB-Cable许可管理中的数据安全与隐私保护
  • UE材质RadialGradientExponential
  • WebSocket 使用教程
  • 前端无限滚动内容自动回收技术详解:原理、实现与优化
  • 在 VMware 中安装 Ubuntu 的超详细实战分享
  • Postman用JSON格式数据发送POST请求及注意事项
  • LeetCode 2226. Maximum Candies Allocated to K Children(2025/3/14 每日一题)
  • 【MySQL】数据库简要介绍和简单应用
  • 分享一个sql统计的客户需求
  • Vue2+Vant2 项目初学
  • 故障诊断——neo4j入门
  • centos7通过yum安装redis