Java进阶 深入解析JVM核心架构与运行原理
发布时间:2026/9/29 2:35:31来源:尧图网络
目录1. 引言2.JVM概述2.1 JVM的作用2.2JVM的基础架构3.类加载3.1类加载的概念3.2类加载过程3.3类加载器3.4双亲委派机制3.4.1作用原理jvm加载类时,遵循双亲委派机制3.4.2为什么要用双亲委派机制4.运行时数据区4.1程序计数器4.2.虚拟机栈4.3.本地方法栈4.4.堆4.4.1 堆内存概述4.4.2 堆内存区域划分为什么要分区对象内存分配过程堆空间的参数设置(了解)5.方法区5.1 作用5.2 大小设置5.3 方法区垃圾回收6. 本地方法接口什么是本地方法为什么要使用 Native Method7.执行引擎7.1作用:7.2编译过程8.垃圾回收8.1 垃圾回收概述8.1.1什么是垃圾对象8.1.2早期的垃圾回收8.1.3 垃圾回收重点区域8.1.4 内存溢出和内存泄漏8.2垃圾回收相关算法8.2.1标记阶段1.引用计数算法(目前没有被使用)2.可达性分析算法(根搜索算法、追踪性垃圾收集)final fianlly{ } finalize() 区别Object类中的finalize()8.2.2 回收阶段标记-复制算法Copying标记-清除算法标记-压缩算法8.3垃圾收集器8.3.1概述8.3.2 垃圾回收器分类8.3.3 GC 性能指标8.3.4 jdk8中内置的垃圾收集器8.3.5 CMS8.3.6 G18.3.7如何设置选择垃圾收集器1. 引言JVM的定义与核心作用Java虚拟机作为Java程序运行的基石实现“一次编写到处运行”。JVM的重要性支撑跨平台性、内存管理、安全性等关键特性。文章目标系统解析JVM架构、工作机制及优化实践。2.JVM概述2.1 JVM的作用Java 虚拟机负责装载字节码到其内部解释/编译为对应平台上的机器码 指令执行。现在的JVM不仅可以执行java字节码文件,还可以执行其他语言编译后的 字节码文件,是一个跨语言平台.2.2JVM的基础架构类加载器ClassLoader运行时数据区RuntimeData Area执行引擎Execution Engine本地库接口Native Interface针对运行时数据区提供的垃圾回收运行原理程序在执行之前先要把java代码转换成字节码class文件jvm首先需要把字节码通过一定的方式类加载器ClassLoader把文件加载到内存中 的运行时数据区RuntimeDataArea而字节码文件是jvm的一套指令集规范并不能直接交个底层操作系统去执行因此需要特定的命令解析 器执行引擎ExecutionEngine将字节码翻译成底层系统指令再交由 CPU去执行而这个过程中需要调用其他语言的接口本地库接口Native Interface来实现整个程序的功能这就是这4个主要组成部分的职责与 功能3.类加载3.1类加载的概念通过类加载模块把硬盘上的.class文件加载到运行时数据区中3.2类加载过程1.加载阶段 (利用IO读取字节码)2.链接阶段验证字节码是否被污染(格式对不对)准备解析3.初始化 是对类中静态成员初始化赋值当类加载完成初始化阶段时,一个类才真正完成加载的整个过程面试题: 类什么时候会被加载类被使用时会加载类1.创建某个类的对象时. new Hello();2.访问类中静态成员3.反射机制加载类 Class.forName(com.ffyc.javapro.jvm.Hello);4.执行某个类中的main方法5.子类被加载时,会加载对应的父类.还有两种特殊情况,看似用到了类,但是类不会被加载1.只使用了类中的静态常量2. 类当做类型使用时, 如创建一个Hello类型的数组 Hello[] hellos new Hello[10];3.3类加载器理解为专门加载类的类.类加载器分类:1.启动类加载器(引导类加载器)不是java语言写的,由c/c写的,嵌入在虚拟机中,用于虚拟机启动时,读取java中核心类库(系统中类).2.扩展类加载器这种类加载器,是由java语言写的,继承ClassLoader类, 读取\jre\lib\ext目录下的类,这个目录中类属于扩展类,自己也可以写一些类,放在此目录下.3.应用程序类加载器也是由java语言写的, 专门用于读取程序员写的应用程序中的类.3.4双亲委派机制3.4.1作用原理jvm加载类时,遵循双亲委派机制当加载一个类时,先让上级的类加载器去加载,优先加载系统中的类, 如果系统中没有,才委派给下级类加载器, 下级如果找到了,那么就加载该类, 如果下级也没找到,就抛出类找不到异常3.4.2为什么要用双亲委派机制为了安全, 避免我们自己写的类 替换了系统中类4.运行时数据区当程序启动运行时,类信息,对象信息等都存储在运行时数据区, 顾名思义就是存储运行时数据的区域.运行时数据区可以分为5个区域:4.1程序计数器作用:程序计数器用来记录cpu执行到程序指令的位置.cpu要切换执行许多的程序,在切换之前需要记录程序本次执行的位置, 下次回来时继续接着 执行特点:内存空间小,运行速度快线程私有的,每个线程都有一个自己的私有的程序计数器,生命周期与线程生命周期一致 (线程创建启动后 计数器开始工作, 线程结束程序计数器也就结束)程序计数器中不存在内存溢出情况(内存不够用) , 也不会出现垃圾回收.4.2.虚拟机栈作用:栈是运行的结构设计,管的是程序(方法)如何执行,虚拟栈是用来运行java语言写的方法的java总是main方法先执行,main第一个被压入到栈中,再调用其他的方法,继续被压入到栈中,方法运行完就出栈, 最顶上的就是当前正在运行的方法.特点:虚拟机栈是线程私有的, 每个线程创建就会创建一个虚拟机栈方法入栈后,称为一个栈帧(保存方法中的信息)栈空间中不存在垃圾回收可能存在内存溢出问题(内存不够用), 如递归调用次数过多,栈中放不了.栈帧结构存储方法中声明的局部变量操作数栈, 所有程序的运算都在操作数栈中进行方法返回地址4.3.本地方法栈特点本地方法栈是用来运行本地方法的(c/c写的系统方法)本地方法栈也是线程私有的不会出现垃圾回收,但是可能会出现内存溢出4.4.堆4.4.1 堆内存概述作用:java中所创建的对象,都存储在堆空间中特点:堆是运行时数据区中最大的一块内存区域堆是线程共享,所有的线程共享堆中的数据堆的空间大小可以调整(通过一些参数进行设置)例如:-Xms:10m(堆起始大小)-Xmx:30m(堆最大内存大小)堆空间是垃圾回收的重点区域堆空间是可能出现内存溢出的4.4.2 堆内存区域划分新生代(年轻代,新生区)伊甸园区幸存者0幸存者1老年代(老年区)为什么要分区把不同生命周期的对象存储在不同的区域(有的对象存活时间长,ArrayList), 针对不同的区域采用不同的回收算法, 频繁的回收新生代, 较少回收老年代区从而减少扫描垃圾时间及GC频率。对象内存分配过程java中新创建的对象,都存储在新生代的伊甸园区, 当垃圾回收发生时, 会将伊甸园中的存活对象移动到幸存者0区,清空伊甸园区, 继续创建对象, 当下次垃圾回收执行时, 将伊甸园区和幸存者0区存活的对象都移动到幸存者1区,每次只使用一个幸存者区,当一个对象默认回收15次后,依然存活,那么就把该对象移动到老年代.注为什么回收次数最大为15次? 能不能改?对象头中有一块记录分代年龄的区域,最大4个bit位, 表示最大15-XX:MaxTenuringThresholdN堆空间的参数设置(了解)堆空间的大小如何设置(jvm调优)-XX:PrintFlagsInitial查看所有参数的默认初始值-Xms:初始堆空间内存-Xmx:最大堆空间内存-Xmn:设置新生代的大小-XX:MaxTenuringTreshold:设置新生代垃圾的最大年龄-XX:PrintGCDetails 输出详细的 GC 处理日志5.方法区5.1 作用方法区也称为元空间主要用来存储类信息(方法,变量静态常,静态变量,量....5.2 大小设置方法区大小是可以进行设置的-XX:MetaspaceSize注方法区是会存在内存溢出问题的, 当加载的类信息很多时,方法区空间不足,就会报错.5.3 方法区垃圾回收方法区是存在垃圾回收的,《Java虚拟机规范》 对方法区的约束是非常宽松的提到过可以不要求虚拟机在方法区中实现垃圾集。方法区的垃圾回收的主要是类信息但是类信息的回收条件很苛刻,一般我们可以认为类被加载后,类信息就不卸载了类卸载的条件比较苛刻,需要同时满足以下3个条件:该类的对象,以及该类的子类对象都已经被回收.加载该类的类加载器已经被回收该类对应的Class对象没有在任何地方被引用无法在任何地方通 过反射访问该类的方法。6. 本地方法接口什么是本地方法一个NativeMethod就是一个java调用非java代码的接口一个 Native Method 是这样一个java方法该方法的底层实现由非Java语言实现 比如C。注native关键字修饰的方法 不是用java语言实现的为什么要使用 Native Methodjava语言是应用层语言,java语言没有权限直接操作计算机硬件设备(内存,硬盘), 操作系统就为上层语言写好了一些操作硬件的接口, 上层语言只需要调用这些系统接口即可, 底层实现由操作系统处理.本地方法接口模块就是在虚拟机中赋值调用本地方法的入口.7.执行引擎7.1作用:执行引擎是虚拟机中最核心之一的内容负责加载字节码到虚拟中, 将字节码解释/编译为机器码, (字节码是虚拟机中指定的指令(汇编指令)),不是机器码7.2编译过程前端编译:通过javac名,把.java文件编译为.class文件后端编译:在运行时,由执行引擎把字节码编译为机器码注为什么执行引擎中设计为半解释,半编译模式将字节码翻译为机器码高级语言-------机器码 一般由两种方式:解释执行编译执行解释执行: web前端 html css js , sql , python解释执行特点:一行一行由解释器解释执行, 缺点: 效率慢 . 不需要编译,可以由解释器直接解释执行编译执行: c c java ---- 整体编译编译执行特点编译后执行效率高, 但是编译是花时间的.原因执行引擎可以对某些执行比较频繁的热点代码进行追踪, 将热点代码编译后缓存起来,下次使用时省去了编译时间,可以直接拿来使用.程序刚开始运行时, 先通过解释器解释执行,提高响应速度, 当热点代码编译后,使用编译执行,提高后续程序执行速度.8.垃圾回收8.1 垃圾回收概述8.1.1什么是垃圾对象在程序运行时,如果一个对象没有被任何引用变量所指向,那么这个对象就是垃圾对象.垃圾对象如果不及时清理, 长期占用内存空间,会导致新创建的对象空间不足,最终发生内存溢出错误.实例public void test(){ 1 Hello h1 new Hello() Hello h2 h1; h1 nul; h2 null; } main(){ test(); }8.1.2早期的垃圾回收c语言malloc() 申请内存空间free() 释放内存空间C 也是手动申请,手动释放手动内存管理好处: 对内存管理更精确,用时申请,用完销毁不足: 麻烦, 忘记释放内存,导致内存泄漏现在的语言很多都支持自动内存管理(自动垃圾回收)8.1.3 垃圾回收重点区域垃圾回收的重点区域是堆, 方法区也存在垃圾回收频繁回收新生代,较少回收老年代8.1.4 内存溢出和内存泄漏内存溢出: Out Of Memory简称 OOM, 指的就是内存空间不够了,后续创建的对象放不下了,此时程序会崩溃终止运行.内存泄漏:一个对象已经不再被使用了,本应该被回收掉,但是由于种种原因垃圾回收器却不能回收该对象,俏销的占用着有效的内存空间,这种现象被称为内存泄漏.例如: IO,数据库连接,socket通信等对象,都提供close()用于释放对象, 但是我们如果不掉用close(),垃圾回收器时不能自作主张回收掉这些对象的,那么这些对象就悄悄的占用着内存空间.8.2垃圾回收相关算法8.2.1标记阶段主要就是对堆空间中的对象进行标记,标记出哪些对象是有用的,哪些对象已经是垃圾对象了.标记阶涉及到两种算法:1.引用计数算法(目前没有被使用)Hello h1 new Hello(); //对象中有一个计数器 1 1 2 Hello h2 h1; h2 null; //-1--1 h1 null; //-1--0引用计数算法虽然实现起来比较简单,但是也存在一些问题:计数器需要占用空间,可能会频繁的加减计数器,占用时间开销存在一个致命的缺陷, 不能解决循环引用问题, 会造成内存泄漏2.可达性分析算法(根搜索算法、追踪性垃圾收集)从一些活跃对象(GCRoots)开始进行查找,凡是与活跃对象相关联的对象,都是存活对象,一旦某些对象,与活跃对象没有任何链接,那么这些对象就可以判定为是垃圾对象.哪些对象可以被算作活跃对象(根对象 GCRoots)虚拟机栈中引用的对象, 入栈后等待运行类中定义的成员变量(全局)作为同步锁对象的系统中的对象, 如Class对象, 异常对象....final fianlly{ } finalize() 区别try{} catch(){ } finally{ }Object类中的finalize()在Object类中定义的, 子类可以重写, 在对象首次被判定为是垃圾对象后,在回收该对象前,由垃圾收收集器调用,可以在finalize()中执行一些最终的操作,finalize()方法,只会调用一次. 该对象再次被判定为垃圾时,就不会再调用finalize().protected void finalize() throws Throwable { }由于 finalize()存在,对向一般可以由3种状态:可触及的: 对象被使用着,不是垃圾对象可复活的: 对象首次被判定为垃圾对象,finalize()还未调用, 有可能在finalize()中复活不可触及的: finalize()被调用过一次,再次被判定为垃圾对象,就没有复活的可能了8.2.2 回收阶段标记-复制算法Copying有多块内存, 每次有一块是空闲的,回收时,把正在使用中的内存块中存回对象都移动(复制)到空闲内存块中,清空当前正在使用的内存块, 互换角色.复制算法,特别适合新生代的垃圾回收, 因为新生代垃圾对象多,存活的对象少.注这种回收办法,减少内存中的碎片标记-清除算法保持存活对象不动,清除垃圾对象即可, 标记清除算法适合老年代的垃圾回收,因为老年代对象存活时间长,也有的对象比较,减少移动次数. 会产生内存碎片.标记-压缩算法在把垃圾对象清理后,把存活对象压缩到内存的一端,进行重新排放,减少内存碎片产生, 但是要移动存活对象.8.3垃圾收集器8.3.1概述如果说垃圾收集算法是内存回收的方法论,那么收集器就是内存回收的实践者.由于JDK的版本处于高速迭代过程中因此Java发展至今已经衍生了众多的垃圾回收器。从不同角度分析垃圾收集器可以将GC分为不同的类型。实际使用时,可以根据实际的使用场景选择不同的垃圾回收器,这也是JVM调优的重要部分.8.3.2 垃圾回收器分类按照线程数量分:单线垃圾收集器多线程垃圾收集器按照工作模式分:独占式垃圾收集器(垃圾回收线程执行时,其他用户线程需要暂停,不能执行)并发式垃圾收集器(垃圾收集线程和用户线程可以做到同时执行)按工作的内存区间分:新生代垃圾收集器老年代垃圾收集器8.3.3 GC 性能指标吞吐量运行用户代码的时间占总运行时间的比例总运行时间程序的运 行时间内存回收的时间用户线程暂停时间(用户线程暂停时间越短越好)执行垃圾收集时程序的工作线程被暂停的时间 STW内存占用Java堆区所占的内存大小8.3.4 jdk8中内置的垃圾收集器Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old,CMS,G18.3.5 CMSCMS(Concurrent Mark Sweep)并发标记清除追求低停顿, cms垃圾收集器首次做到了垃圾收集线程和用户线程可以同时执行.初始标记: 用户线程会暂停, 垃圾收集线程是独占的并发标记: 垃圾收集线程和用户线程一起执行重新标记: 用户线程会暂停, 垃圾收集线程是独占的并发清理: 垃圾收集线程和用户线程一起执行8.3.6 G1Garbage First收集器的出现为了适应现在不断扩大的内存和不断增加的处理器数量进一步 降低暂停时间pausetime同时兼顾良好的吞吐量G1垃圾收集器,主要是面向服务器端使用场景特点:将堆中的每个分区(伊甸园区,幸存者区,老年区) 再次拆分成若干个小的区域,每次在回收时,优先回收垃圾最多的小区域,所以叫G1 (垃圾优先).可以做到对整个堆进行回收(不再区分老年区还是新生区)3.G1垃圾收集器,也可以做到用户线程和垃圾收集线程同时执行.8.3.7如何设置选择垃圾收集器打印默认的垃圾收集器:-XX:PrintCommandLineFlags -versionJDK 8 默认的垃圾回收器 年轻代使用 Parallel Scavenge GC 老年代使用 Parallel Old GC设置选择指定的垃圾收集器:-XX:UseG1GC设置默认垃圾回收器Serial 回收器-XX:UseSerialGC 年轻代使用Serial GC 老年代使用Serial Old GCParNew 回收器-XX:UseParNewGC 年轻代使用 ParNew GC,不影响老年代。CMS回收器-XX:UseConcMarkSweepGC 老年代使用 CMS GC。G1回收器-XX:UseG1GC 手动指定使用 G1 收集器执行内存回收任务。-XX:G1HeapRegionSize 设置每个 Region 的大
网站建设高端定制企业官网