jvm基础概念
1.1 什么是JVM?
JVM(Java Virtual Machine,Java 虚拟机)是一种运行 Java 字节码的虚拟化平台。JVM 的主要作用是将编译后的 Java 字节码(.class 文件)转换为机器指令,使其能够在不同平台上运行,而不需要修改代码。通过这种机制,Java 实现了“一次编写,处处运行”的跨平台特性。
1.2 jvm的功能
JVM 的关键功能包括:
- 解释和执行字节码:JVM 将字节码转换为平台相关的机器指令,并负责执行这些指令。
- 内存管理:JVM 提供垃圾回收机制,自动管理内存分配和释放,避免了手动内存管理导致的内存泄漏等问题。
- 安全性和隔离:JVM 提供沙箱机制,确保 Java 应用的安全性,避免恶意代码对底层操作系统的直接访问。
JVM 也支持其他编程语言(如 Kotlin、Scala 等),只要这些语言编译后的字节码符合 JVM 规范。
1.3 jvm的组成
JVM(Java 虚拟机)的组成可以分为以下几个主要部分,它们共同负责字节码的加载、解释、执行以及内存管理等工作:
1. 类加载子系统(Class Loader Subsystem)
负责将 Java 字节码(.class 文件)加载到内存中,并且按照需求将类动态加载到 JVM。它的主要任务是查找并加载类文件,并在 JVM 中创建相应的类对象。类加载子系统包括以下三个阶段:
-
加载(Loading):读取字节码文件并转换为类的内存表示,javap -v,jclasslib,arthas(dump命令)工具打开。
类加载器的双亲委派机制: -
双亲委派机制的核心思想是:当一个类加载器收到加载类的请求时,它不会立即自己去加载该类,而是将该请求委派给它的父类加载器,父类加载器再将请求委派给它的父类加载器,直到最顶层的根类加载器(通常是引导类加载器,Bootstrap ClassLoader)。如果父类加载器无法完成类的加载,才会让子类加载器自己尝试去加载。
这种机制保证了类加载的一致性,特别是像
java.lang.Object
这样的核心类,总是由引导类加载器加载,这样可以避免多次加载同一个类或被不可信的类加载器覆盖。尽管双亲委派机制是 Java 类加载的一项重要原则,但在某些特殊情况下,可能会被打破,比如:SPI(Service Provider Interface)机制:Java 中的 SPI 机制会用自定义的类加载器来加载服务接口的实现类。OSGi 和 Tomcat 等框架:这些框架有自己的类加载机制,用于实现模块化或动态加载类。
- 链接(Linking):将类的二进制表示合并到 JVM 中,包括验证、准备和解析。
- 初始化(Initialization):执行静态初始化器和静态字段赋值。
2. 运行时数据区(Runtime Data Area)
JVM 在执行 Java 程序时使用的内存区域,可以分为以下几个部分:
- 方法区(Method Area):存储类的结构信息(如字段、方法、常量等)和静态变量。
- 堆(Heap):存储所有 Java 对象和数组,是 JVM 内存的最大组成部分,由垃圾回收器管理,也可向栈一样设置堆大小,-Xms,一般和栈的容量设置相同的值,避免堆内存的申请和收缩收到影响。
- 栈(Java Stack):每个线程都会有自己的栈,用于存储栈帧:局部变量、操作数栈、方法调用,异常表,动态链接(帧数据)等,可能会数据溢出,一般为1024的倍数大小,也可以自定义栈内存大小-XSS1g,一般为-xss1k到xss1025m。
- 程序计数器(PC Register):每个线程都有自己的程序计数器,用于指示当前执行的字节码指令地址,不会发生内存溢出的。
- 本地方法栈(Native Method Stack):为调用本地方法(非 Java 编写的方法)提供支持。
3. 执行引擎(Execution Engine)
执行引擎负责执行字节码,它将字节码解释为机器指令并交给 CPU 执行。执行引擎包括:
- 解释器(Interpreter):逐行解释并执行字节码,速度较慢。
- 即时编译器(JIT Compiler, Just-In-Time Compiler):将经常执行的字节码编译为机器码,提高执行效率。
- 垃圾回收器(Garbage Collector):自动管理堆内存,负责回收不再使用的对象,避免内存泄漏。
内存泄漏指的是不再使用的对象在系统中未被回收,内存泄漏的积累可能会导致内存溢出。
java中为了简化对象的释放,引入了自动的垃圾回收(Garbage Collection简称GC)机制。通过垃圾回收器来对不再使用的对象完成自动的回收,垃圾回收器主要负责对堆上的内存进行回收。其他很多现代语言比如C#、Python、Go都拥有自己的垃圾回收器。
几种常见的对象引用
可达性算法中描述的对象引用,一般指的是强引用,即是GCRoot对象对普通对象有引用关系,只要这层关系存在普通对象就不会被回收。除了强引用之外,Java中还设计了几种其他引用方式:软引用。弱引用。虚引用。终结器引用。弱引用主要在ThreadLocal中使用。弱引用对象本身也可以使用引用队列进行回收。
4. 本地方法接口(Native Interface)
JNI(Java Native Interface)允许 Java 程序调用本地(Native)代码,比如用 C 或 C++ 编写的程序。通过 JNI,Java 可以与操作系统或硬件进行交互。
5. 垃圾回收(Garbage Collection, GC)
垃圾回收器负责自动管理 JVM 堆内存,跟踪对象的生命周期,并回收不再使用的内存空间。垃圾回收器通常使用算法如标记-清除(Mark-and-Sweep)、复制、分代GC、标记-整理(Mark-and-Compact)等来完成垃圾回收工作。-verbose:gc
1.标记清除
优点:实现简单,只需要在第一阶段给每个对象维护标志位,第二阶段删除对象即可。缺点:1.碎片化问题
由于内存是连续的,所以在对象被删除之后,内存中会出现很多细小的可用内存单元。如果我们需要的是一个比较大的空间,很有可能这些内存单元的大小过小无法进行分配。
2.分配速度慢。由于内存碎片的存在,需要维护一个空闲链表,极有可能发生每次需要遍历到链表的最后才能获得合适的内存空间。
2.复制算法
吞吐量高
复制算法只需要遍历一次存活对象复制到To空间即可,比标记-整理算法少了一次遍历的过程,因而性能较好,但是不如标记-清除算法,因为标记清除算法不需要进行对象的移动
内存使用效率低
每次只能让一半的内存空间来为创建对象使用
不会发生碎片化
复制算法在复制之后就会将对象按顺序放入To空间中,所以对象以外的区域都是可用空间,不存在碎片化内存空间。
3.标记整理算法
内存使用效率高
整个堆内存都可以使用,不会像复制算法只能使用半个堆内存
不会发生碎片化
整理阶段的效率不高
整理算法有很多种,比如Lisp2整理算法需要对整个堆中的对象搜索3次,整体性能不佳。可以通过Two-Finger、表格算法、ImmixGc等高效的整理算法优化此阶段的性能
在整理阶段可以将对象往内存的一侧进行移动,剩下的空间都是可以分配对象的有效空间
G1垃圾回收器(Garbage First Garbage Collector,简称G1 GC)是Java虚拟机的一种高效的垃圾回收算法,主要用于替代传统的CMS(Concurrent Mark-Sweep)收集器。它特别适合处理具有大堆内存和对低停顿时间要求高的应用场景。
G1 GC的特点
-
区域划分(Region-based memory layout):
- 堆内存被划分成多个大小相同的区域(Regions),每个区域可能属于年轻代或老年代,避免了固定代空间大小的限制。
-
并发处理:
- G1能够在执行应用程序的同时进行垃圾回收,减少了应用程序的停顿时间。
-
分阶段回收:
- G1分为几个阶段进行回收:初始标记、并发标记、最终标记、筛选回收。尤其在“并发标记”阶段,GC可以和应用程序并发执行,不会长时间阻塞应用。
-
可预测的停顿时间:
- G1通过目标停顿时间来进行优化,允许开发人员设定一个最大停顿时间的目标,GC会根据此目标调整回收过程中的行为。
-
垃圾优先回收:
- G1根据各个区域的垃圾占比,优先回收垃圾最多的区域,最大限度地提高回收效率。
G1 GC的工作过程
G1 GC的过程可以分为以下几步:
-
年轻代垃圾回收(Young GC): 处理年轻代中的对象,对象如果存活会晋升到老年代。
-
并发标记(Concurrent Marking): 标记堆内存中的存活对象,计算每个区域的存活对象占比,识别出包含最多垃圾的区域。
-
混合回收(Mixed GC): 除了清理年轻代,还会清理一些老年代的区域,尤其是那些垃圾占比高的区域。
-
完全回收(Full GC): 当内存使用情况非常紧张时,可能会触发完全回收,但这是G1 GC尽力避免的情况,因为它会导致较长的停顿时间。
G1 GC的调优参数
-XX:MaxGCPauseMillis=<N>
:设定最大垃圾回收停顿时间的目标(毫秒)。G1 GC会尽量优化以满足这个目标。-XX:InitiatingHeapOccupancyPercent=<N>
:在堆内存使用量达到该百分比时开始并发标记阶段,默认是45%。-XX:G1ReservePercent=<N>
:保留堆内存的百分比,用于避免在垃圾回收时出现内存不足的情况。
G1 GC非常适合用于处理大规模应用程序,特别是那些对GC停顿时间敏感的场景。
垃圾回收器的组合关系虽然很多,但是针对几个特定的版本,比较好的组合选择如下:
JDK8及之前:
ParNew +CMS(关注暂停时间)Parallel Scavenge + Parallel Old (关注吞吐量)、 G1(JDK8之前不建议,较大堆并且关注暂停时间)
JDK9之后:
G1(默认)
从JDK9之后,由于G1日趋成熟,JDK默认的垃圾回收器已经修改为G1,所以强烈建议在生产环境上使用G1。