JVM相关

JVM 的内存划分

是其管理内存的基础,不同的内存区域有着不同的功能和特点,主要分为线程私有的区域和线程共享的区域。​

线程私有的区域​

栈:栈又分为本地方法栈和虚拟机栈。本地方法栈主要用于存储调用本地方法时需要传入的参数;虚拟机栈则由一个个栈帧组成,每个栈帧对应着一次方法调用,包含了局部变量表、操作数栈、动态链接、方法返回地址等信息。​

程序计数器:它的作用是指向下一行要执行的代码行号,是唯一一个在 JVM 规范中没有规定 OutOfMemoryError(内存溢出)情况的区域。​

线程共享的区域​

方法区:其具体实现为元空间,主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。​

堆:堆是 JVM 中最大的一块内存区域,主要用于存储对象实例。几乎所有的对象都在这里分配内存。

对象的生命周期​

一个对象的组成​

对象在内存中的组成主要包括对象头、实例数据和对齐填充三部分。​

对象头:包含 markword、指向对象类型的指针,如果是数组的话还额外存储了数组长度。markword 用于存储对象的哈希码、GC 分代年龄、锁状态等信息;指向对象类型的指针则指向该对象对应的类元数据,使得 JVM 能够确定该对象是哪个类的实例。​

实例数据:这部分是对象真正存储的有效信息,即我们在类中定义的各种字段的值,包括从父类继承下来的和子类中定义的。​

对齐填充:由于 JVM 要求对象的大小必须是 8 字节的整数倍,所以当对象的实际大小不满足这个要求时,就需要通过对齐填充来补齐,以提高内存访问效率。​

分配内存给对象的方式​

在堆中为对象分配内存时,根据内存是否规整,有不同的分配方式。​

指针碰撞:如果内存是规整的,即所有用过的内存放在一边,空闲的内存放在另一边,中间用一个指针隔开。分配内存时,只需将指针向空闲内存一侧移动与对象大小相等的距离即可。这种方式的缺点是需要进行内存整理,以保持内存的规整性。​

空闲列表:如果内存不是规整的,存在很多空间碎片,JVM 会维护一个空闲列表,记录哪些内存块是可用的。分配内存时,从空闲列表中找到一块足够大的内存块分配给对象,并更新空闲列表。

如何访问到对象​

对象的访问方式主要有直接指针和句柄访问两种。​

直接指针:直接指针是指对象的引用中存储的就是对象在堆中的实际地址。这种方式的优势是访问速度快,比句柄访问少一次指针定位的开销。​

句柄访问:句柄访问是指在堆中划分出一块内存作为句柄池,引用中存储的是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。这种方式在对象移动(如 GC 时的内存整理)的场景下有优势,因为只需修改句柄中的实例数据指针,而引用本身不需要修改

垃圾回收

判定生死:垃圾的判定​

判定对象是否为垃圾(即是否需要被回收)是垃圾回收的第一步,主要有引用计数法和可达性分析两种方法。​
引用计数法(脑门刻字法) :这种方法为每个对象设置一个引用计数器。每有一个引用指向该对象,计数器加 1;每断开一个引用,计数器减 1。当计数器的值为 0 时,就认为该对象是垃圾,可以被回收。引用计数法的优点是实现简单、效率高,但缺点是无法解决循环引用的问题,比如 A 引用 B,B 引用 C,C 又引用 A,此时三个对象的引用计数器都不为 0,但它们实际上已经无法被外部访问,却不会被判定为垃圾。​
可达性分析(平地长树法) :可达性分析是从一系列被称为 "GC 根" 的对象开始,向下搜索。如果一个对象能通过 GC 根的引用链关联上,就说明该对象是可达的,不会被回收;否则,就被判定为垃圾。

现行的虚拟机主要采用可达性分析来判定对象的生死。能作为 GC 根的对象包括:​

在虚拟机栈(栈帧中的本地变量表)中引用的对象,如方法参数、局部变量、临时变量等。​

在方法区中类静态属性引用的对象,如 Java 类的引用类型静态变量。​

在方法区中常量引用的对象,如字符串常量池里的引用。​

在本地方法栈中 JNI(Native 方法)引用的对象。​

Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象、一些常驻的异常对象、系统类加载器等。​

所有被同步锁(synchronized 关键字)持有的对象。​

反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。

垃圾回收的策略​

垃圾回收的策略涉及到何时回收、怎样回收等问题,这与对象的分代有关。​

分代假说:基于强分代假说(绝大多数对象都是朝生夕灭的)、弱分代假说(熬过越多次垃圾回收的对象,越难以消亡)和跨代引用假说(跨代引用相对于同代引用来说仅占极少数),JVM 将堆划分为新生代和老年代。新生代主要存放刚创建的对象,这些对象的生命周期较短,垃圾回收频率高、回收速度快;老年代主要存放从新生代存活下来的对象,这些对象的生命周期较长,垃圾回收频率低。

垃圾回收算法​

根据不同的内存区域和对象特点,JVM 采用了不同的垃圾回收算法。​
标记 - 清除算法 :该算法分为标记和清除两个阶段。第一阶段是标记出所有需要回收的对象;第二阶段是将这些标记的对象进行清除。这种算法的优势是实现简单,适合老年代(对象存活率高,回收次数少)。但它的缺点是会产生大量的空间碎片,导致后续为大对象分配内存时无法找到足够的连续内存,从而不得不提前触发另一次垃圾回收。​
标记 - 复制算法 :该算法将内存分为大小相等的两块,每次只使用其中一块。当这一块内存用完时,将还存活的对象复制到另一块内存上,然后把已使用过的内存块整体清除。这种算法的优势是不会产生空间碎片,适合新生代(对象存活率低,复制成本低)。但它的缺点是只有一半的内存空间是有效的,内存利用率较低,而且在对象存活率高的情况下,复制操作的成本会很高。​
标记 - 整理算法:该算法分为标记和整理两个阶段。第一阶段和标记 - 清除算法一样,标记出所有需要回收的对象;第二阶段是将所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。这种算法的好处是不会产生空间碎片,适合老年代。但缺点是需要移动对象,这会增加回收的成本。

相关推荐
sqyaa.3 小时前
Guava LoadingCache
jvm·缓存·guava
大佐不会说日语~5 小时前
JVM类加载机制解析
jvm
探索java7 小时前
Java 深入解析:JVM对象创建与内存机制全景图
java·jvm
麦兜*12 小时前
【SpringBoot 】Spring Boot OAuth2 六大安全隐患深度分析报告,包含渗透测试复现、漏洞原理、风险等级及完整修复方案
java·jvm·spring boot·后端·spring·系统架构
大佐不会说日语~13 小时前
JVM垃圾回收机制面试笔记
jvm·笔记·面试
TCChzp1 天前
synchronized全链路解析:从字节码到JVM内核的锁实现与升级策略
java·jvm
埃泽漫笔1 天前
JVM 基础 - JVM 内存结构
jvm
典孝赢麻崩乐急1 天前
Java学习---JVM(1)
java·jvm·学习
Devil枫1 天前
Kotlin项目实战与总结
开发语言·jvm·kotlin