JVM之内存管理(一)

news2025/5/10 11:01:01

部分内容来源:JavaGuide+二哥Java


图解JVM内存结构


内存管理快速复习

栈帧:局部变量表,动态链接(符号引用转为真实引用),操作数栈(存储中间结算结果),方法返回地址

运行时常量池:常量池表,符号引用,字面量

对象创建过程:类加载检查(类没有加载就进行类加载)+分配内存+初始化零值+设置对象头+执行对象初始化方法

类加载过程:

  1. 加载:通过类的全限定名获取该类的二进制字节流,存到类常量池,内存中生成Class类对象
  2. 连接:
    1. 验证:验证Class二进制字节流合规(例如验证魔数和版本号)
    2. 准备:为类对象分配内存
    3. 解析:符号引用转为直接引用
  1. 初始化:执行初始化方法

内存分配:指针碰撞(CAS重试)+空闲列表

内存分配失败:CAS配上重试机制+TLAB为每个线程预先在Eden区分配一块内存

对象:对象头(运行时数据+类型指针,GC年龄,Hash码)+实例数据

对象访问定位:使用句柄(间接访问)+直接指针访问

JVM堆内存分区:新生代(Eden+S1+S2)+老年代

对象什么时候会进入老年代:长期存活的对象,大对象,动态年龄判断分配担保机制

逃逸分析:在栈内为对象分配内存

Stop The World :停止所有用户线程

Oop Map:记录了对象内部所有引用字段(指针)的位置

安全点:可以暂停所有线程执行特定操作的位置

常量池包括:

类常量池

运行时常量池

字符串常量池

JDK1.6:常量池在永久代

JDK1.7:运行时常量池+类常量池在永久代,字符串常量池在堆

JDK1.8:运行时常量池+类常量池在元空间,字符串常量池在堆


引用类型有哪些?有什么区别?

强引用指的就是代码中普遍存在的赋值方式,比如 A a = new A () 这种。强引用关联的对象,永远不会被 GC 回收

软引用可以用 SoftReference 来描述,指的是那些有用但是不是必须要的对象

系统在发生内存溢出前会对这类引用的对象进行回收

弱引用可以用 WeakReference 来描述,他的强度比软引用更低一点

弱引用的对象下一次 GC 的时候一定会被回收,而不管内存是否足够

虚引用也被称作幻影引用,是最弱的引用关系,可以用 PhantomReference 来描述,他必须和 ReferenceQueue 一起使用,同样的当发生 GC 的时候,虚引用也会被回收

可以用虚引用来管理堆外内存


说一下弱引用?举例子在哪里可以引用?

Java 中的弱引用是一种引用类型,它不会阻止一个对象被垃圾回收


在 Java 中,弱引用是通过 Java.lang.ref.WeakReference 类实现的

弱引用的一个主要用途是创建非强制性的对象引用,这些引用可以在内存压力大时被垃圾回收器清理,从而避免内存泄露


弱引用的使用场景:

  • 缓存系统:弱引用常用于实现缓存,特别是当希望缓存项能够在内存压力下自动释放时。如果缓存的大小不受控制,可能会导致内存溢出。使用弱引用来维护缓存,可以让 JVM 在需要更多内存时自动清理这些缓存对象。
  • 对象池:在对象池中,弱引用可以用来管理那些暂时不使用的对象。当对象不再被强引用时,它们可以被垃圾回收,释放内存。
  • 避免内存泄露:当一个对象不应该被长期引用时,使用弱引用可以防止该对象被意外地保留,从而避免潜在的内存泄露

说一下你对内存泄露和内存溢出的了解

什么是内存泄露:

内存泄漏是指程序在运行过程中不再使用的对象仍然被引用而无法被垃圾收集器回收,从而导致可用内存逐渐减少

虽然在 Java 中,垃圾回收机制会自动回收不再使用的对象,但如果有对象仍被不再使用的引用持有,垃圾收集器无法回收这些内存,最终可能导致程序的内存使用不断增加


内存泄露常见原因:

  • 静态集合:使用静态数据结构(如 HashMap 或 ArrayList)存储对象,且未清理。
  • 事件监听:未取消对事件源的监听,导致对象持续被引用。
  • 线程没被回收:未停止的线程可能持有对象引用,无法被回收。

内存溢出:

内存溢出是指 Java 虚拟机(JVM)在申请内存时,无法找到足够的内存,最终引发 OutOfMemoryError 。这通常发生在堆内存不足以存放新创建的对象时


内存溢出常见原因:

  • 大量对象创建:程序中不断创建大量对象,超出 JVM 堆的限制。
  • 持久引用:大型数据结构(如缓存、集合等)长时间持有对象引用,导致内存累积。
  • 递归调用:深度递归导致栈溢出

JVM的内存泄露有几种溢出情况?

堆内存溢出:当出现 Java.lang.OutOfMemoryError:Java heap space 异常时,就是堆内存溢出了。原因是代码中可能存在大对象分配,或者发生了内存泄露,导致在多次 GC 之后,还是无法找到一块足够大的内存容纳当前对象

栈溢出:如果我们写一段程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。类似这种情况,JVM 实际会抛出 StackOverFlowError;当然,如果 JVM 试图去扩展栈空间的时候失败,则会抛出 OutOfMemoryError

元空间溢出:元空间的溢出,系统会抛出 Java.lang.OutOfMemoryError: Metaspace。出现这个异常的问题的原因是系统的代码非常多或引用的第三方包非常多或者通过动态代码生成类加载等方法,导致元空间的内存占用很大

直接内存内存溢出:在使用 ByteBuffer 中的 allocateDirect () 的时候会用到,很多 JavaNIO (像 netty) 的框架中被封装为其他的方法,出现该问题时会抛出 Java.lang.OutOfMemoryError: Direct buffer memory 异常


栈中存的是指针还是对象?

在 JVM 内存模型中,栈(Stack)主要用于管理线程的局部变量和方法调用的上下文

而堆(Heap)则是用于存储所有类的实例和数组


当我们在栈中讨论 “存储” 时,实际上指的是存储基本类型的数据(如 int, double 等)和对象的引用,而不是对象本身。


这里的关键点是,栈中存储的不是对象,而是对象的引用

也就是说,当你在方法中声明一个对象,比如 MyObject obj = new MyObject ();,

这里的 obj 实际上是一个存储在栈上的引用,指向堆中实际的对象实例

这个引用是一个固定大小的数据(例如在 64 位系统上是 8 字节),它指向堆中分配给对象的内存区域


说一下程序计数器的作用?为什么程序计数器是私有的?

Java 程序是支持多线程一起运行的,多个线程一起运行的时候 cpu 会有一个调动器组件给它们分配时间片,比如说会给线程 1 分给一个时间片,它在时间片内如果它的代码没有执行完,它就会把线程 1 的状态执行一个暂存,切换到线程 2 去,执行线程 2 的代码,等线程 2 的代码执行到了一定程度,线程 2 的时间片用完了,再切换回来,再继续执行线程 1 剩余部分的代码


我们考虑一下,如果在线程切换的过程中,下一条指令执行到哪里了,是不是还是会用到我们的程序计数器啊

没个线程都有自己的程序计数器,因为它们各自执行的代码的指令地址是不一样的呀,所以每个线程都应该有自己的程序计数器


说一下方法区中方法的执行过程

当程序中通过对象或类直接调用某个方法时,主要包括以下几个步骤:

解析方法调用:JVM 会根据方法的符号引用找到实际的方法地址(如果之前没有解析过的话)

栈帧创建:在调用一个方法前,JVM 会在当前线程的 Java 虚拟机栈中为该方法分配一个新的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息

执行方法:执行方法内的字节码指令,涉及的操作可能包括局部变量的读写、操作数栈的操作、跳转控制、对象创建、方法调用等。

返回处理:方法执行完毕后,可能会返回一个结果给调用者,并清理当前栈帧,恢复调用者的执行环境


JVM内存中的栈和堆有什么区别?

用途

栈主要用于存储局部变量、方法调用的参数、方法返回地址以及一些临时数据。每当一个方法被调用,一个栈帧(stack frame)就会在栈中创建,用于存储该方法的信息,当方法执行完毕,栈帧也会被移除

堆用于存储对象的实例(包括类的实例和数组)。当你使用 new 关键字创建一个对象时,对象的实例就会在堆上分配空间


生命周期

栈中的数据具有确定的生命周期,当一个方法调用结束时,其对应的栈帧就会被销毁,栈中存储的局部变量也会随之消失

堆中的对象生命周期不确定,对象会在垃圾回收机制(Garbage Collection, GC)检测到对象不再被引用时才被回收


存取速度

栈的存取速度通常比堆快,因为栈遵循先进后出(LIFO, Last In First Out)的原则,操作简单快速

堆的存取速度相对较慢,因为对象在堆上的分配和回收需要更多的时间,而且垃圾回收机制的运行也会影响性能


存储空间

栈的空间相对较小,且固定,由操作系统管理

当栈溢出时,通常是因为递归过深或局部变量过大

堆的空间较大,动态扩展,由 JVM 管理

堆溢出通常是由于创建了太多的大对象或未能及时回收不再使用的对象


可见性

栈中的数据对线程是私有的,每个线程有自己的栈空间。堆中的数据对线程是共享的,所有线程都可以访问堆上的对象


static修饰的类型

未被static修饰的基本类型:基本类型的变量(局部变量)存放在栈中,而不是堆中。例如int num = 10;,变量num是在方法执行时在栈中开辟空间存储的。基本类型的成员变量存放在堆中(当所属对象在堆中时) 。

static修饰的基本类型:被static修饰的基本类型(静态变量)存放在方法区(在 JDK 8 及之后,方法区的实现是元空间),而不是栈中。静态变量属于类,在类加载时就会分配空间并初始化,存储在方法区供类的所有对象共享。

包装类型:包装类型属于对象类型,其对象实例确实几乎都存在堆中,但包装类型的对象在某些场景下会有缓存机制。例如Integer-128127之间的值会被缓存,当创建这个范围内的Integer对象时,不会在堆中重新创建,而是直接引用缓存中的对象

此外,部分包装类(如IntegerShortByteCharacterLong)存在对象缓存机制。Integer为例,在创建-128127之间的Integer对象时,不会在堆中重新创建,而是直接引用方法区中缓存的对象;超出这个范围才会在堆中创建新对象


线程的内部有什么?

程序计数器

本机方法栈

Java虚拟机栈


说一下JVM栈的内部组成

JVM栈的内部组成

Java虚拟机栈是线程私有的
它的生命周期和线程相同
除了Native方法是调用本地方法栈实现,其他的所有方法的调用都是通过Java虚拟机栈来实现的
Java虚拟机栈的内部是由栈帧组成
方法调用的数据需要通过栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出


Java虚拟机栈的内部是由一个又一个的栈帧组成
栈帧内部:局部变量表、操作数栈、动态链接、方法返回地址
 


说一下栈帧的内部

局部变量表

存放了数据类型,对象引用


操作数栈

主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果
另外,计算过程中产生的临时变量也会放在操作数栈中

动态链接


场景:主要服务一个方法需要调用其他方法的场景
Class 文件的常量池里保存有大量的符号引用比如方法引用的符号引用。
当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用


方法返回地址

就是我们方法结束后的返回地址


说一下栈帧内部有啥?

局部变量表

操作数栈

动态链接

方法返回地址


说一下JVM栈会出现的问题

tackOverFlowError错误:栈帧过多爆了

函数循环调用过多

我们这个线程递归调用的时候,我们会往栈里面压入栈帧,如果压入的栈帧过多,就会爆出

Java方法的两种返回方式

一:Returen正常返回

二:抛出异常


OutOfMemoryError:内存空间不够爆了

虚拟机动态扩展栈时,无法申请到足够的内存空间


什么是本地方法栈

为本地方法服务

(也就是和我们的操作系统有关,我们的操作系统的方法)


说一下JVM的内存区域

JVM 内存区域最粗略的划分可以分为

当然,按照虚拟机规范,可以划分为以下⼏个区域:

JVM 内存分为线程私有区线程共享区

线程共享区:方法区和堆

线程隔离的数据区: 虚拟机栈 、本地方法栈 和 程序计数器

1)程序计数器

程序计数器(Program Counter Register)也被称为 PC 寄存器,是⼀块较⼩的内存空间。

它可以看作是当前线程所执⾏的字节码的⾏号指示器。

2Java 虚拟机栈

Java 虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的⽣命周期与线程相同。

Java 虚拟机栈描述的是 Java ⽅法执⾏的线程内存模型:⽅法执⾏时,JVM 会同步创建⼀个栈帧,⽤来存储局部变量表、操作数栈、动态连接等。

3)本地方法栈

本地⽅法栈(Native Method Stacks)与虚拟机栈所发挥的作⽤是⾮常相似的,其区别只是虚拟机栈为虚拟机执⾏

Java ⽅法(也就是字节码)服务,⽽本地⽅法栈则是为虚拟机使⽤到的本地(Native)⽅法服务。

Java 虚拟机规范允许本地⽅法栈被实现成固定⼤⼩的或者是根据计算动态扩展和收缩的。

4Java

对于 Java 应⽤程序来说,Java 堆(Java Heap)是虚拟机所管理的内存中最⼤的⼀块。Java 堆是被所有线程共享

的⼀块内存区域,在虚拟机启动时创建。此内存区域的唯⼀⽬的就是存放对象实例,Java ⾥“几乎”所有的对象实例

都在这⾥分配内存。Java 堆是垃圾收集器管理的内存区域,因此⼀些资料中它也被称作“GC 堆”(Garbage Collected Heap,)。从回

收内存的⻆度看,由于现代垃圾收集器⼤部分都是基于分代收集理论设计的,所以 Java 堆中经常会出现 新⽣代 、⽼年代 、 Eden空间 、 From Survivor空间 、 To Survivor空间 等名词,需要注意的是这种划分只是根据垃圾回收机制来进⾏的划分,不是 Java 虚拟机规范本身制定的

5)方法区

⽅法区是⽐较特别的⼀块区域,和堆类似,它也是各个线程共享的内存区域,⽤于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

它特别在 Java 虚拟机规范对它的约束⾮常宽松,所以⽅法区的具体实现历经了许多变迁,例如 jdk1.7 之前使⽤永久代作为⽅法区的实现


JVM的堆是用来干嘛的

Java 虚拟机所管理的内存中最大的一块

Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建

此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存


什么是逃逸分析

jdk1.7之后,已经默认开启逃逸分析

也就是某些方法中的对象引用没有被返回或者未被外面使用,那么就可以直接在栈上分配内存

也就是我们不用在堆给这个对象分配内存,我们在栈上给这个对象分内存就可以了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2372249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙编译boost整合linux跨平台应用

openharmony deveco 4.1支持armeabi-v7a deveco 5.0后不支持arm32位系统 boost编译 使用deveco的写cmake集成boost boost使用1.88的最新版本,带cmake工具链 https://github.com/boostorg/boost.git boost的源码都在sub_module中 deveco 4.1的版本sdk最高到9&am…

rabbitMQ消息问题与解决

rabbitMQ 消息顺序性、消息幂等性、消息不丢失、最终一致性、补偿机制、消息队列设计 1.消息顺序性 溯源: 消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常…

Java SE(10)——抽象类接口

1.抽象类 1.1 概念 在之前讲Java SE(6)——类和对象(一)的时候说过,所有的对象都可以通过类来抽象。但是反过来,并不是说所有的类都是用来抽象一个具体的对象。如果一个类本身没有足够的信息来描述一个具体的对象,而…

学习threejs,使用Physijs物理引擎

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️Physijs 物理引擎1.1.1 ☘️…

allure生成测试报告(搭配Pytest、allure-pytest)

文章目录 前言allure简介allure安装软件下载安装配置环境变量安装成功验证 allure运行流程allure装饰器函数基本说明装饰器函数使用allure.attach 命令行运行利用allure-pytest生成中间结果json 查看测试报告总览页面每个tab页的说明类别页面测试套图表页面时间刻度功能页面包 …

龙虎榜——20250509

上证指数今天缩量,整体跌多涨少,走势处于日线短期的高位~ 深证指数今天缩量小级别震荡,大盘股表现更好~ 2025年5月9日龙虎榜行业方向分析 一、核心行业方向 军工航天 • 代表个股:航天南湖、天箭科技、襄阳轴承。 • 驱动逻辑…

操作系统的初步了解

目录 引言:什么是操作系统? 一、设计操作系统的目的 二、操作系统是做什么的: 操作系统主要有四大核心任务: 1. 管理硬件 2. 运行软件 3. 存储数据 4. 提供用户界面 如何理解操作系统的管理呢? 1. 什么是操作…

软件工程之软件项目管理深度解析

前文基础: 1.软件工程学概述:软件工程学概述-CSDN博客 2.软件过程深度解析:软件过程深度解析-CSDN博客 3.软件工程之需求分析涉及的图与工具:软件工程之需求分析涉及的图与工具-CSDN博客 4.软件工程之形式化说明技术深度解…

基于Boost库、Jsoncpp、cppjieba、cpp-httplib等构建Boost搜索引擎

⭐️个人主页:小羊 ⭐️所属专栏:项目 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 项目背景技术栈和项目环境正排索引和倒排索引数据去标签与清洗下载数据源去标签 建立索引构建正排索引构建倒排索引 建立搜索引擎h…

Qt 通过控件按钮实现hello world + 命名规范(7)

文章目录 使用编辑框来完成 hello world通过编辑图形化界面方式通过纯代码方式 通过按钮的方式来创建 hello world通过编辑图形化界面方式通过纯代码方式 总结Qt Creator中的快捷键如何使用文档命名规范 简介:这篇文章着重点并不在于创建hello world程序&#xff0c…

关于大数据的基础知识(二)——国内大数据产业链分布结构

成长路上不孤单😊😊😊😊😊😊 【14后😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于大数据的基础知识(二&a…

Winform(11.案例讲解1)

今天写两个案例,用于更好的理解控件的使用 在写之前先写一个类 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _1.案例讲解 { internal class Student { public string …

电机密集型工厂环境下的无线通信技术选型与优化策略

点击下面图片带您领略全新的嵌入式学习路线 🔥爆款热榜 88万阅读 1.6万收藏 在电机、变频器、电焊机等强电磁干扰源遍布的工业环境中,无线通信系统的可靠性面临严峻挑战。本文从抗干扰能力、传输稳定性、实时性需求三大核心维度出发,结合工…

一文了解氨基酸的分类、代谢和应用

氨基酸(Amino acids)是在分子中含有氨基和羧基的一类化合物。氨基酸是生命的基石,人类所有的疾病与健康状况都与氨基酸有直接或间接的关系。氨基酸失衡可引起肝硬化、神经系统感染性疾病、糖尿病、免疫性疾病、心血管疾病、肾病、肿瘤等各类疾…

系统学习算法:动态规划(斐波那契+路径问题)

题目一: 思路: 作为动态规划的第一道题,这个题很有代表性且很简单,适合入门 先理解题意,很简单,就是斐波那契数列的加强版,从前两个数变为前三个数 算法原理: 这五步可以说是所有…

JAVA房屋租售管理系统房屋出租出售平台房屋销售房屋租赁房屋交易信息管理源码

一、源码描述 这是一套房屋租售管理源码,基于SpringBootVue框架,后端采用JAVA开发,源码功能完善,涵盖了房屋租赁、房屋销售、房屋交易等业务。 二、源码截图

掌握Multi-Agent实践(三):ReAct Agent集成Bing和Google搜索功能,采用推理与执行交替策略,增强处理复杂任务能力

一个普遍的现象是,大模型通常会根据给定的提示直接生成回复。对于一些简单的任务,大模型或许能够较好地应对。然而,当我们面对更加复杂的任务时,往往希望大模型能够表现得更加“智能”,具备适应多样场景和解决复杂问题的能力。为此,AgentScope 提供了内置的 ReAct 智能体…

Bearer Token的神秘面纱:深入解析HTTP认证头的设计哲学

为何有些Token会带Bearer? 在接口测试与开发中,我们经常会遇到这样的请求头: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... 这个神秘的"Bearer"前缀从何而来?为何不直接使用Authorization: Token..…

【国产化】在银河麒麟ARM环境下离线安装docker

1、前言 采用离线安装的方式。 关于离线安装的方式官网有介绍,但是说的很简单,网址:Binaries | Docker Docs 官网介绍的有几种主流linux系统的安装方式,但是没有kylin的,所以在此记录一下。 在安装过程中也遇到了些…

java volatile关键字

volatile 是 Java 中用于保证多线程环境下变量可见性和禁止指令重排序的关键字。 普通变量不加volatile修饰有可见性问题,即有线程修改该变量值,其他线程无法立即感知该变量值修改了。代码: private static int intVal 0; // 普通变量未加 …