JVM工作原理与实战(十八):运行时数据区-堆

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、堆介绍、堆的关键参数等内容。

一、运行时数据区

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

线程不共享区域:

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

线程共享区域:

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

image.gif

二、堆

1.堆介绍

在Java虚拟机(JVM)中,堆是用于动态分配内存的区域,也是空间最大的部分。所有通过new关键字创建的对象实例都将在堆上分配内存,这使得堆成为了管理对象生命周期的重要区域。

局部变量表是存放在栈内存中的,它主要存放的是基本数据类型和对象引用,这个对象引用可以指向堆上的对象。静态变量也可以存储堆上对象的引用,这使得静态变量成为线程之间共享对象的一种方式。

image.gif

堆内存大小是有限制的,一旦应用程序持续地将对象分配到堆中,导致其达到上限,就会触发OutOfMemory异常,导致程序运行失败。为了避免这种情况,开发人员需要密切关注堆内存的使用情况,并采取适当的措施来管理内存分配。这包括优化代码以减少内存使用,实施垃圾回收机制,或增加可用内存量等。通过有效的内存管理,可以提高应用程序的稳定性和性能。

模拟堆区的溢出案例

通过使用new关键字,不断地创建对象,并将它们添加到集合中,来模拟堆内存的溢出。随后,观察堆溢出后产生的异常信息。

public class Demo1 {
    public static void main(String[] args) {
        ArrayList<Object> objects = new ArrayList<>();
        while(true){
            objects.add(new byte[1024]);
        }
    }
}
image.gif

运行结果:

image.gif

2.关键参数

在Java堆空间管理中,有以下几个关键参数和概念需要关注:

  • 已使用堆空间(Used Heap Space):这个值表示当前已经被应用程序分配和使用的堆内存量。这个数值可以帮助了解应用程序的内存需求以及是否存在内存泄漏问题。
  • 总堆内存(Total Heap Memory):这是Java虚拟机(JVM)已经分配的可用堆内存量。这个数值可以帮助了解JVM的最大可用内存。
  • 最大堆内存(Max Heap Memory):这是JVM可以分配的最大堆内存量。这个数值可以帮助了解JVM的内存限制,以及是否需要调整JVM的启动参数来增加最大堆内存。

通过arthas查看堆内存:

堆内存used total max三个值可以通过dashboard命令看到,并且可以手动指定刷新频率(不指定默认5秒一次)。

dashboard -i 5000

image.gif

案例:

public class Demo1 {
    public static void main(String[] args) throws IOException {
        System.in.read();
        ArrayList<Object> objects = new ArrayList<>();
        while(true){
            objects.add(new byte[1024]);
        }
    }
}
image.gif

运行结果:

image.gif

在堆内存使用量增加的情况下,当可用内存接近饱和时,Java虚拟机将继续为堆分配内存。如果堆内存耗尽,Java虚拟机将不断进行内存分配,从而使total值相应地增大。需要注意的是,total值不会超过max值(max包括total,total包括used )。

image.gif

在未指定任何虚拟机参数的情况下,max的默认值是系统内存的四分之一,而total的默认值是系统内存的六十四分之一。在实际应用中,可以手动配置total和max的值以确保系统的稳定运行。

修改堆的大小,可以使用虚拟机参数 –Xmx(max最大值)和-Xms (初始的total)。语法为-Xmx值 -Xms值,单位可以为字节(默认,是 1024 的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)。需要注意的是Xmx必须大于2MB,Xms必须大于1MB。

案例:

public class Demo1 {
    public static void main(String[] args) throws IOException {
        System.in.read();
        ArrayList<Object> objects = new ArrayList<>();
        while(true){
            objects.add(new byte[1024]);
        }
    }
}
image.gif

设置堆的大小:

-Xmx4G -Xms4G
image.gif

image.gif

运行结果:

image.gif

在Arthas中显示的堆内存大小与设置的值存在差异,这是由于Arthas使用了JMX技术来获取堆内存的信息。JMX技术中的内存获取方式与垃圾回收器相关,它计算的是可分配对象的内存量,而不仅仅是整个内存。


在Java服务端程序的开发过程中,建议将-Xmx和-Xms参数设置为相同的值。这样做可以确保程序启动后可用的总内存即为最大内存,避免了向Java虚拟机再次申请内存的需求。这减少了申请和分配内存所需的时间开销,并避免了因内存过剩而导致的堆内存收缩情况。


-Xmx参数的具体设置值应与实际的应用程序运行环境相匹配。根据应用程序的需求和可用资源,合理地调整-Xmx的值可以确保程序的稳定运行并优化性能。在设置-Xmx参数时,应考虑程序的内存需求、垃圾回收机制以及其他相关因素。


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了运行时数据区、堆介绍、堆的关键参数等内容,希望对大家有所帮助。

相关文章
|
14天前
|
自然语言处理 前端开发 Java
深入浅出JVM(六)之前端编译过程与语法糖原理
深入浅出JVM(六)之前端编译过程与语法糖原理
|
14天前
|
存储 缓存 算法
深入浅出JVM(二)之运行时数据区和内存溢出异常
深入浅出JVM(二)之运行时数据区和内存溢出异常
|
2天前
|
安全 Java
【JVM】运行时数据区、程序计数器
【JVM】运行时数据区、程序计数器
2 0
|
12天前
|
Java 数据库连接 Spring
K8S+Docker理论与实践深度集成java面试jvm原理
K8S+Docker理论与实践深度集成java面试jvm原理
|
14天前
|
存储 缓存 Java
JVM 运行时内存篇
JVM 运行时内存篇
10 0
|
14天前
|
Arthas Prometheus 监控
JVM工作原理与实战(四十四):JVM常见题目
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了JVM常见题目等内容。
23 1
|
4天前
|
存储 Java 对象存储
JVM(内存区域划分)
JVM(内存区域划分)
14 1
|
14天前
|
存储 Java
深入理解Java虚拟机:JVM内存模型
【4月更文挑战第30天】本文将详细解析Java虚拟机(JVM)的内存模型,包括堆、栈、方法区等部分,并探讨它们在Java程序运行过程中的作用。通过对JVM内存模型的深入理解,可以帮助我们更好地编写高效的Java代码,避免内存溢出等问题。
|
1天前
|
存储 Java 开发者
深入理解Java虚拟机:JVM内存模型解析
【5月更文挑战第27天】 在Java程序的运行过程中,JVM(Java Virtual Machine)扮演着至关重要的角色。作为Java语言的核心执行环境,JVM不仅负责代码的执行,还管理着程序运行时的内存分配与回收。本文将深入探讨JVM的内存模型,包括其结构、各部分的作用以及它们之间的相互关系。通过对JVM内存模型的剖析,我们能够更好地理解Java程序的性能特征,并针对性地进行调优,从而提升应用的执行效率和稳定性。
|
1天前
|
Java
<Java SE> 5道递归计算,创建数组,数组遍历,JVM内存分配...
<Java SE> 5道递归计算,创建数组,数组遍历,JVM内存分配
21 2