JVM理解(通俗易懂)
虽然网上有很多关于JVM的教程,但是都天花乱坠,很多都是一上来就JVM内存模型、JVM双亲委派等等,(可能我比较菜看不懂)。于是我自己决定写一篇能看懂的文章~如果有看不懂我就自己百度,大家有什么疑问也可以评论区交流~ 欢迎指点我的Error~
JVM概念
JVM大家都知道,就Java虚拟机嘛。但JVM名称的由来实际上是Java Virtual Machine 的缩写~
它是一台虚拟出来的计算机,能够运行在咱们平时所用的Window/Linux/Mac电脑上,也正因如此,Java能够实现跨平台性。
其次,Java虚拟机不与硬件直接交互,而是直接作用于操作系统。通过上面描述的操作系统能够帮我们完成与硬件的交互工作。大家可以看这张图:
JVM体系
我们平时所写的Java文件,我们都知道,它是会被编译器编译成二进制的.class后缀的文件。后续的执行流程,涉及几个非常重要的概念,我分小标题讲解一下。
类加载器(Class Loader)
JVM执行Java文件时,会通过类加载器加载对应的class文件到JVM中,此时正式进入JVM执行环节。
方法区(Method Area)
方法区主要存放元数据信息,如类信息、常量、静态变量、编译后到代码等。类加载器将class文件会先放入方法区。
堆区(Heap)
在Java内存架构中,堆内存(Heap)是对象实例的专属存储区域。垃圾收集器(Garbage Collector, GC)的核心使命便是扫描堆内存,识别并回收那些不再被引用的"垃圾对象",从而释放内存空间供新对象分配使用。
在JDK 1.8的里程碑式更新中,永久代(Permanent Generation, PermGen)这一传统方法区实现被彻底移除,取而代之的是元空间(Metaspace)。发展历程如下所示:
版本 | 永久代变化 |
---|---|
JDK6 | 永久代用于存储类的元数据,包括类的名称、字段、方法、常量池等信息。永久代有固定大小限制,由-XX:MaxPermSize参数设置,当类加载过多时,容易出现java.lang.OutOfMemoryError: PermGen space错误。 |
JDK7 | 永久代开始被元空间替代,元空间使用本地内存,允许更灵活的内存管理。虽然永久代仍然存在,但部分元数据已开始迁移到元空间。 |
JDK8 | 永久代被完全移除,元空间成为存储类元数据的区域。元空间使用本地内存,不再受限于JVM堆内存,默认情况下会根据应用需要动态调整大小。元空间的大小可以通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize等参数控制。 |
栈区(Stack)
栈区是我们代码的运行空间,栈区有虚拟机栈与本地方法栈。咱们编写的每一个方法都会在栈区执行,同时还保存了方法的局部变量、操作数栈和返回地址等信息。栈区属于线程独享区域~
本地方法栈(Native Method Stack)
我们查看Thread源码时,会惊奇的发现,源码几乎看不到什么实现,只有一个start0()方法,用native修饰且无方法体。它是由C来实现的,一般此类方法会被放入本地方法栈。
虚拟机栈
-
异常说明
当Java线程执行过程中,若其方法调用的栈帧深度超出了虚拟机栈预设的最大容量限制,系统将抛出StackOverflowError。这种错误常见于深度递归场景——每次递归调用都会在栈中新增一个栈帧,若递归终止条件缺失或栈容量不足,栈帧将持续累积直至突破栈深度阈值。
现代JVM(如HotSpot)支持虚拟机栈的动态扩展机制。当线程执行需要更多栈空间时,JVM会尝试扩展栈的存储空间。然而,这种扩展并非无限制:若系统内存资源已耗尽,或扩展后的栈空间仍无法满足需求,JVM将抛出OutOfMemoryError。这种错误表明程序对内存的需求已超出当前系统可分配资源的极限,需通过优化内存使用或增加堆外存配置来解决。 -
生命周期
在Java内存体系中,栈(Stack)作为线程私有的内存区域,其内存管理机制具有独特特性。与堆(Heap)不同,栈不存在垃圾回收机制,因为栈帧的分配与回收遵循严格的LIFO(后进先出)原则。当方法执行完毕时,其对应的栈帧会自动弹出栈并释放内存,无需依赖垃圾回收器的介入。
栈的生命周期与所属线程紧密绑定。当线程被创建时,JVM会为其分配独立的栈空间;当线程终止时,其栈内存也随之销毁,实现了资源的自动回收。这种设计保证了线程间栈内存的隔离性,避免了多线程环境下的内存冲突。在栈内存的分配层面,以下三类数据会在栈中占据空间:
基本类型变量:包括byte、short、int、long、float、double、char、boolean这8种基本数据类型的局部变量。
对象引用变量:存储对象在堆内存中的地址指针,而非对象本身。