网上各种关于JVM的知识比较零碎且学术化,不利于理解记忆,这里按照问题-目标-方案(结构、算法)的结构梳理下JVM的存在。
JVM概述
为什么需要JVM?(根据LLM的回答总结的)
- 1.代码跨平台需求 为了解决高级编程语言要在不同操作系统下运行要重新编译(多数情况要修改代码)的可移植性问题,Java语言被设计为具有跨平台的特性,即“Write Once, Run Everywhere!”,因此面临屏蔽程序代码编译运行时屏蔽操作系统差异的问题,所以需要一个介于操作系统和编程语言的中间层——JVM来解决这个问题
- 内存管理需求
传统高级编程语言如C/C++的开发者要对手动管理内存,这样很容易出现内存溢出和空指针问题。所以Java的设计理念中加入了安全、方便的自动内存管理机制,以使程序员不再被内存管理细节困扰,专注于上层程序逻辑。
JVM提供了哪些功能来解决这些问题?
- 1.字节码机制:解决跨平台问题 Java语言规范定义了统一的Java字节码格式,Java编程语言先编译为Java字节码,然后装载进JVM运行,如下图所示
有无虚拟机的编译运行过程差别如下图所示:
由于Java语言规范统一了字节码的格式,所以Java程序(.java文件)编译为字节码文件(.class文件)后可以在所有符合规范的虚拟机上运行,实现了"一次编译,随处运行"的目标。这里其实是通过虚拟机屏蔽了操作系统及硬件的差异对编程语言的影响。
- 2.自动内存管理机制 通过提供自动的内存分配、回收等机制来实现内存的自动管理,具体包括内存划分模型、内存垃圾回收器等。 JVM在演变的诸多版本中形成了多种内存划分方法、回收算法。这里主要列出核心的回收思想。
- 分代回收:一般会划分Heap、元数据区/方法区、Thread Space(含虚拟机栈、本地方法区、程序计数器)、堆外内存等,G1其实是在分代的基础上又增加了分区的概念,有点类似于操作系统内存的段页式管理。其中元数据区和线程区一般为虚拟机自我管理区,除了一些极端情况受程序代码影响较较小,堆外内存在代码涉及IO较多的场景下会使用到,受应用程序代码及启动参数影响最大的是堆内存(HeapSpace),对内存的合理使用也对应用程序的有效工作时长影响最大,所以JVM参数调优也主要是针对这部分。
- 标记复制和标记清除:JVM 内存垃圾回收以对象为单位,核心思想是通过GCRoot的可达性分析来确认要回收的对象,然后调用对象的finalize()方法,这里边要解决的问题是标记阶段的效率和回收之后的内存碎片问题,各种回收器中贯穿的核心算法是标记复制和标记清除,标记复制思想主要是为了规避堆存碎片将存活对象拷贝到连续的内存区域,而标记清除算法则标记完成后在原有内存区块进行清除。
- 分治法:在内存较大时(>=32G),对大内存区块(主要是堆)中的对象进行一次标记或者复制的性能急剧下降,所以JDK1.7开始引进了G1回收器,对内存区块又进行了拆分,通过分治法将大内存区块划分为等量小块(Region),需要垃圾回收时以Region为单位进行回收。
除此之外JVM还进行哪些工作?
类的生命周期及加载机制
- 载入:Class文件通过类加载器进行加载,加载过程首先会读取文件流、按照JVM规范校验文件格式、然后解析类文件内容、为类文件申请内存空间、最后进行初始化,需要注意的是类文件存储在MetaSpace,而类对象存放在HeapSpace中。
- 使用:类被加载时虚拟机提供的三种类加载器BootstrapClassLoader、ExtentionClassLoader和ApplicationClassLoader遵循双亲委派原则,这种机制有助于保障JVM类库的安全及多应用之间的隔离性。类对象的初始化在出现new关键字、调用子类、调用类静态成员或静态方法、反射调用等情况下会被初始化,然后投入使用。
- 回收:使用过程中Class对象随着HeapSpace的回收活动,有可能被回收掉。而类文件本身,在关联的类类加载器被回收后,也可能随着引用关系消失而被回收掉。
native 接口
提供JVM本地功能或操作系统功能的开放接口。
弱引用、软引用、虚引用(phantom)
JAVA中为了实现更加灵活的内存管理,提供了Reference机制。他们的特点分别如下: 强引用:直接声明赋值的对象,被强应用的对象不会被回收。 软引用:内存不足时会被回收。 弱引用:不论内存是否充足,只要进行垃圾回收,被应用对象就会顺便被回收。 虚引用:主要用于跟踪对象被垃圾回收的状态。
JVM调优工具
如GC日志、jstat、jmap、jconsole,Java Visual Mission等
JVM自身面临的主要问题是什么?
JVM调优主要解决以下几个问题 1.不管哪种回收算法都会占用应用程序资源和“Stop The World”,所以调优要降低垃圾回收的频次。 2.降低“Stop The World”的时间。对于面向用户即时响应的高并发应用,STW的时间长不仅会导致用户体验下降而且会导致应用请求积压带来网络阻塞,进入用户不断重复提交的恶性循环,直到引发系统崩溃。 3.调优主要在响应时间、吞吐量、系统硬件容量的基础上制定出合理的内存分配方案及使用合理的内存回收算法,实现1和2。
JVM是如何解决这些问题的,或者我们需要怎么面对这些问题?
1.事前进行合理的JVM参数设置并充分测试; 2.事中进行JVM状态监控,精准设置报警阈值,及时感知异常情况,保留内存快照并快速处置; 3.事后进行充分复盘分析,有效改进。