JVM工作原理与实战(十九):运行时数据区-方法区

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了方法区、方法区在Java虚拟机的实现、类的元信息、运行时常量池、字符串常量池、静态变量的存储等内容。

一、运行时数据区

Java虚拟机(JVM)在运行Java程序期间,会创建并维护一系列内存区域,这些区域总称为运行时数据区。这些区域根据其用途和特性,被严格定义并管理。《Java虚拟机规范》详细规定了这些区域的作用和行为,以确保所有Java虚拟机实现的一致性和正确性。

线程不共享区域:

  • 程序计数器:用于存储当前线程执行的字节码指令地址。这个区域是每个线程独有的,不共享。
  • Java虚拟机栈:每个线程在创建时都会创建一个虚拟机栈,每个方法调用都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接和方法出口信息。
  • 本地方法栈:与虚拟机栈相似,本地方法栈为native方法提供服务。

线程共享区域:

  • 方法区:用于存储已被JVM加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。
  • :堆是所有线程共享的区域,用于动态分配内存。所有的对象实例以及数组都应当在堆上分配。


二、方法区

1.方法区介绍

方法区是Java虚拟机中的一部分,用于存储已被虚拟机加载的类信息、常量、静态变量以及即时编译器编译后的代码等数据。这个区域的设计目标是为所有线程提供共享的、动态类型的数据。它的核心功能是支持类的加载和链接,以及提供运行时类型信息。

方法区主要由以下三部分构成:

  • 类的元信息(Class Metadata):这部分保存了关于类的所有基本信息。这些信息在类加载时被创建,并存储在方法区中。类的元信息包括类的名称、类的访问权限、类的字节码版本、父类的名称、实现的接口列表、字段和方法信息等。这些信息在运行时被JVM使用,以支持类的方法解析、反射操作和动态代理等功能。
  • 运行时常量池(Runtime Constant Pool):运行时常量池是方法区中的一个重要部分,它主要负责存储字节码文件中的常量池内容。常量池是字节码文件中的一个特殊部分,用于存储各种类型的常量,如字面量、类符号引用等。在运行时,JVM会根据需要动态地向常量池中添加、删除或修改相应的常量。此外,对于每个类,其常量池都有一个私有的副本,但所有线程共享同一个运行时常量池,以确保线程安全。
  • 字符串常量池(String Constant Pool):字符串常量池是运行时常量池的一部分,用于存储字符串字面量。在Java程序中,每个独特的字符串字面量都会在字符串常量池中创建一个相应的字符串对象。如果一个字符串字面量已经存在于常量池中,则不会创建新的对象,而是返回对已有对象的引用。这种设计可以有效地节省内存,并避免创建重复的字符串对象。

2.方法区在Java虚拟机的实现

方法区是Java虚拟机结构中的重要部分,它是《Java虚拟机规范》中定义的一个抽象概念。每款具体的Java虚拟机实现可能会根据规范进行不同的优化和调整。以HotSpot虚拟机为例,来探讨其实现细节。

在JDK7及更早的版本中,方法区被实现为永久代(PermGen)。它位于Java堆内存区域中,并通过虚拟机参数-XX:MaxPermSize来控制其最大大小。然而,这种实现方式在JDK8中被彻底改变。

从JDK8开始,方法区被移至元空间(Metaspace)中。元空间不再属于Java堆的一部分,而是直接建立在操作系统的本地内存中。这种设计使得元空间的大小不再受Java堆大小的限制,而是取决于本地内存的大小。可以通过虚拟机参数-XX:MaxMetaspaceSize可以设置元空间的最大大小。

image.gif

在诊断和监控Java应用时,了解这些差异尤为重要。使用如Arthas这样的诊断工具,可以查看不同版本的Java虚拟机的内存使用情况。在JDK7中,需要关注ps_perm_gen属性来查看方法区的使用情况;而在JDK8及之后的版本,则需要查看metaspace属性。

案例:

为了模拟方法区的溢出情况,可以使用如ByteBuddy这样的框架动态生成大量的字节码数据,并观察方法区是否会出现内存溢出。

引入ByteBuddy依赖:

<dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.12.23</version>
        </dependency>
image.gif

编写测试案例:

public class Demo1 extends ClassLoader {
    public static void main(String[] args) throws IOException {
        System.in.read();
        Demo1 demo1 = new Demo1();
        int count = 0;
        while (true) {
            String name = "Class" + count;
            ClassWriter classWriter = new ClassWriter(0);
            classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null
                    , "java/lang/Object", null);
            byte[] bytes = classWriter.toByteArray();
            demo1.defineClass(name, bytes, 0, bytes.length);
            System.out.println(++count);
        }
    }
}
image.gif

在JDK7上,执行十几万次操作就可能出现错误;而在JDK8上,尽管内存使用量会直线上升,但程序并不会出现错误。

3.类的元信息

方法区,也被称为元空间,是Java虚拟机中用于存储类的元数据信息的区域。这些元信息,通常被称为InstanceKlass对象,包含了关于类的基本信息,例如类的名称、父类的名称、实现的接口、成员变量和方法等。这些信息在类的加载阶段被完全建立并存储在方法区中。

InstanceKlass对象是类元信息的核心,它不仅包含了类的静态信息,如字段和方法,还包含了类的动态行为信息,如字节码信息和常量池等。这些信息在运行时被JVM用于支持诸如反射、动态类加载和异常处理等功能。

image.gif

需要注意的是,方法区的实现和组织方式可能会因JVM的实现而有所不同。例如,在一些JVM实现中,方法区可能会被组织成一个或多个哈希表,以快速查找类的元信息。而在其他实现中,可能会使用其他数据结构或算法来组织和管理这些信息。

4.运行时常量池

在Java的内存区域中,方法区用于存储类的元数据信息。然而,除了存储类的元信息之外,方法区还包含了一个重要的部分:运行时常量池。

运行时常量池是Java虚拟机在运行时创建的一个数据结构,用于存储字节码中的常量池内容。常量池是字节码文件中的一个特殊部分,包含了程序中的常量值,例如字符串字面量、整数等。这些常量在字节码文件中通过编号索引的方式进行访问,这种常量池称为静态常量池。

当字节码文件被加载到内存中时,静态常量池的内容会被复制到运行时常量池中。与静态常量池不同,运行时常量池中的常量可以直接通过内存地址进行访问,因此具有更高的访问速度。这种运行时常量池的设计使得Java程序在运行时能够快速地访问和操作常量,提高了程序的执行效率。

image.gif

5.字符串常量池

在运行时数据区的方法区中,除了类的元信息和运行时常量池外,还有一个特别重要的区域,那就是字符串常量池。字符串常量池主要负责存储字节码文件中定义的常量字符串内容。例如,在代码中定义的常量字符串“123”,这个字符串就会存放在字符串常量池中。

image.gif

早期的设计中,字符串常量池被视为运行时常量池的一部分,它们共享相同的存储空间。然而,随着技术的进步和优化需求的变化,Java虚拟机对这两者的存储区域进行分离。这种调整的主要原因在于,字符串常量池和运行时常量池的功能和职责存在明显的差异。运行时常量池主要负责管理动态编译和类的加载,而字符串常量池则专注于存储和管理程序中定义的常量字符串。

image.gif

6.静态变量的存储

在Java的运行时数据区中,静态变量(也称为类变量)的存储位置在不同版本的Java中有所不同。

在JDK 6及之前的版本中,静态变量是存放在方法区中的,确切地说,是在永久代(PermGen)中。然而,随着Java的发展,这个设计逐渐暴露出一些问题,例如内存溢出和空间限制等。

从JDK 7开始,Java对运行时数据区进行了重新设计,其中最显著的变化就是将静态变量从永久代移出,并存放在了堆内存中的Class对象中。这种变化不仅解决了永久代存在的问题,还使得静态变量的存储更加符合其作为类级别的变量的特性。每个Class对象都包含了该类的静态变量信息,这些信息随着类的加载而加载到内存中。这种设计使得静态变量的访问更加直接和高效,因为它们不再需要从方法区中查找,而是可以直接从Class对象中获取。

image.gif


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了方法区、方法区在Java虚拟机的实现、类的元信息、运行时常量池、字符串常量池、静态变量的存储等内容,希望对大家有所帮助。

相关文章
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
29天前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
1月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
49 2
|
1月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
43 3
|
1月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
3月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
27 3
|
3月前
|
消息中间件 设计模式 安全
多线程魔法:揭秘一个JVM中如何同时运行多个消费者
【8月更文挑战第22天】在Java虚拟机(JVM)中探索多消费者模式,此模式解耦生产与消费过程,提升系统性能。通过`ExecutorService`和`BlockingQueue`构建含2个生产者及4个消费者的系统,实现实时消息处理。多消费者模式虽增强处理能力,但也引入线程安全与资源竞争等挑战,需谨慎设计以确保高效稳定运行。
93 2
|
3月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
170 0
|
3月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4