十年之重修JVM原理(经长一寸寿延十年原理)
yuyutoo 2025-03-26 18:54 3 浏览 0 评论
弱小和无知并不是生存的障碍,傲慢才是。
---- ---- 面试者
总结
关于JVM相关的内容,只摘选一些常用需要理解以及面试常考的内容,其中相关内容完全来自《深入理解Java虚拟机》,这里只做内容提炼以及相关补充,包括Java运行时数据区、类的加载过程与双亲委派、对象的创建与访问、内存模型和垃圾回收等。
Java运行时数据区
当代码在JVM中运行时,JVM会把它所管理的内存分为若干区域进行管理,部分区域是线程共享的,部分区域是线程独占的,期间同样涉及线程内存与主存之间的数据同步,以及相关垃圾回收逻辑。
程序计数器
线程私有的一小块内存空间,用于存储当前线程所执行字节码指令的地址,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器。在多线程场景下,为了保证线程切换之后能恢复到正确的执行位置,每一个线程都需要有一个独立的程序计数器,独立存储互不影响。
虚拟机栈
线程私有内存空间,虚拟机在执行方法时,会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息,每一个方法被调用直至完毕的过程,就对应着一个栈帧在虚拟机栈入栈出站的过程。其中局部变量表和操作数栈在编译阶段,就已经确定并存储在方法的相关属性中。当执行的方法链路太长,申请的栈深度超过虚拟机所允许的深度时,就会抛出StackOverflowError 异常。
本地方法栈
本地方法栈为线程私有内存空间,其作用与虚拟机栈相似,其中的区别只是虚拟机栈为执行Java方法服务,而本地方法栈为本地(Native)方法服务。
堆
虚拟机管理的最大的一块内存区域,用于存储对象实例,由各线程共享。堆是垃圾收集器管理的内存区域,当JVM无法完成对实例内存分配时,将会抛出OutOfMemoryError 异 常。
方法区
用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,各线程共享的区域。在实现上,JDK8以前是基于永久代的逻辑设计实现,希望通过垃圾收集器对方法区进行回收,由于永久代存在内存设置上线,更容易出现内存溢出的问题,故在JDK8之后,采用元空间的逻辑来实现方法区,存储在方法区的类信息直接存入主存,不再受堆内存的限制以及垃圾收集器的管理,但是也是受物理机内存大小的限制的。
注意:常量池、静态变量、Class对象在JDK1.7时,就已经移入堆存储了,剩余的类信息以及字节码在JDK1.8以后存入了元空间(直接内存),方法区只是一个JVM规范,具体实现由JVM自定义。
类加载
类加载过程
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期会经历加载、验证、准备、解析、初始化、使用和卸载;
有且只有以下六种场景必须立刻对类进行加载与初始化:
- 遇到new(使用new关键字实例化对象)、getstatic(读取静态字段)、putstatic(设置一个静态字段)、invokestatic(调用一个静态方法)这四条字节码指令时,其中读取或设置被 final 修饰、已在编译期把结果放入常量池的 静态字段除外;
- 使用 java.lang.reflect 包的方法对类型进行反射调用的时候,如果类型没有进行 过初始化,则需要先触发其初始化;
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父 类的初始化;
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main()方法的那个 类),虚拟机会先初始化这个主类;
- 当使用 JDK 7 新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
- 当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
加载
- 通过一个类的全限定名来获取定义此类的二进制字节流;(这里没有限定必须从class文件获取,故后来衍生从JAR、WAR、从网络中获取、运行时生成字节码(动态代理)等)
- 将这个字节流转换为JVM定义的存储格式存入方法区;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
验证
- 文件格式验证,比如二进制文件是否以魔数 0xCAFEBABE 开头(头4个字节),二进制文件中主次版本号,是否在当前JVM虚拟机接受(JDK向下兼容class文件,当时向上不支持,第5和第6字节是次版本号,第7第8字节是主版本号),以及相关class文件格式的验证。
- 元数据验证,这里主要是对语法进行分析与验证;
- 字节码验证,主要是通过数据流分析和控制流分析,确保语义执行是合法符合逻辑的。
- 符号引用验证,主要是校验代码中通过符号引用的类、方法是否存在或可访问等。
准备
准备阶段正式为类中定义的变量(即静态变量,而非实例变量)分配内存,并设置初始值(默认值),具体赋值得到初始化阶段;
解析
JVM将常量池内的符号引用替换成直接引用,其中符号引用支持一组符号用来唯一标记目标,引用目标并不是一定加载到虚拟机的内容,替换成直接引用后,则是可以指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
初始化
初始化阶段会将准备阶段针对类变量初始化后的值,再根据程序编码实际赋值逻辑进行赋值,同时会基于类构造器
类构造器
类加载器
类与类加载器
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在JVM中的唯一性。即如果一个类,在同一个JVM中,使用两个不一样的类加载器加载,则这两个类不相等。这里的相等包括:equals()方法、isAssignableFrom()方 法、isInstance()方法的返回结果等。
双亲委派模型
- 启动类加载器(Bootstrap Class Loader):这个类加载器负责加载存放在JAVA_HOME/lib目录的类,或者被-Xbootclasspath 参数所指定的路径中存放的, 而且是 Java 虚拟机能够识别的(按照文件名识别,如 rt.jar、tools.jar,名字不符合的类 库即使放在 lib 目录中也不会被加载);
- 扩展类加载器(Extension Class Loader):这个类加载器是在类 sun.misc.Launcher$ExtClassLoader中以Java 代码的形式实现的。它负责加载 \lib\ext 目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库。
- 应用程序类加载器(Application Class Loader):这个类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它 负责加载用户类路径(ClassPath)上所有的类库。
双亲委派模型,其中加载器之间并不是继承关系,只是组合引用,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,每一个层次的类加载都是如此,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中,没有找到所需的类)时,子加载器才会尝试自己去完成加载。
这样的好处就是保证基础类型例如Object这样的基础类,在JVM运行时只被加载一次,而不至于出现多个相同同名类导致系统混乱。
破坏双亲委派模型
双亲委派模型的出现,是为了解决基础类只被加载一次以及类加载流程规范问题,但是破坏双亲委派模型,也是为了解决实际问题,并不存在破坏就是不好的意思。
- 用户基于继承ClassLoader覆写loadClass()方法进行自定义加载类过程,导致并不遵守双亲委派原则;
- 在SPI技术上,由于启动类无法加载提供SPI接口的类,故需要通过Thread类的ClassLoader进行加载;
- OSGi技术,支持热部署
对象的创建&内存布局&访问
new对象流程
- 当JVM遇到new指令时,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、 解析和初始化过,如果没有,那必须先执行相应的类加载过程;
- 类加载检查通过之后,虚拟机将会为新生的对象分配内存。
- 对象头信息进行初始化。(Mark Word 、类指针);
- 执行类的构造函数
()方法,进行静态属性初始化、静态代码块执行以及构造函数执行
对象内存布局
在堆内存中,对象的存储布局可以分为三个部分,对象头、实例数据和对其填充;对象头包括Mark Word 和 类指针(指向该实例所属类);实例数据存储的字段内容,其中包括从父类继承的属性;对齐填充这个因为JVM要求对象起始地址必须是8字节的整数倍,不足则进行补充。
这个Mark Word涉及synchronized实现原理:synchronized实现原理
对象访问
对象的访问有两种方式,一是句柄访问,二是直接指针访问(JVM使用)。句柄访问就需要在堆中开辟一个内存空间存储句柄,定义的引用变量指向句柄,句柄存储两个指针,一个指向堆类的对象实例数据,一个指向方法区的对象数据类型,这样当对象进行GC被迁移之后,只需要调整句柄中的指针,引用变量不需要调整,但是这样就需要多走一次指针。直接指针访问,就是引用变量直接指向堆中对象实例数据,对象实例中也保存了对象类型的指针,这样访问速度更快。
Java内存模型
Java内存模型规定了所有实例字段、静态字段和构成数组对象的元素,都存储在主内存中,每一条线程有自己的工作内存,其中工作内存中保存了该线程使用的变量的主内存副本,线程对变量的所有操作(读写)都必须在工作内存中进行,而不能直接读写主内存,不同线程之间也无法直接访问对方的工作内存中的变量,线程之间的数据传递都需要通过主内存来完成。
主内存与工作内存数据交互:
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
- load(载入):作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作使用。
- write(写入):作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中。
涉及工作内存与主存间数据同步经典问题:Volatile原理
垃圾回收
可达性分析
Java的垃圾回收是通过可达性分析,通过定义一系列的“GC Roots”的根对象,从这些节点开始基于引用关系往下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链,则表示此对象不可达,需要被回收,一般经历至少两次不可达标记之后,对象才会被回收。
GC Roots定义:
- 虚拟机栈中引用的对象,即线程栈帧中还存在对象的引用;
- 方法区中静态属性引用的对象,常量引用的对象,本地方法中native方法引用的对象;
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些异常对象如NullPointException,还有系统的类加载器等;
- 所有被同步锁synchronized持有的对象;
- 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
垃圾回收算法
- 标记-整理算法:是标记-清除算法的升级,即对要回收的内存进行标记,然后进行清除,清除之后进行整理,保证被使用的内存是连续的,以及空余的内存也是连续的。这里如果不整理,则需要维护一个内存空间链表来记录那个内存可用哪个不可用,这样会导致整体吞吐量下降,如果选择整理,会导致在回收期间需要stop the world,但是能接受。
- 标记-复制算法:内存会被分为两部分,一部分使用,一部分预留,当使用的那部分发生回收之后,直接把标记存活的对象复制到预留内存中,被使用的那部分直接全部回收掉,这样两个内存区域反复使用;
- 分代回收算法:把堆内存分为新生代和老年代,新生代通过多次回收还存活的对象会存入老年代,其中新生代由按照8:1:1分为一个Eden区和两个Survivor区,每次使用一个Survivor区和Eden区,进行回收时,把存活的对象存入另外一个Survivor区。当Survivor存储不下时,就对存入老年代区。
垃圾回收器
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代),jdk23 使用的G1收集器。Parallel Scavenge基于标记-复制算法实现,支持多线程收集,Parallel Old支持多线程并发收集,基于标记-整理算法实现。
G1 开创的基于 Region 的堆内存布局是它能够实现这个目标的关键。虽然 G1 也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
坐观垂钓者,徒有羡鱼情!
相关推荐
- 苹果要求全新App开发四月起必须支持“齐刘海”
-
今日消息,苹果公司通过邮件告知应用程序开发者,从2018年4月起提交给AppStore的所有新应用必须支持iPhoneX的超级视网膜显示器。这意味着新应用程序的开发者必须确保它们适应“齐刘海”,并...
- 耗时一年多,QEMU开发者成功在电脑上模拟了初版iPhone OS
-
IT之家12月24日消息,用户通过黑苹果(Hackintosh)工具,已经可以在非Mac设备上运行macOS系统。但由于种种限制,至今也没有多少人能够在PC上运行iOS系统。现...
- 下个月的WWDC后,苹果将发布原生Watch SDK测试版本
-
在近日Re/code举办的CodeConference上,苹果的运营副总裁JeffWilliams称,目前有4000多个AppleWatch应用上线,而未来的苹果表开发者套件,将允许开发者直接获...
- 苹果再次提醒:4月起强制要求APP进行适配
-
点击右上角关注我们,每天给您带来最新最潮的科技资讯,让您足不出户也知道科技圈大事!软件适配对于许多厂商来说都是一个比较头疼的事,苹果在握紧AppStore审核权的情况下情况要好许多。最近他们公布了...
- 苹果Xcode 16首个Beta版发布,AI代码补全最少需16GB内存
-
IT之家6月12日消息,在苹果WWDC24开发者大会上,苹果发布了iOS18、macOS15Sequoia等最新版本系统更新。与此同时,苹果推出了Xcode16开发工具的首...
- 传苹果已向特定开发者开放iWatch SDK
-
|责编:薄志强苹果会不会在这次发布会中发布全新的智能手表产品iWatch还很难说,不少人认为由于iWatch的消息少之又少,很可能这次还是没有iWatch。不过现在又有外媒传出消息称,苹果已经选定了...
- 苹果发布Swift 6语言:引入新测试框架、增强C++ 互操作性
-
IT之家9月20日消息,科技媒体devclass昨日(9月19日)报道,苹果公司在发布iOS/iPadOS18和macOS15Sequoia系统之外,还发布了Sw...
- 发布Siri SDK 之前苹果还是先想想这个问题
-
今年的GoogleI/O大会上,在预览GoogleHome时,我们就看到了设备可以互相对话的场景是多么惊艳,苹果快点跟上吧。最近因为亚马逊Echo和谷歌GoogleHome的火热...
- iOS 17.2 SDK代码确认古尔曼爆料:免开箱更新苹果iPhone系统
-
IT之家10月27日消息,彭博社的马克古尔曼(MarkGurman)本月早些时候发布报道,称苹果正在研发新的系统,可以让员工在不拆开包装的情况下,升级iPhone的iOS系统。根据国...
- 《企业应用架构模式》之事件驱动架构
-
事件驱动架构(Event-DrivenArchitecture,EDA)是一种强调事件流和异步通信的应用程序架构。在该架构中,应用程序被分解为多个小型、可独立部署的组件,这些组件通过事件进行通信...
- k8s中常用的controller以及用途和对应机制
-
controller的用途ReplicaSet、Deployment、StatefulSet:用于无状态和有状态应用的副本管理。DaemonSet:确保每个节点上都运行一个副本的控制器。...
- Disruptor框架源码阅读-如何不重复消费
-
RingBuffer如何保证数据不丢失由于ringbuffer是一个环形的队列,那么生产者和消费者在遍历这个队列的时候,如何制衡呢?1、生产快,消费慢,数据丢失?生产者速度过快,导致一个对象还没消...
- C# 控制电脑睡眠,休眠,关机以及唤醒
-
最近碰到一个关于芯片测试过程中的问题,这颗芯片是用在笔记本端口上,笔记本客户那边会有一个压力测试,就是频繁的电脑电源状态切换,S0(正常使用的开机状态),S3(睡眠模式),S4(休眠模式)以及S5(关...
- 大厂防止超卖的7种实现,很受用!(大厂防止超卖的7种实现,很受用的产品)
-
高并发场景在现场的日常工作中很常见,特别是在互联网公司中,这篇文章就来通过秒杀商品来模拟高并发的场景。本文环境:...
- 臻识车牌识别配制MQTT通讯,解析车号
-
在物联网项目中,我们的软件与车牌识别通讯,通常使用MQTT通讯更简单。...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
【开源分享】2024在线客服系统PHP源码(安装教程+全新UI)
-
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)