读懂 JVM:内存模型、类加载机制、分代回收 JVM前言什么是JVMJVM运行时数据区方法区(元数据区)堆Heap栈程序计数器本地方法栈例题类加载类加载的基本流程双亲委派模型垃圾回收GC垃圾回收步骤找到垃圾回收垃圾总结前言前面我们把计算机网络协议栈梳理完毕了解数据如何跨主机通信。而写好的 Java 代码是怎么在机器上运行的这就是 JVM 的工作。JVM 是 Java 程序运行的虚拟机实现了跨平台一次编译到处运行也是后端核心模块。什么是JVMJVM全称Java Virtual MachineJava虚拟机。一个运行起来的Java进程就是一个JVM虚拟机。核心特性跨平台。Java 源码编译成.class字节码文件JVM 负责解析执行字节码。不同操作系统有对应版本 JVM一份 class 到处跑。区分一下JDK、JRE、JVM1JDK JRE 开发工具javac、Java等2JRE JVM Java基本类库JVM运行时数据区Java进程运行过程中就要从操作系统申请一大块内存这块内存划分成不同的区域每个区域都有不同的作用。下面详细分析这几个区域。方法区(元数据区)该区存储的内容就是类对象。.class文件加载到内存之后就成了类对象。堆Heap该区存储的内容是代码中new的对象实例、数组。堆区是占据空间最大的区域也最容易发生OOM即内存溢出。栈该区存储的是代码执行过程中方法之间的调用关系。方法调用就压栈方法执行结束出栈。程序计数器该区是一个比较小的空间存放一个“地址”表示下一条要执行的指令在内存中的哪个地方。而要执行的指令存放在方法区每个方法里面的指令都是以二进制的形式保存在对应的类对象中。classTest{publicvoida(){...}publicvoidb(){...}/*这里的方法a和方法b都会被编译成二进制指令放到.class文件中执行类加载时就能把.class文件中 的内容加载到类对象中此时方法里面的指令就以二进制的形式保存在对应的类对象中。*/}刚开始调用方法时程序计数器中存储的是方法的入口地址。随着一条条指令的执行程序计数器的值会自动更新去指向下一条指令。若是顺序执行则下一条指令就是把指令地址进行递增。若是循环或条件代码则下一条指令可能会跳转到一个较远的地址。本地方法栈该区存放的是使用native修饰的方法。该区的方法不是由Java实现的而是在JVM内部由C实现。注意程序计数器、虚拟机栈、本地方法栈是线程私有每个线程都有独立一份堆、元空间方法区是线程共享整个 JVM 进程只有一份。下图为JVM内存模型。例题classTest{publicintn100;publicstaticinta10;voidmain(){TesttnewTest();}}问nat三个变量分别存在哪个区域分析一个变量存在于哪个区域与变量的形态密切相关与类型无关。1局部变量—栈2成员变量—堆3静态变量—方法区答1n—成员变量—对象中包含n这个属性—对象存在于堆上—n在堆上2a—静态变量—类属性—在类对象中—a在方法区元数据区3t—方法内部的局部变量—栈帧中的局部变量表—t在栈上。类加载类加载的基本流程Java代码会被编译成.class文件Java程序要想运行起来就需要让JVM读取到这些.class文件并且把里面的内容构造成类对象保存到内存的方法区。加载找到.class文件打开文件读取文件内容。往往代码中给定某个类的全限定类名JVM就会根据这个类名在一些指定的目录范围内寻找。验证.class文件是二进制格式因此需要验证当前读到的这个格式是否符合要求。Java官方文档中规定了语言规范、虚拟机规范其中虚拟机规范中写明了.class文件要遵守的格式ClassFile{//相当于结构体u4 magic;//用来表示文件的格式u2 minor_version;u2 major_version;//这两个共同构成.class的版本号u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];//当前.class文件中用到的常量池u2 access_flags;//表示类的权限修饰符u2 this_class;//当前类的相关信息u2 super_class;//该类的父类的相关信息u2 interfaces_count;u2 interfaces[interfaces_count];//该类实现了哪些接口u2 fields_count;field_info fields[fields_count];//类中有哪些属性u2 methods_count;method_info methods[methods_count];//类中有哪些方法u2 attributes_count;attribute_info attributes[attributes_count];//类中有哪些注解}准备给类对象分配内存空间。仅分配空间未进行初始化并赋予默认零值。解析针对类对象中包含的字符串常量进行处理。由于引用本质上保存的是一个变量的地址在.class文件中不涉及到内存地址因此字符串常量的初始化语句会先被设置成“文件的偏移量”通过偏移量可以找到字符串常量所在的位置。当这个类真正被加载到内存时再将偏移量替换回内存地址。初始化针对类对象进行初始化还需要初始化static成员执行静态代码块加载父类。双亲委派模型双亲委派模型是“加载”过程中的一个环节负责的是根据全限定类名找到.class文件。类加载器JVM中内置了三个类加载器1BootStrap ClassLoader—相当于爷爷—负责的是JDK标准库目录。2Extension ClassLoader—相当于父亲—负责的是JDK中一些扩展的库对应的目录。3Application ClassLoader—相当于孩子—负责的是项目当前目录第三方库对应目录。它们直接不是继承关系而是ClassLoader中有一个parent属性指向一个父“类加载器”。加载的过程加载也就是找.class文件的过程其实就是一个简单的查找优先级问题。1给定一个类的全限定类名比如java.lang.Long。2从Application ClassLoader作为入口开始执行查找的逻辑。3Application ClassLoader不会立即扫描自己负责的目录而是甩锅把查找任务交给他的父“类加载器”Extension ClassLoader。4同样Extension ClassLoader也不会立即扫描自己负责的目录而是甩锅把查找任务交给他的父“类加载器”BootStrap ClassLoader。5BootStrap ClassLoader也不想立即扫描自己负责的目录但是他没有父“类加载器”只能自己来做扫描标准库目录若扫描到目标文件任务结束执行后续的类加载操作。6若没有扫描到就会回到Extension ClassLoader他来扫描负责的扩展库目录若扫描到目标文件任务结束执行后续的类加载操作。7若没有扫描到就会回到Application ClassLoader他来扫描负责的当前项目和第三方库的目录若扫描到目标文件任务结束执行后续的类加载操作。若没有扫描到就抛出ClassNotFoundException异常。这套流程的目的确保了标准库的类被加载的优先级最高其次是扩展库最后是自己写的类和第三方库。垃圾回收GC回收的垃圾是内存中的对象也就是堆中的内存。垃圾回收机制GC让JVM自行判定内存中的这个对象是否还会再次使用若不再使用则回收。栈中的局部变量跟随栈帧的生命周期。静态变量生命周期是整个程序始终存在因此静态变量无需释放。垃圾回收机制缺点系统开销需要额外消耗CPU、内存资源需要有一些特定线程不停扫描内存中的所有对象看是否能回收。效率问题这样的扫描线程不一定能及时释放内存一旦同一时刻出现大量的对象都要被回收GC产生的负担很大可能会引起整个程序卡顿STW问题可达性分析标记阶段会触发 STWStop The World暂停业务用户线程防止对象引用关系不断变化保证标记结果准确。。垃圾回收步骤找到垃圾引用计数给new出来的对象单独安排一块空间来保存一个计数器用来描述有几个引用指向这个对象。但在Java中不使用引用计数1浪费内存。计数器最小也要占用两个字节若对象本身很小计数器占据的内存空间比例大。2引用计数机制存在循环引用问题。例如2.可达性分析可达性分析本质上是时间换空间。一组线程周期性扫描代码中所有对象从一些特定的对象出发尽可能地进行访问遍历把所有能够访问到的对象标记为“可达”最终经过扫描之后未被标记为“可达”的对象就是垃圾。也就是N叉树。可达性分析的出发点很多所有局部变量、常量池中引用变量、方法区中静态变量引用的对象统称为GCRoots。回收垃圾标记清除1标记清除就是把对应的对象的内存空间直接释放掉。2但这样做会产生很多内存碎片随时间推移内存碎片越来越严重会导致后续内存申请举步维艰。复制算法复制算法就是通过复制的方式把有效的对象归类到一起再统一释放剩下的空间。标记整理标记从GCRoots出发标记所有存活对象整理把所有存活对象向内存一侧移动、紧凑排列垃圾全部挤到另一边一次性清理掉边界以外的垃圾。既解决了内存碎片又能处理复制算法的利用率但是开销大。实际上 JVM 采取的释放思路是三者结合体分代回收。总结JVM实现Java跨平台Java源码编译为class字节码交由JVM解释执行。JVM运行时数据区分为线程私有区域PC寄存器、虚拟机栈、本地方法栈和线程共享区域堆、元空间。类加载五大阶段加载、验证、准备、解析、初始化双亲委派保证JDK核心类安全。GC以可达性分析判定对象生死分代回收新生代复制算法老年代标记整理。本篇是「Java后端编程系列」的连载内容点击链接查看完整系列 上一篇网络六应用层 HTTPHTTPSURL、请求响应、Cookie 与证书 点击直达 Java后端基础专栏合集