提示:文章先作为初版,等后续时间充足后,补充更深的内容
文章目录
- JVM
- 一、垃圾回收算法
- 二、什么是STW
- 三、JVM参数
- 四、JVM内存模型
JVM
一、垃圾回收算法
JVM中的垃圾回收算法可以分为两种类型:基于引用计数的垃圾回收算法和基于可达性分析的垃圾回收算法。目前主流的JVM垃圾回收算法都是基于可达性分析的。
引用计数算法
引用计数算法是一种简单的垃圾回收算法,它的原理是通过计数来判断对象是否为垃圾对象。每个对象都有一个引用计数器,当有一个指针指向该对象时,引用计数器就会加1,当指针失效时,引用计数器就会减1。当引用计数器的值为0时,就可以判定该对象为垃圾对象。然而,这种算法有一个明显的问题,就是无法解决循环引用的问题,即两个对象互相引用的情况。
可达性分析算法
可达性分析算法是现代JVM垃圾回收算法的主流算法,它的基本思路是通过一系列称为“GC Roots”的根对象作为起点,从这些对象开始遍历整个对象图,能够到达的对象就是存活对象,不能到达的对象就是垃圾对象。根对象包括虚拟机栈中的引用对象、方法区中类静态属性引用的对象和常量引用的对象等。
可达性分析算法具有以下特点:
它可以有效地解决循环引用的问题。
它可以通过多种不同的算法实现,例如标记-清除算法、复制算法、标记-整理算法和分代算法等。
它需要暂停应用程序的执行,以便进行垃圾回收,因此可能会对程序的性能产生一定的影响。
常见的垃圾回收算法包括:
标记-清除算法:该算法分为标记和清除两个阶段,首先标记出所有存活对象,然后清除所有未标记的对象。这种算法比较简单,但是有个很严重的问题,会产生大量的内存碎片。
复制算法:为了解决标记清除算法的内存碎片问题。该算法将内存分为两个区域,每次只使用其中一个区域,当一个区域满了之后,将其中所有存活对象复制到另一个区域,然后清空原来的区域。他的问题就在于浪费空间。而且,他的效率跟存活对象的个数有关。
标记压缩算法:为了解决复制算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将边界以外的所有内存直接清除。
标记-整理算法:标记-整理算法也是为了解决内存碎片问题。与标记-压缩算法不同的是,标记-整理算法在标记阶段后,会将所有存活对象移动到一端,然后直接清理边界以外的所有内存。移动对象的操作会产生内存的空洞,但是清理完毕之后,会将所有存活对象压缩到一起,使内存空间得到了整理。该算法将存活对象压缩到内存的一端,然后清除另一端的所有未被占用的内存空间。
分代算法:分代收集算法的核心思想是将堆内存分为不同的代,一般将新生代划分为 Eden 区、Survivor 区,老年代就是指存活时间较长的对象。新生代的对象由于生命周期短,所以采用复制算法,老年代的对象则采用标记-整理或者标记-清除算法。
二、什么是STW
STW指的是Stop-The-World,即全局停顿。在Java应用程序运行的过程中,JVM需要对内存进行垃圾回收、线程栈整理、内存整理等操作,这些操作都需要暂停Java应用程序的执行,这就是STW。在STW期间,JVM会暂停所有线程的执行,包括用户线程和垃圾回收线程,直到垃圾回收等操作完成后才会继续执行。(只有在进行 **Full GC(全局垃圾回收)**时,JVM 才会暂停所有线程的执行,包括用户线程和垃圾回收线程,直到垃圾回收等操作完成后才会继续执行。这种情况下的停顿时间会比较长,可能会影响应用程序的性能和响应时间。而对于局部垃圾回收(如 Minor GC),JVM 只会暂停用户线程的执行,等待垃圾回收线程完成操作后再继续执行用户线程。这种情况下的停顿时间相对较短,不会对应用程序的性能和响应时间产生明显的影响。)
STW是为了保证垃圾回收的正确性而采取的一种措施。在垃圾回收期间,如果不停顿Java应用程序的执行,那么就有可能会产生一些问题,比如对象的引用关系会发生变化,而正在执行的线程可能会访问到不一致的对象状态,导致应用程序出现不可预知的错误。因此,在进行垃圾回收等操作时,必须要停顿Java应用程序的执行,以保证数据的一致性。
STW时间的长短和频率对Java应用程序的性能影响很大,因此,JVM一直在不断优化垃圾回收算法和机制,以减少STW的时间和频率。
三、JVM参数
JVM参数分为标准参数和非标准参数两类。标准参数是指在所有JVM实现中都必须支持的参数,非标准参数是指在某些JVM实现中才会支持的参数。
标准参数包括:
-classpath或-cp:指定类路径,多个路径之间用冒号或分号分隔(Linux或Windows环境下分别使用不同的分隔符)。
-version:输出当前JVM的版本信息。
-help:输出JVM的命令行帮助信息。
-Xms:指定JVM堆的初始大小。
-Xmx:指定JVM堆的最大大小。
-Xss:指定每个线程的栈空间大小。
-Xmn:指定新生代的大小。
-XX:+UseParallelGC:指定使用并行垃圾回收器。
-XX:+UseConcMarkSweepGC:指定使用CMS垃圾回收器。
-XX:+UseG1GC:指定使用G1垃圾回收器。
非标准参数包括:
-XX:+HeapDumpOnOutOfMemoryError:在内存溢出时自动生成堆转储快照。
-XX:MaxPermSize:指定永久代的最大大小(JDK 8及以上不支持)。
-XX:MaxMetaspaceSize:指定元空间的最大大小(JDK 8及以上使用)。
-XX:PrintGCDetails:打印GC详细信息。
-XX:SurvivorRatio:指定新生代中Eden区和Survivor区的比例。
-XX:PermSize:指定永久代的初始大小(JDK 8及以上不支持)。
-XX:MetaspaceSize:指定元空间的初始大小(JDK 8及以上使用)。
四、JVM内存模型
JVM内存模型指的是Java虚拟机运行时使用的内存模型,它将内存分为不同的区域,每个区域有不同的作用和生命周期。这些区域包括:
程序计数器(Program Counter Register):线程私有,记录当前线程执行的字节码行号,用于支持线程切换和异常处理。
虚拟机栈(Java Virtual Machine Stacks):线程私有,每个方法在执行时会创建一个栈帧,用于保存方法的局部变量、操作数栈、动态链接、方法出口等信息。栈帧随着方法的执行压入和弹出虚拟机栈,如果虚拟机栈的空间不足,会抛出StackOverflowError,如果虚拟机栈可以动态扩展但无法申请到足够的内存空间,会抛出OutOfMemoryError。
本地方法栈(Native Method Stack):和虚拟机栈的作用相似,只不过是为本地方法服务的。
堆(Heap):线程共享,用于存储Java对象实例。Java虚拟机启动时会分配一块固定大小的堆空间,可以通过设置-Xmx和-Xms来调整堆的大小。如果堆中没有足够的空间分配新的对象,会触发垃圾回收,如果垃圾回收后仍然无法获得足够的空间,会抛出OutOfMemoryError。
方法区(Method Area):线程共享,用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也被称为永久代(Permanent Generation),在JDK8及以后的版本中,被移除并被称为元空间(Metaspace)。
运行时常量池(Runtime Constant Pool):方法区的一部分,用于存储编译时生成的各种字面量和符号引用。每个类或接口都有一个运行时常量池,用于支持类或接口的运行。
直接内存(Direct Memory):JVM管理的堆外内存,通过DirectByteBuffer来进行操作,也被称为NIO堆外内存。由于堆外内存不受JVM内存限制的控制,因此可能导致系统内存的不足。