对于JVM的简单理解 一

一、对于JVM的内存区域

  • 简单来说 JVM的内存区域分为五个区
  • 根据特点可以分为三个:
  • 线程私有:程序计数器、虚拟机栈、本地方法栈
  • 线程共享:Java堆
  • 线程共享:方法区(JDK8以前:永久代;JDK8以后:元空间)

一、程序计数器

它存的是当前线程正在执行的字节码指令的地址 为什么需要它:CPU是时间片轮转调度的,一个线程随时可能因为CPU耗尽被切换出去,当该线程被切换回来时,怎么知道它运行到那一步了呢?这就需要程序计数器,它相当于线程运行时的一个"书签" 特点: 线程私有:好类比与每个线程有自己的书签,互不干扰 唯一不会发生OOM的区域:《JAVA虚拟机规范》里唯一没有规定任何OOM情况的区域。原因很简单------它只存一个固定大小的地址,不需要扩容,自然不会溢出。 一个细节:如果线程执行的是Java方法,计数器记录字节码地址。如果执行的是native方法,计数器值为空------因为native方法的执行不归JVM的字节码解释器管

二、Java虚拟机栈

它描述的是Java方法执行时的内存模型。每个方法被调用时,JVM就创建一个栈帧(Stack Frame)压入栈中;方法执行完,栈帧弹出。一个方法从调用到返回,就对应一个栈帧的入栈到出栈

栈帧由四个部分组成:

  1. 局部变量表:方法内的局部变量:基本类型存值本身,对象存引用地址
  2. 操作数栈:方法执行过程中的临时计算数据
  3. 动态链接:指向运行时常量池中该方法的引用,支持多态
  4. 方法返回地址:方法执行完后该回到哪里

三、本地方法栈

和虚拟机栈结构一样,只不过描述的是本地native的方法调用到结束的内存模型。HotSpot直接把两者合并实现,所以规范层面的两个区域在主流JVM里其实是同一块

四、Java堆

Java堆存储几乎所有的对象实例和数组。"几乎"是因为JIT编译器的逃逸分析可能让对象在栈上分配,但这时优化手段,语义上对象仍属于堆。

五、方法区

方法区存的是每个类的元数据(类名、父类、接口、字段方法描述)、运行时常量池、JIT编译后的代码缓存。需要注意的是它管的是"类"的信息而不是对象的信息------类是图纸,堆里的对象是依靠类造出的房子

二、对象分配 新生代 老生代

一、对象从出生到死亡,在堆里是怎么流转的

一、对象分配的四条路:越快越靠前

设计理念:能不仅共享区就不进,能不加锁就不加。分配是Java里最高频的操作,每一步优化都直接决定吞吐量。

  1. 栈上分配: 让对象"根本不进堆"

JIT编译做逃逸分析:如果分析出当前对象只在方法内部调用、不会逃出方法(没有被return,以及没有在外部引用),就不必在堆上分配------直接在栈帧里以"标量替换的方式存在"(把对象拆解成基本类型字段,直接放局部变量表)。方法返回,栈帧弹出,对象自动消失,零GC压力。 注意:栈上分配是JIT优化,代码写法不变。这也是为什么说"所有对象都在堆里"严格讲是不成立的。

  1. TLAB:堆里的私人车道

绝大多数对象逃逸出了方法,所以它就不能用JIT的栈帧优化,会进入堆内存。但是堆是共享的------两个线程同时new,在Eden区抢同一块内存,必须加锁同步,高频分配下开销巨大。 JVM的解法就是用TLAB(Thread Local Allocation Buffer):每个线程在Eden里预先圈一小块私有地,对象优先在自己地盘上分配,无锁,指针碰撞,几条指令搞定。TLAB用完了,才去共享的Eden区CAS分配(重试到成功)

  1. Eden区:新生的主战场

TLAB装不下的大对象,或TLAB耗尽后的分配,落在Eden区。Eden占新生代的8/10。 Eden满了 -> 触发MinorGC------新生代唯一的GC触发方式,不是定时任务,是分配失败驱动的

二、新生代:两个Survivor的设计智慧

复制算法的运作:Minor GC时,Eden里绝大部分对象已经死了。把Eden + From Survivor中存活的对象,一次性复制到To Survivor, 并且年龄+1, 然后整块清空Eden 和 From。

为什么必须两个Survivor?

  • 复制算法需要"来源区"和"去往区"物理隔离 ------ 一个survivor在复制中同时充当来源和目标,内存位置会重叠
  • 单SUrvivor会导致下一次Minor GC时 本轮存活对象无处安放 两个Survivor互为镜像、角色互换:这轮时From,下轮就是To。这就是空间换正确性的经典取舍

什么时候对象离开新生代:

  1. 年龄达标:每熬过一次Minor GC,年龄就会增大一岁,超过15岁时就会离开新生区,这个是因为对象头里标记GC年龄的字段只有4bit,最大的就是15。
  2. Survivor中同龄及以下年龄的对象总大小超过Survivor一半,这批对象整体晋升,这是为了防止Survivor被挤爆
  3. 大对象直接进:超大对象(如大数组)绕过新生代直接进入老生代
  4. Survivor装不下:Minor GC后存活对象太多,To Survivor塞不下,通过空间分配担保直接进老年代

三、老年代

存什么:

  1. 晋升的长寿对象
  2. 大对象直接分配
  3. 字符串常量池
相关推荐
136096757232 小时前
证书签不下来,是因为 well-known 被反代了
后端
Yeswater2 小时前
从 NIO 到 Netty:五个坑,和一张把它填平的基础设施网
java·后端·netty
福大大架构师每日一题2 小时前
2026-10-18:转换字符串的最小成本Ⅲ。用go语言,给定两个字符串 source 和 target,以及若干条替换规则。每条规则包含一个模式串、一个替换串
后端
韩振方2 小时前
Kubernetes Secret 让数据库密码离开镜像和 Pod 配置
后端
闽林致远2 小时前
基于标准 C++ 的插件化多线程通用数据采集框架|支持自定义业务模块,Oracle/MySQL 入库、FTP 图片上传,Windows/Linux 跨平台部署
后端
doujiaozh2 小时前
协议演进实录:从明文哈希到 AES-GCM 加密,如何平滑升级?
后端·node.js
花卷持续成长2 小时前
慢SQL的整体优化思路
后端·mysql
Sashtok2 小时前
cpp基础知识(4)
后端
Sashtok2 小时前
cpp基础知识(6)
后端