秋招Day10 - JVM - 内存管理

article/2025/8/17 16:24:24

JVM组织架构主要有三个部分:类加载器、运行时数据区和字节码执行引擎

  • 类加载器:负责从文件系统、网络或其他来源加载class文件,将class文件中的二进制数据加载到内存中
  • 运行时数据区:运行时的数据存放的区域,分为方法区、堆、虚拟机栈、本地方法栈和程序计数器
  • 字节码执行引擎:用来运行Java字节码,主要包括解释器和JIT编译器

虚拟机栈

Java 虚拟机栈的生命周期与线程相同。

当线程执行一个方法时,会创建一个对应的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,然后栈帧会被压入虚拟机栈中。当方法执行完毕后,栈帧会从虚拟机栈中移除。

一个空方法如果没有任何变量和参数,局部变量表一定为空吗?

不一定,对于非静态方法,局部变量表中会有一个this引用,指向当前实例;对于静态方法,由于不需要通过this访问实例,所以局部变量表中的变量数量为0(在字节码中体现为locals=0)

本地方法栈

和虚拟机栈类似,但是本地方法栈是为了非Java语言编写的方法(C/C++)服务的,每个栈帧中保存了局部变量表,动态链接,方法出口(没有操作数栈

本地方法栈的运行场景?

当Java应用需要和操作系统底层或硬件进行交互式,需要用到本地方法,比如内存管理,文件操作,系统时间、系统调用

还有JVM自身的一些底层功能也需要用到本地方法,比如过Object里的hashCode(), clone()

native方法

native方法在Java语言中用来修饰本地方法,用来调用非Java语言编写的方法,Java可以通过JNI(Java Native Interface)来和操作系统底层、硬件或本地库进行交互

堆内存

堆中的实例如果不再被任何变量引用,最后会被垃圾收集器回收

方法区

方法区只是一个逻辑概念,并非实际存在。主要存放已被加载的类信息,常量,静态变量。

在JVM的HotSpot实现中,方法区被替换为永久代,但在Java8以后的版本,已经被元空间替代。

JDK 1.6、1.7、1.8内存区域变化

JDK1.6中,方法区的实现是永久代,存放的内容是已被加载的类信息,静态变量(常量)、常量池

JDK1.7中,仍然是永久代,但字符串常量池和静态变量被移动到堆空间中去了,其他信息仍然在永久代中

JDK1.8之后,方法区的实现替换为了元空间(直接内存),并将运行时常量池、类常量池都移动到了元空间。

为什么用元空间替换永久代?

因为永久代使用JVM内存,而元空间使用本地内存。永久代收到JVM内存大小的限制,容易造成内存溢出,而元空间是在直接内存中开辟的,不容易造成内存溢出。其次,永久代的GC触发条件苛刻,回收频率很低

对象创建过程

当使用new创建一个对象时,JVM会首先检查new指令(字节码)的参数(比如说#2)是否能在运行时常量池中的对应索引处找到符号引用(如 "java/lang/String"),然后检查这个符号引用是否经过了类加载,如果没有,就先执行类加载。

如果已经加载,则

  1. 分配内存
  2. 完成对象内存初始化(赋初始值)
  3. 设置对象头(包括类元数据指针,GC年龄分代等信息)
  4. 执行init方法完成赋值操作

对象的销毁过程

如果对象不再被任何强引用所指向,也就是说JVM用可达性算法判断出这个对象无法通过强引用链到达该,就会被GC垃圾回收器销毁,有标记清除、标记复制、标记整理三种回收算法。

堆内存如何分配?

指针碰撞

如果堆内存完美的分为了两部分:已使用和未使用,有一个指针指向了下一个可分配内存位置的起始地址,然后向后移动新对象大小的空间,如果没有发生碰撞(没有超过堆内存边界)原来指针和新指针之间的部分就是为该对象分配的内存

指针碰撞适合没有内存碎片的情况。

空闲列表

Java维护一个列表,列表中记录了还未被使用的空闲的内存块,每个内存块都有大小和地址信息。

当有新对象要分配内存时,JVM会遍历空闲列表找出能够放下新对象的内存块。

如果内存块空间未被完全利用,则会作为一个新的内存块放入空闲列表中。

空闲列表适用于内存碎片化较严重对象大小差异较大的场景如老年代。

new对象时,堆会发生抢占吗?

会。比如用指针碰撞分配内存时:

  • 两个线程同时读取当前指针位置P
  • 线程A计算:P + 50(s的大小)
  • 线程B计算:P + 100(l的大小)
  • 两个线程可能同时更新指针

两个对象被分配到相同或重叠的内存区域

JVM的解决办法是为每个线程预留了一小块内存空间,称为TLAB,用于存放该线程创建的对象。如果TLAB的最大阈值已经不够新对象存放,才会使用全局指针在堆中分配。

// 伪代码流程
if (对象大小 <= TLAB剩余空间) {在TLAB中分配;
} else if (对象大小 > TLAB最大阈值) {直接在堆共享区域分配;
} else {申请新TLAB并在其中分配;
}

对象的内存布局

不同的JVM实现不同,以HotSpot为例:

对象在内存中包括三部分:

  • 对象头:包括类的元数据指针(指向方法、字段信息)和对象标记Mark Word(包括哈希码GC分代年龄信息、锁状态标志、线程持有的锁,偏向锁ID等)
  • 实例数据:也就是对象成员变量的值,JVM可能会对这些数据进行重排/对齐,以提高访问效率
  • 对齐填充:确保是八字节的整数倍,因为CPU一次寻址的指针大小是8字节,正好是L1缓存行的大小,如果不进行对齐,那么可能会导致跨行访问,导致额外的缓存行加载,导致访问效率降低。是一种以空间换时间的方式

类的元数据指针可能被压缩,压缩默认开启,压缩后占4个字节,压缩前占8个字节。

new Object()的对象内存大小是多少?

Object对象没有实例数据,所以占用内存大小为8字节的对象标记Mark Word + 4字节的压缩后的类元数据指针 + 0字节的数组对象专用字段 + 0字节的实例数据 + 4字节的对齐填充 = 16字节

JOL查看内存对象布局

引入依赖:

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version>
</dependency>
public class JOLSample {public static void main(String[] args) {// 打印JVM详细信息(可选)System.out.println(VM.current().details());// 创建Object实例Object obj = new Object();// 打印Object实例的内存布局String layout = ClassLayout.parseInstance(obj).toPrintable();System.out.println(layout);}
}

可以看到有 OFFSET、SIZE、TYPE DESCRIPTION、VALUE 这几个信息。

  • OFFSET:偏移地址,单位字节;
  • SIZE:占用的内存大小,单位字节;
  • TYPE DESCRIPTION:类型描述,其中 object header 为对象头;
  • VALUE:对应内存中当前存储的值,二进制 32 位;

对象的引用大小

Object o = new Object(); 

64位JVM上,未开启压缩指针时,对象的引用大小是8字节;开启压缩指针后,对象的引用大小是4字节。

引用类型的成员变量中,引用存放在该成员变量对象内存的实例数据中

  • 基本类型成员变量 → 直接存储值
  • 引用类型成员变量 → 存储引用(对象地址)

Object o = new Object();大小?

class MyClass {Object o = new Object();  // o引用存储在MyClass对象的实例数据中
}

MyClass对象:
├── 对象头
├── 实例数据
│   └── o引用 (4字节) → 指向Object对象
└── 对齐填充

Object对象:
├── 对象头 (12字节)
├── 实例数据 (0字节)
└── 对齐填充 (4字节)
总计:16字节

总占用:4 + 16 = 20字节

 

JVM如何访问对象?

句柄和直接指针。

句柄

通过一个中间的句柄表来访问对象:

优点是对象移动时只需要改变句柄池中的地址,而不需要改变引用本身的指向。

直接指针访问

引用直接存储的是对象内存的地址,直接通过该对象内存的地址访问到类型信息,实例数据等。

优点是访问速度更快,缺点是如果对象移动需要更新访问地址。

HotSpot默认使用直接指针。

对象有哪几种引用? 

四种,强引用、软引用、弱引用和虚引用

强引用是 Java 中最常见的引用类型。使用 new 关键字赋值的引用就是强引用,只要强引用关联着对象,垃圾收集器就不会回收这部分对象,即使内存不足。

软引用于描述一些非必须对象,通过 SoftReference 类实现。软引用的对象在内存不足时会被回收。 

弱引用用于描述一些短生命周期的非必须对象,如 ThreadLocalMap 中的 Entry,就是通过 WeakReference 类实现的。弱引用的对象会在下一次垃圾回收时会被回收,不论内存是否充足。

虚引用主要用来跟踪对象被垃圾回收的过程,通过 PhantomReference 类实现。虚引用的对象在任何时候都可能被回收。 无法通过虚引用直接获取对象,必须配合ReferenceQueue

Java堆内存分区

分为新生代和老年代两个区域。新生代又分为Eden区,Survivors From和To

新创建的对象都分配在Eden区,当Eden区填满会经历一次Minor GC,存活的对象会被移动到Survivor区。

任何时刻只有一个Survivor区有对象,因为新生代的垃圾收集主要采用标记-复制算法(Eden到Survivor采用简化的复制算法,仅通过引用链追踪),因为新生代的存活对象比较少,每次复制少量的存活对象效率比较高
    •    这个有对象的区叫from区
    •    空的那个区叫to区
    •    每次GC将存活对象从from复制到to
    •    GC完成后,原to变成新from,原from被清空变成新to

如果新生代的对象经过多次GC后仍然存活,会被移动到老年代。

当老年代内存不足时,会触发一次Major GC,对老年代垃圾回收。

对象什么时候进入老年代?

怎么算长期存活? 

JVM维护一个“年龄计数器”,每次新生代中Minor GC未被回收的对象,年龄计数器+1,当年龄超过一个特定阈值(默认15),就会被放入老年代

怎么算大对象?

阈值大小由 JVM 参数 -XX:PretenureSizeThreshold 控制,但在 JDK 8 中,默认值为 0,也就是说默认情况下,对象仅根据 GC 存活的次数来判断是否进入老年代

G1 垃圾收集器中,大对象会直接分配到 HUMONGOUS 区域。当对象大小超过一个 Region 容量的 50% 时,会被认为是大对象。

什么是动态年龄判断? 

如果Survivor区中所有对象的总大小超过了一定比例(通常是50%),那么年龄较小的对象也可能提前进入老年代。

这是因为如果年龄较小的对象在 Survivor 区中占用了较大的空间,会导致 Survivor 区中的对象复制次数增多,影响垃圾回收的效率。

STW

STW是指Stop The World,会暂停所有用户线程的执行。这是为了保证对象引用在移动过程中不会被修改

GC前如何暂停线程?

JVM会使用一个叫做安全点(safe point)的机制确保线程能够安全的被暂停,包括四个步骤:

  1. JVM发出暂停信号
  2. 线程执行到安全点后,自行挂起并等待GC完成
  3. 垃圾回收器完成GC操作
  4. 线程继续执行

什么是安全点?

收到暂停信号后,线程执行到特定位置就暂停执行,挂起自身,这个特定位置叫做安全点,保证线程暂停执行时的数据一致性和状态完整性(引用关系完整可追踪,不会是中间状态)。通常位于方法调用、循环跳转、异常处理等位置。

逃逸

分为方法逃逸和线程逃逸

方法逃逸是指某个对象是否由于返回、复制到全局变量导致逃逸到方法之外,如果逃逸则必须分配到堆中,没有逃逸就进行栈上分配 / 标量替换。

线程逃逸是指某个对象被另一个线程引用,生命周期超出了当前线程,那么该对象也应该分配到堆中。

逃逸分析的好处

  • 降低垃圾回收的压力
  • 线程逃逸分析可以避免对不逃逸的对象加锁,因为变量不会逃逸出线程,不会被其他线程修改
  • 如果对象的字段在方法中独立使用,那么可以进行标量替换,避免对象分配(对象甚至都不会在栈上分配)

 

内存溢出和内存泄漏

内存溢出(OOM)是指程序请求分配内存时没有新的空间了,或者说空间不够了。

内存泄漏是指本应该短期存活的对象没有被及时释放,导致占用的内存无法被使用,久而久之造成内存溢出。

内存泄漏是缺陷,内存溢出是最终结果

手写内存溢出例子

public class HeapSpaceErrorGenerator {public static void main(String[] args) {List<byte[]> list = new ArrayList<>();try {while (true){byte[] bytes = new byte[10 * 1024 * 1024];list.add(bytes);}} catch (OutOfMemoryError e) {System.out.println("OutOfMemoryError 发生在 " + list.size() + " 对象后");throw e;}}
}

每一次的bytes引用都存放到了list中,导致其指向的对象的引用链一直存在,无法被垃圾回收,导致内存泄露

内存泄漏的原因

  • 一旦经过了类加载,静态的集合成员变量的生命周期就和应用程序相同,添加强引用时,其内部的元素都具有强引用链,如果没有主动释放集合中的引用,导致其内部元素无法被垃圾回收,可能导致内存泄漏。
  • 单例模式下对象持有的外部引用无法及时释放;单例对象在整个应用程序的生命周期中存活,如果单例对象持有其他对象的引用且没有主动释放这些引用,这些对象将无法被回收。
  • 数据库、IO、Socket 等连接资源没有及时关闭;
  • ThreadLocalMap的key被清理后,仍然持有value的强引用。在线程执行完后,要调用 ThreadLocal 的 remove 方法进行清理。

处理过内存泄露问题吗?

处理过ThreadLocal 没有及时清理导致出现的内存泄漏问题。

  • jsp -l查看运行的进程id
  • top -p [pid]查看进程的CPU和内存占用情况
  • top -Hp [pid]查看进程下所有线程占用的CPU和内存情况
  • jstack -F [pid] > [pid].txt抓取线程,查看有无死锁、死循环或长时间等待的问题
  • jstat -gcutil [pid] 5000 10每隔 5 秒输出 GC 信息,输出 10 次,查看 YGC 和 Full GC 次数。如果YCG增长缓慢,Full GC快速增长,那就可能存在内存泄露
  • jmap -dump:format=b,file=heap.hprof 10025生成dump文件
  • 使用VisualVM图形化工具装入dump文件,在结果中观察占用内存最多的对象

处理过内存溢出问题吗?

使用jmap -dump命令生成Heap Dump文件,使用工具进行分析,查看内存中对象占用情况

检查是否有未关闭的资源,是否有长生命周期对象

什么情况下会发生栈溢出?

程序调用栈的深度超过JVM的限制时,本质是线程的栈空间不足,无法再为新的栈帧分配内存,最常见的场景是递归调用,递归返回条件设置不当。


http://www.hkcw.cn/article/FVJASUvSsT.shtml

相关文章

茅台经销商被罚后起诉市监局续:法院重审一审撤销处罚决定

“贵州茅台经销商被罚后起诉市监局”一案近日有了最新进展。澎湃新闻从当事人及其代理律师处了解到,贵州省都匀市人民法院日前对该案作出重审一审判决,认定案涉处罚决定适用法律错误、程序违法,决定撤销黔南州市场监督管理局此前作出的行政处罚决定。澎湃新闻此前报道,郭亮…

花生壳里塞冰淇淋卖28一个 真果壳制成引争议

花生壳里塞冰淇淋卖28一个 真果壳制成引争议!近日,一位女子发布视频称,在苏州一家餐厅遇到了价格高昂的冰淇淋。她提到,一份装在花生壳里的冰淇淋售价28元,分量却少得惊人。尽管服务员会帮忙切开,仪式感十足,但她认为并不值这个价格。记者随后走访了位于苏州市姑苏区美罗…

严重或可能导致死亡!不要空腹吃荔枝 了解“荔枝病”真相

最近,话题#荔枝病突然成为热门话题。广东一名女子因过量食用荔枝后,次日出现头晕不适、持续性鼻出血等症状,最终被诊断为“荔枝病”。科普博主表示,此病严重时会引发休克甚至死亡。荔枝病也称为低血糖急性脑炎综合征,发病人群以4至11岁的儿童较为多见。一旦发生,会导致人…

工信部曝光:“腾讯支付”有诈!冒名诈骗需警惕

工信部反诈专班近日发布消息称,有用户举报发现了一款名为“腾讯支付”的理财APP。经与腾讯官方核实,这款APP并非大众熟知的“微信支付”。实际上,微信支付仅支持在微信应用内使用,并没有独立的APP。所谓的“腾讯支付”APP冒充腾讯集团名义,恶意使用腾讯名称,虚构腾讯股权…

郑州最长寿奶奶根本闲不住:我115岁,成大人了

5月28日河南新密,“我115岁,成大人了!”郑州最长寿奶奶根本闲不住,推车散步,眼神好,能爬4楼,还爱洗碗。70岁小女儿:每次回家喊一声妈,很幸福。责任编辑:zx0002

男子家暴被捕后与弟弟弟媳造假借条起诉妻子追债 三人因虚假诉讼被判刑

被丈夫殴打导致4根肋骨骨折后,刘颖报了警,并向法院起诉离婚。而丈夫的弟弟也起诉了她——要她偿还三百多万“债务”。几年下来,刘颖和丈夫的“夫妻官司”牵涉了离婚案、故意伤害案、民间借贷案。其后,刘颖的丈夫李某涛,以及他的弟弟、弟媳三人,均被湖南邵阳的一审法院以虚…

HTML 计算网页的PPI

HTML 计算网页的PPI vscode上安装live server插件&#xff0c;可以实时看网页预览 有个疑问&#xff1a; 鸿蒙density是按照类别写死的吗&#xff0c;手机520dpi 折叠屏426dpi 平板360dpi <html lang"en" data - overlayscrollbars - initialize><header&…

Axure RP11安装、激活、汉化

一:注册码 Axure RP11.0.0.4122在2025-5-29日亲测有效: 49bb9513c40444b9bcc3ce49a7a022f9

300斤巨石砸中汽车 司机幸运逃生 地质灾害点雨后落石

300斤巨石砸中汽车 司机幸运逃生 地质灾害点雨后落石!5月28日,贵州毕节市七星关区何官屯镇一条通村公路上发生落石事故。一块约300斤重的巨石砸中一辆过路汽车,导致车辆从路边高坎坠落。司机受轻伤,送医检查后当日返回家中,车损由保险公司处理。落石还击碎了附近民房的玻璃…

高校校长论文抄袭复制比高达90%?校方:属实,免职

近日,“烟台科技学院校长硕士论文涉嫌严重抄袭”一事引发社会广泛关注。5月29日,烟台科技学院就有关情况发布声明:经查核,情况属实。学校董事会研究决定,免去马红坤烟台科技学院校长职务。据媒体此前报道,马红坤2007年在南昌大学法学院获得硕士学位的论文涉嫌严重抄袭,复…

油价将调整!92号汽油或重返“7元时代” 猪价波动影响市场情绪

油价将调整!92号汽油或重返“7元时代” 猪价波动影响市场情绪!五月渐入尾声,生猪市场数据显示,月内猪价呈现波动走低的趋势。5月1日标猪报价为14.82元/公斤,2-3号稳定在14.85元/公斤,但从5月4日起,价格盘整在14.79~14.82元/公斤之间。进入中旬后,猪价降幅逐渐扩大,至5…

广东虎门通报小车坠桥致5死 事故原因正在调查中

广东虎门通报小车坠桥致5死 事故原因正在调查中。近日,广东东莞环莞快速路虎门段发生了一起交通事故,引起了广泛关注。5月19日,有网友反映其侄儿驾车经过该路段时,因四车道突然收窄为三车道导致车辆失控,从高架冲出路面坠落,造成5人伤亡。5月29日晚,广东省东莞市虎门镇发…

韩媒总结航空事故频发原因 安全信任危机

韩媒总结航空事故频发原因 安全信任危机!韩国再爆坠机悲剧,海军巡逻机7分钟内坠毁,4人遇难引发安全信任危机。5月29日下午,韩国庆尚北道浦项市上空传来一声巨响,一架海军P-3C反潜巡逻机在起飞仅7分钟后撞山坠毁,机上4名机组人员全部遇难。这起事故不仅让韩国海军颜面尽失…

气象局回应茂名突发巨响:突然一声巨响,天都被照亮了

气象局回应茂名突发巨响。5月28日晚,多位网友发布视频称当日21点33分左右,广东茂名天空出现疑似“陨石坠落”现象:爆闪强光瞬间点亮夜空,瞬间黑夜宛如白昼。有目击者称不久后听到一声巨响:“突然一声巨响,天都被照亮了。”据@中国气象爱好者:一颗火流星划破粤西的夜空,…

12:遨博机器人开发

一、流程步骤 1.获取当前点关节坐标 2.走当前点关节坐标 1.获取目标点x,y,z&#xff08;位置坐标&#xff0c;以m为单位&#xff0c;需要*1000变成mm&#xff09;和四元素&#xff08;位姿坐标&#xff09; 2.四元素→欧拉角&#xff08;弧度制&#xff09; 3.欧拉角&#x…

婚内强奸案当事人已与妻子协议离婚 申请国家赔偿33万

婚内强奸案当事人已与妻子协议离婚 申请国家赔偿33万!5月29日下午,40岁的尹某在律师的陪同下,向河南省濮阳县人民检察院递交了《国家赔偿申请书》,申请国家赔偿33万元,并要求追责相关办案人员。此前,尹某因“婚内强奸”被羁押了285天。在未被关押之前,尹某妻子两次起诉离…

女厅官杨慧被双开 曾花4000万买别墅 权钱交易终落马

女厅官杨慧被双开曾花4000万买别墅!贵州省纪委监委对中共贵州省第十三届委员会委员、贵州省卫生健康委员会原党组书记杨慧严重违纪违法问题进行了立案审查调查。调查显示,杨慧丧失理想信念,背离初心使命,搞政治攀附;违背组织原则,不按规定报告个人有关事项,在干部调动工…

谢锋说哪里有封锁哪里就有突围 科技创新勇往直前

中国驻美大使谢锋在华盛顿的一次活动中谈及中国的科技创新历程时提到,面对封锁和风浪,中国总能找到突围之路。此次活动由中国驻美国大使馆举办,名为“我的中国相册——我的中国足迹”影片首映会暨现代化的中国体验活动。谢锋在致辞中强调,中国科技创新历经重重困难持续升级…

年轻人开始在生产线上操作机械臂 蓝领知识化趋势显现

富士康郑州科技园是苹果全球最大的生产基地之一。富士康的郑州厂区已有15年的历史,从iPhone 4s一直延续到最新的iPhone 16系列。一些制程已经发展到全自动化,比如电路板工厂高度自动化,技术含量高,处于全球领先水平。苹果在华供应商正在参与制造苹果所有最先进的产品,苹果…

学习路之PHP--easyswoole操作数据库

学习路之PHP--easyswoole操作数据库 0、安装orm插件一、创建数据库二、创建模型三、控制器显示四、效果五、问题 0、安装orm插件 composer require easyswoole/orm一、创建数据库 表&#xff1a; CREATE TABLE cases (id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键,titl…