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

深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常

第二章 Java内存区域与内存溢出异常

2.1 意义

对于C、C++程序开发来说,程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制,让java程序员不需要手写delete或者free代码,不容易出现内存泄漏和内存溢出问题,但是如果出现了内存泄漏和溢出的问题,就需要知道虚拟机是怎样使用内存的,才能排查错误并且修正。本章主要讲明Java虚拟机内存的各个区域,各个区域的作用、服务对象,以及常见的异常。

2.2 运行时数据区域

Java虚拟机在执行Java程序的过程中会把管理的内存分为几个不同的数据区域,其中包括以下几个运行时数据区,这些区域有各自的用途以及创建和销毁的时间。
在这里插入图片描述

2.2.1 程序计数器

  • 程序计数器占用了一块较小的内存空间,充当线程指示器的角色。

  • Java虚拟机的多线程是通过线程之间切换、分配处理器执行时间实现的,一个内核在某一时刻只会执行一条线程的指令,所以计数器就可以在切换线程时发挥作用,能够确定切换到正确位置继续程序执行。而且要保证每个线程的计数器是独立的,互不干涉。

  • 如果执行的Java方法,计数器记录的是正在执行的虚拟机字节码的地址。如果是native方法,计数器为空(undefined)。

  • 唯一一个没有OutOfMemoryError情况的区域

2.2.2 Java虚拟机栈

  • 线程私有的,生命周期与线程相同。

  • 虚拟机栈对应Java方法执行,每个方法对应栈中的一个栈帧(每个方法被调用到执行完毕的过程就对应一个栈帧在虚拟机栈中入栈到出栈),这个栈帧存储局部变量表、操作数栈、动态连接、方法出口等信息。

  • 局部变量表存放编译期可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型。

    局部变量表的的存储空间以局部变量槽(slot)表示,long和double占两个slot,其他类型一个slot。

    栈帧在编译期就给局部变量表分配好了内存空间,即slot的数量在方法运行期间不会改变。

  • 两类异常:线程请求的栈深度大于虚拟机的所允许的深度,抛出StackOverflowError异常;

    Java虚拟机栈容量可以动态扩展的情况下,扩展时无法申请到足够的内存会抛出OutOfMemoryError异常

2.2.3 本地方法栈

与虚拟机栈执行Java方法相似,只不过本地方法栈执行Native方法。没有强制规定,有的Java虚拟机(Hot-Spot)直接合二为一。

2.2.4 Java堆

  • 所有线程共享,虚拟机启动时创建
  • 唯一目的是存放对象实例,几乎所有的对象实例都在堆上分配内存
  • 是垃圾收集器管理的内存区域,基于分代收集理论逻辑上分为“新生代”“老年代”“永久代”“Eden空间”“From Survivor空间”“To Survivor空间”等。
  • 可以处于物理不连续的内存空间,但逻辑上是连续的。
  • 参数-Xmx(设置 Java 堆内存的最大大小)和-Xms(设置初始堆内存大小)可以控制是否可扩展
  • 如果堆中没有内存完成实例分配,且堆无法扩展,会抛出OutOfMemoryError异常。

2.2.5 方法区

  • 各个线程共享,别名“非堆”
  • 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
  • 可以不实现垃圾回收,主要因为回收效果不太好,回收条件苛刻,但有时有必要
  • 无法满足新的内存分配会抛出OutOfMemoryError异常
  • *方法区的实现可以由JVM实现自由决定,JDK8以前称为永久代,但不等价,为了让垃圾收集器方便管理这部分内存。JDK8以后改为元空间

2.2.6 运行时常量池

  • 方法区的一部分,存放编译期生成的字面量(比如字符串或基本类型的常量)和符号引用(类和接口的全限定名:例如,java.lang.String。字段的名称和描述符:例如,int age中的字段名称age和类型描述符int。方法的名称和描述符:例如,void print(String s)中的方法名称print和描述符(Ljava/lang/String;)V
  • 一般会把符号引用翻译的直接引用也存储在运行时常量池中
  • 具备动态性,运行期间也可以放入新的常量,String类的intern()方法
  • 无法申请到内存会抛出OutOfMemoryError异常

2.2.7 直接内存

  • 不是运行时数据区的一部分
  • 使用Native函数分配堆外内存,再通过堆内的一个对象(DirectByteBuffer)引用这块内存来使用,提高性能
  • 也会出现OutOfMemoryError异常

2.3 Hotspot虚拟机对象探秘

HotSpot虚拟机在Java堆中对象分配、布局和访问的全过程

2.3.1 对象的创建过程

  1. new一个对象时,Java虚拟机会首先检查这个指令的参数能否在常量池中定位到一个类的符号引用,然后判断是否已经被加载、解析和初始化过。如果没有则先执行类加载过程,然后为新生对象分配内存。
  • 如果内存规整,一边是使用过的内存,一边是空闲的内存,由指针分隔,那么分配内存就只需要移动指针来完成,这就是指针碰撞。

  • 实际上内存并不规整,所以虚拟机通过一个列表来记录哪些内存块是可用的,然后从列表中找到一块空间分配给对象实例,这就是空闲列表。

  • 堆是否规整由垃圾收集器有没有整理能力决定。

为了并发情况下保证创建对象的线程安全,有以下两种方案:一般情况本地缓冲区用完再进行同步处理。

  • 对分配内存的行为进行同步处理:实际中虚拟机采用CAS(Compare-And-Swap比较当前值和预期值,如果它们相等,则将当前值更新为新值。否则,什么也不做。)+失败重试(当多个线程同时尝试更新同一个变量时,只有一个线程的CAS操作会成功,其他线程会失败。失败的线程不会阻塞,而是重新读取变量的当前值并再次尝试操作)
  • 每个线程在堆中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)
  1. 分配完后,将分配到的内存初始化为零值,不包括对象头。
  2. 然后根据对象头里的信息进行设置。
  3. 接着执行构造方法()方法, 一个完整的对象就被创建出来了。

2.3.2 对象的内存布局

一个对象在堆中的布局为三个部分:对象头(header)、实例数据(Instance data)、对齐填充(Padding)

  1. 对象头包括两类信息
    • Mark Word:对象自身的运行时数据,动态数据结构
    • 类型指针:指向类型元数据,确定该对象是哪个类的实例。(使用句柄访问则不需要保存类型指针,类型指针都存到了句柄池中)
    • 如果是数组还需要记录数组长度
  2. 对象真正存储的有效信息,各种类型的字段内容。
    • 存储顺序由虚拟机和java源码决定
    • 父类定义的变量会在子类之前
  3. 对齐填充起占位符的作用,保证8字节的整数倍

2.3.3 对象的访问定位

由虚拟机实现决定,主流有两个:使用句柄或者直接指针

  • 使用句柄,reference存储对象的句柄地址,句柄指向实例数据和类型数据。对象被移动时只会改变句柄中的指针,而reference不需要修改,如图

在这里插入图片描述

  • 直接指针,对象自身保存指向类型数据的指针,不需要多一次间接指针的开销,hotspot使用。如图

在这里插入图片描述

2.4 OutOfMemoryError异常

验证运行时区域存储的内容,以及遇到内存溢出异常时,通过信息得知是哪个区域的内存溢出,知道怎样的代码可能会导致这些区域内存溢出,以及出现这些异常后该如何处理。

2.4.1 Java堆溢出

参数-XX:+HeapDumpOnOutOf-MemoryError可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照以便进行事后分析

Java堆内存的OutOfMemoryError异常,会跟随进一步提示“Java heap space“ ,处理方法:

  • 通过内存映像分析工具对dump出来的堆转储快照进行分析,确认导致oom的对象是否必须(内存泄露还是内存溢出)

  • 如果内存泄露:

    查看泄露对象到GC roots的引用链,定位对象创建的位置,找到出现内存泄漏的代码的具体位置

  • 如果内存溢出:

    • 检查堆参数设置,查看是否还有调整空间
    • 检查是否某些对象生命周期过长、持有状态过长或存储结构设计不合理

2.4.2 虚拟机栈和本地方法栈溢出

Java虚拟机规范中描述了两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  2. 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出
    OutOfMemoryError异常。

对于HotSpot虚拟机,不区分虚拟机栈和本地方法栈,所以栈容量只由-Xss设定,而且不支持栈的动态扩展。所以除非创建线程时就内存不足才会出现OutOfMemoryError异常。只会在运行时栈无法容纳新的栈帧抛出StackOverflowError异常。

如果出现了建立过多的线程导致的内存溢出,可以考虑减少最大堆和减少栈容量来换取更多的线程。因为windows给一个进程分配的内存是有限的。

2.4.3 方法区和运行时常量池溢出

首先明白在HotSpot中方法区表现形式的变化,如下图

在这里插入图片描述

*方法区的实现可以由JVM实现自由决定,尽管JVM规范将方法区描述为非堆内存区域,但运行时常量池在Java 8之后确实被放置在堆内存中。这意味着,运行时常量池虽然是方法区逻辑上的一部分,但在实际的JVM实现中,它的存储位置可以是堆内存。字符串常量池同理

为什么运行时常量池在堆内存中

  • 动态性:运行时常量池中的常量不仅包括类文件中定义的常量,还可以在运行时动态添加。这种动态性需要灵活的内存管理,堆内存提供了这样的灵活性。
  • 垃圾回收:运行时常量池中的常量可能在程序运行过程中变得不再需要。将其放在堆内存中,JVM可以利用堆内存的垃圾回收机制更高效地回收这些不再使用的常量。

回到溢出问题,String::intern()是一个本地方法,作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用,否则会将此String对象包含的字符串添加到字符串常量池中,并返回引用。

jdk7之前,运行时常量池属于方法区,且大小固定容易溢出。之后转移到堆上以后不容易溢出。

2.4.4 本机直接内存溢出

直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致

直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO),有可能是直接内存的问题。

  • 从GC Roots找引用链
  • Stop The World,因为如果对象引用关系还在不停变化,会影响收集的准确性
  • 虚拟机应当可以直接得到哪些地方记录了对象引用,比如OopMap,会记录栈和寄存器里引用的位置。

相关文章:

深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常

第二章 Java内存区域与内存溢出异常 2.1 意义 对于C、C程序开发来说,程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制,让java程序员不需要手写delete或者free代码,不容易出现内存泄漏和内存溢出问题,但是如果…...

跨境电商必备保护账号的4个网络环境设置

在跨境电商的世界里,一个稳定可靠的网络环境就是你事业成功的关键!但是,不稳定的IP很容易导致账号被封,让你的辛苦付之东流,相信许多小伙伴也经历过莫名其妙的账号封禁情况! 为了让大家避免这种心痛的情况…...

Python+requests接口自动化测试框架实例教程

前段时间由于公司测试方向的转型,由原来的web页面功能测试转变成接口测试,之前大多都是手工进行,利用postman和jmeter进行的接口测试,后来,组内有人讲原先web自动化的测试框架移驾成接口的自动化框架,使用的…...

【网络安全】DNS重绑定原理详析

原创文章,不得转载。 文章目录 DNSDNS查询过程同源策略DNS重绑定攻击原理DNS重绑定攻击步骤DNS重绑定工具工具一工具二DNS 在网络中,访问网站实际上是通过其对应的 IP 地址实现的,然而,IP 地址往往难以记忆。因此,DNS(域名系统)应运而生。 DNS(Domain Name System)是…...

C语言初识编译和链接

目录 翻译环境和运行环境编译环境预编译编译词法分析语法分析语义分析 汇编 链接运行环境 翻译环境和运行环境 在ANSI C的任何⼀种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执⾏的机器指令(⼆进制指令&…...

TrinityCore环境搭建

1)https://192.168.3.96:41797/soft/app root jianan2)mysql322bb8f85b0920d9 192.168.3.96 9f5c813fefbbc3aa3) su wow cd /home/wow/TrinityCore/TrinityCore-TDB335.22061/build cmake ../ -DCMAKE_INSTALL_PREFIX/home/wow/server3.5.5 #构建项目cmake ../ -DCMAKE_INSTALL…...

Proteus 仿真设计:开启电子工程创新之门

摘要: 本文详细介绍了 Proteus 仿真软件在电子工程领域的广泛应用。从 Proteus 的功能特点、安装与使用方法入手,深入探讨了其在电路设计、单片机系统仿真、PCB 设计等方面的强大优势。通过具体的案例分析,展示了如何利用 Proteus 进行高效的…...

microchip dspic3一些奇怪问题

UART初始化,导致一些MCU PIN输出低电平。 https://microchip.my.site.com/s/case/500V4000007jvz4IAA/detail 板子上电EEPROM读取不稳定,增加延时解决问题。 –If delay 1ms, will read EE Err –If delay 10ms, program and reset, will read EE err.…...

FinOps原则:云计算成本管理的关键

导语: FinOps 原则为我们提供了北极星(North Star),在我们实践云财务管理时指导我们的活动。这些原则由 FinOps 基金会成员制定,并通过经验磨练出来。 北极星(North Star)的含义: …...

JavaScript之如何优化模板字符串的性能

在 JavaScript 中,优化模板字符串的性能可以从几个方面入手。模板字符串(Template Literals)是 ES6 引入的特性,它们使用反引号 () 包围,可以嵌入表达式并支持多行字符串。虽然模板字符串通常很方便,但在性…...

不能将类型“null”分配给类型“number | undefined”。ts(2322)

错误解释: 这个TypeScript错误表明你正在尝试将null赋值给一个预期为number类型或undefined类型的变量。在TypeScript中,null和undefined是有效的值,但通常我们希望它们与number类型不兼容。 解决方法: 检查导致错误的赋值语句&…...

Nginx部署前端Vue项目详细教程

文章目录 Nginx部署前端Vue项目详细教程准备工作打包Vue项目安装Nginx配置Nginx创建配置文件启用配置文件 部署Vue项目配置SSL(可选)测试和验证总结 Nginx部署前端Vue项目详细教程 本教程将详细介绍如何使用Nginx部署前端Vue项目,涵盖从项目…...

kvm 虚拟机命令行虚拟机操作、制作快照和恢复快照以及工作常用总结

文章目录 kvm 虚拟机命令行虚拟机操作、制作快照和恢复快照一、kvm 虚拟机命令行虚拟机操作(创建和删除)查看虚拟机virt-install创建一个虚拟机关闭虚拟机重启虚拟机销毁虚拟机 二、kvm 制作快照和恢复快照**创建快照**工作常见问题创建快照报错::intern…...

内网安全-横向移动【3】

1.域横向移动-内网服务-Exchange探针 Exchange是一个电子右键服务组件,由微软公司开发。它不仅是一个邮件系统,还是一个消息与协作系统。Exchange可以用来构建企业、学校的邮件系统,同时也是一个协作平台,可以基于此开发工作流、…...

语言中的浮点数

浮点数相比定点数或者整数,为了处理小数点引入了指数,导致小数点的位置根据不同浮点数而不同,故名为Floating Point Number. 一般而言,IEEE754标准被大部分编程语言的浮点数使用,它节省了浮点数的保存空间。如不然&…...

Pyspark下操作dataframe方法(1)

文章目录 Pyspark dataframe创建DataFrame使用Row对象使用元组与scheam使用字典与scheam注意 agg 聚合操作alias 设置别名字段设置别名设置dataframe别名 cache 缓存checkpoint RDD持久化到外部存储coalesce 设置dataframe分区数量collect 拉取数据columns 获取dataframe列 Pys…...

注解实现json序列化的时候自动进行数据脱敏

最近在进行开发的时候遇到一个问题,需要对用户信息进行脱敏处理,原有的方式是写一个util类,在需要脱敏的字段查出数据后,显示掉用方法处理后再set回去,觉得这种方式能实现功能,但是不是特别优雅&#xff0c…...

使用Python下载文件的简易指南

在日常的数据处理、自动化任务或软件开发中,经常需要从网络上下载文件。Python作为一门功能强大的编程语言,提供了多种方法来实现文件的下载。本文将介绍几种常用的方法来使用Python下载文件,包括使用requests库和urllib库。 准备工作 在开…...

中秋国庆双节长假,景区迎来客流高峰,如何保障景区安全管理?

一、方案背景 近年来,国内旅游市场持续升温,节假日期间景区游客数量激增,给景区安全管理带来了巨大挑战。然而,景区安全风险意识不足、防护措施不完善、游客安全意识欠缺等问题依然存在,导致景区安全事故频发。随着中秋…...

多维数组转一维数组:探索 JavaScript 中的数组扁平化

在 JavaScript 编程中,我们经常会遇到需要将多维数组转换为一维数组的情况。无论是处理复杂的数据结构还是进行数据的进一步操作,数组扁平化都是一个常见且有用的技术。本文将介绍几种在 JavaScript 中将多维数组转换为一维数组的方法。 什么是数组扁平…...

<6>-MySQL表的增删查改

目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表&#xf…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...