知识点汇总

知识点汇总
  • JVM是Java运行基础,面试时一定会遇到JVM相关的问题,内容相对集中,但只是对深度要求较高

  • 其中内存模型、类加载机制、GC是重点方面,性能调优部分更偏向应用,重点突出实践能力,编译器优化和执行模式部分偏向于理论,重点掌握知识点
  • 需了解内存模型各部分作用,保存哪些数据
  • 类加载:双亲委派机加载机制,常用加载器分别加载哪种类型的类
  • GC分代回收的思想和依据,以及不同垃圾回收算法的回收思路和适合场景
  • 性能调优:常用JVM优化参数作用、参数调优的依据、常用的JVM分析工具能分析哪些问题以及使用方法
  • 执行模式:解释、编译、混合模式的优缺点,Java7提供的分层编译技术、JIT即时编译技术、OSR栈上替换、C1/C2编译器针对的场景
  • 编译器优化:javac的编译过程、ast抽象语法树、编译器优化和运行期优化

知识点详解

知识点详解
  1. JVM内存模型
    • 线程独占:栈、本地方法栈、程序计数器
    • 线程共享:堆、方法区
    • 又称方法栈,线程私有的,线程执行方法是都会创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈
  2. 本地方法栈
    • 与栈类似,也是用来保存执行方法的信息,执行Java方法是使用占,执行Native方法时是使用本地方法栈
  3. 程序计数器
    • 保存当前线程执行的字节码位置,每个线程工作时都有独立的计数器,值为执行Java方法服务,执行Native方法时,程序计数器为空
    • JVM内存管理最大的一块,堆被线程共享,目的是存放对象的实例,几乎所有对象的实例都会放在这里。当堆没有可用空间时,会抛出OOM异常(Out of Menory内存溢出),根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理
  4. 方法区
    • 又称非堆区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器优化后的代码等数据
    • 1.7的永久代和1.8的源空间都是方法区的一种实现
  5. JVM内存可见性
    • JMM是定义程序中变量的访问规则,线程对变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作,由于指令重排,可能会导致读写的顺序被打乱,因此JMM需要提供原子性、可见性、有序性保证

说说类加载和卸载

加载过程

加载过程

  1. 加载:通过类的全限定名,查找此类的字节码文件,利用字节码文件创建Class对象
  2. 链接:分为三个阶段
    • 验证:确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全(前面的文章中修改魔数cafebabe之后,验证就失败了)
    • 准备:进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null),不包含final修饰的静态变量,因为final变量在编译时就分配好了
    • 解析:将常量池中的符号引用替换为直接引用的过程,直接引用为直接指向目标的指针或者相对偏移量等
  3. 初始化:主要完成静态块执行以及静态变量的复制,先初始化父类,再初始化当前类。初始化是懒惰的,只有对类主动使用的时候才会初始化

加载机制

加载机制
  • 加载机制:双亲委派模式
    • 当一个类加载器收到类加载请求时,它首先会将这个请求委托给父类加载器去处理。如果父类加载器无法加载该类,则该类加载器才会自己去加载这个类。
    • 优点:避免类的重复加载,避免Java的核心API被篡改

卸载过程

卸载过程
  • 类卸载的实现依赖于JVM的垃圾回收机制。当一个类不再被引用时,JVM可能会通过垃圾回收机制将该类的实例回收

简述一下JVM内存模型

简述一下JVM内存模型
  • JVM内存模型(JVM Memory Model,JMM)是Java虚拟机用来描述多线程程序中各个线程之间以及线程和内存之间的交互关系的规范。JMM定义了线程的工作内存和主内存之间的交互方式,并规定了在何时如何把工作内存中的数据同步回主内存,或者如何从主内存中读取数据到工作内存中。JMM的设计目的是为了保证在多线程程序中,无论运行在什么平台和处理器架构上,Java程序都能达到一致的内存访问效果。Java开发人员在编写多线程时必须遵守JMM规范来保证程序的正确性

说说堆和栈的区别

说说堆和栈的区别
  1. 内存管理方式:栈采用静态内存分配,而堆采用动态内存分配。栈的大小在编译时就已经确定,而堆的大小可以在运行时动态调整
  2. 存储内容:栈主要用于存储基本数据类型(int、float、boolean等)以及对象的引用,而堆主要负责存储对象实例
  3. 内存分配方式:栈的内存分配方式是后进先出,而堆是随意分配、随意释放
  4. 存储效率:由于栈采用静态内存分配,因此它的存取速度比堆更快,而堆则因为需要进行动态内存分配和垃圾回收等操作,因此存取速度相对较慢
  5. 内存回收机制:栈的内存由JVM自动管理,当方法结束时,栈中的内存会自动被释放;而堆内存则由JVM的垃圾回收机制进行管理,当对象没有被引用时,垃圾回收机制会自动回收该对象占用的内存空间

什么时候会触发FULL GC

什么时候会触发FULL GC
  • 除直接调用System.gc外,触发Full GC执行的情况有如下四种。
  1. 老年代空间不足

    • 老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:java.lang.OutOfMemoryError: Java heap space。为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
  2. 永久代空间满

    • 永久代中存放的为一些类的信息等,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
  3. CMS GC时出现晋升失败和并发模式失败

    • 对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有晋升失败和并发模式失败两种状况,当这两种状况出现时可能会触发Full GC。晋升失败是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;并发模式失败是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。应对措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。
  4. 统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间

    • 这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到老年代导致老年代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间,那么就直接触发Full GC。

什么是Java虚拟机?为什么Java被称为平台无关的编程语言

什么是Java虚拟机?为什么Java被称为平台无关的编程语言
  • Java虚拟机(JVM)是Java语言的核心组成部分之一,是一种在计算机上模拟运行Java字节码的虚拟机。Java虚拟机可以理解为一个运行Java程序的环境,它提供了一个统一的接口,将Java程序与底层操作系统隔离开来,使得Java程序可以在不同的操作系统上运行。

  • Java被称为平台无关的编程语言,这是因为Java程序是编译成Java字节码,而不是针对某个具体的操作系统或硬件架构编译的机器码。Java字节码可以在任何装有Java虚拟机的操作系统上运行,因为Java虚拟机会将字节码转换成操作系统能够理解的机器码,从而实现跨平台的能力。

Java内存结构

Java内存结构
  1. 程序计数器(Program Counter Register):每个线程都有一个程序计数器,它是线程私有的,用于记录当前线程正在执行的字节码指令的地址或者下一条指令的地址。如果执行的是native方法,则计数器为空。
  2. Java虚拟机栈(Java Virtual Machine Stack):Java虚拟机栈也是线程私有的,用于存储每个县城的方法调用栈。每当一个方法被调用时,JVM都会为该方法分配一个栈帧,该栈帧包含了该方法的参数、局部变量以及操作数栈等信息。方法在返回时,JVM会弹出该栈帧。栈的大小可以是固定的,也可以是动态扩展的。栈的大小决定了方法调用的可达深度(递归调用次数,或者嵌套调用层数等,可以使用-Xss参数设置虚拟机栈的大小)。如果请求的栈深度大于最大可用深度,则会抛出StackOverFlowError。如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutOfMemoryError
  3. 本地方法栈(Native Method Stack):本地方法栈也是线程私有的,与虚拟机栈类似,不同之处在于它为JVM执行本地方法(Native Method)服务
  4. 方法区(Method Area):方法区用于存储已被加载的类的信息、常量池、静态变量、即时编译器编译后的代码等信息。方法区也是由垃圾回收器进行管理和回收的
  5. (Java Heap):堆是JVM中最大的内存区域,用于存储对象实例以及数组等数据。Java堆的内存空间是由垃圾回收器进行管理和回收的

说说对象分配规则

说说对象分配规则
  • 对象优先分配在Eden区,如果Eden区没有足够的空间,虚拟机执行一次Minor GC
  • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区(from和to)之间发生大量的内存拷贝(新生代采用复制算法收集内存,避免产生大量内存碎片)
  • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了一次Minor GC,那么对象就会进入Survivor区,之后每经过一次Minor GC,对象年龄加一,直到达到阈值,对象进入老年区
  • 动态判断对象的年龄,如果Survivor区中相同年龄的虽有对象大小总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象可以直接进入老年代。
  • 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小
    • 如果这个值大于老年区的剩余值,则进行一次FULL GC,
    • 如果小于,则检查HandlePromotionFailure设置,如果为true,则只进行Monitor GC,如果为false,则进行FULL GC

说说Java对象创建过程

说说Java对象创建过程
  • Java对象的创建过程可以概括为以下几个步骤
    1. 类加载:JVM会先检查类是否已经被加载了,如果没有则通过类加载器加载类的class文件,并将类的信息存储到方法区中
    2. 内存分配:当类被加载后,JVM会为该类的对象分配内存,根据Java对象的特点,内存大小是在编译时就已经确定的,因此内存分配可以通过一些简单的算法来实现,例如指针碰撞空闲列表
    3. 初始化:内存分配完成后,JVM会对对象进行默认初始化,即将对象的成员变量赋上默认值。基本类型的默认值是0或false,引用类型的默认值是null
    4. 构造函数:默认初始化后,JVM会调用该对象的构造函数,进行对象的属性初始化和一些其他操作
    5. 返回地址:构造函数执行完毕后,JVM会将对象的引用返回给调用者,此时对象创建过程完毕

知道类的生命周期吗

知道类的生命周期吗
  • 类的生命周期包括:加载、链接、初始化、使用和卸载
    1. 加载:查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
    2. 链接:链接分为三个阶段,验证、准备、初始化
      • 验证:确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全(前面的文章中修改魔数cafebabe之后,验证就失败了)
      • 准备:进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null),不包含final修饰的静态变量,因为final变量在编译时就分配好了
      • 解析:将常量池中的符号引用替换为直接引用的过程,直接引用为直接指向目标的指针或者相对偏移量等
    3. 初始化:为类的静态变量赋予正确的初始值
    4. 使用:new出对象在程序中使用
    5. 卸载:当该类不再被引用时,执行垃圾回收

简述Java对象结构

简述Java对象结构
  • Java对象由三个部分组成:对象头、实例数据、对其填充
    1. 对象头:对象头由两部分组成
      • 第一部分存储对象自身运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)
      • 第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象投中还有一部分用来记录数组长度
    2. 实例数据:用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
    3. 对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)

如何判断对象可以被回收

如何判断对象可以被回收
  • 判断对象是否存活一般有两种方式
    1. 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1。计数为0时可以回收,此方法较为简单,无法解决对象循环引用的问题
    2. 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径成为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,即为不可达对象,可以被回收

你知道哪些垃圾回收算法

你知道哪些垃圾回收算法
  • GC最基础的算法有三种:标记-清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法
    1. 标记-清除算法:首先标记出所有仍然使用的对象,在标记完成后清理掉未被标记的垃圾对象。它的主要缺点是会产生内存碎片。
    2. 复制算法:将堆内存分为两个区域,每次只使用其中一块。当着一块用完之后,将所有存活的对象复制到另一块未使用的区域,同时将这一块整个清空。这种算法的缺点是需要耗费两倍的内存空间,适用于对象存活率比较低的情况
    3. 标记-压缩算法:标记过程与标记清除算法一样,但后续的步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向同一端移动,然后清理掉端边界以外的内存。它的主要缺陷是需要大量的对象移动操作,影响性能
    4. 分代收集算法:根据对象的特征和存活周期,将堆内存划分为不同的区域,一般分为新生代和老年代。不同的区域使用不同的垃圾收集算法和参数设置。例如年轻代通常采用复制算法,老年代采用标记整理算法或分块整理等
    5. G1收集器:它是一个面向服务端应用的垃圾收集器,采用分代收集算法,将堆内存划分为多个region,并动态做垃圾回收。G1适用于大型、多核、高并发应用下的垃圾回收

你知道哪些JVM诊断和监控工具

你知道哪些JVM诊断和监控工具
  • JVM调优命令大多数都是针对HotSpot虚拟机的。以下列举了一些常见的JVM调优命令:
  1. jps(JVM Process Status Tool):用于显示指定系统内所有的HotSpot虚拟机进程。

  2. jstat(JVM statistics Monitoring):用于监视虚拟机运行时状态信息的命令,可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

  3. jmap(JVM Memory Map):用于生成虚拟机Heap dump文件。

  4. jhat(JVM Heap Analysis Tool):与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。

  5. jstack:用于生成Java虚拟机当前时刻的线程快照。

  6. jinfo(JVM Configuration info):实时查看和调整虚拟机运行参数。

  7. Java VisualVM:基于图形界面的虚拟机监控和分析工具,可以显示系统中运行的多个JVM实例,并提供强大的信息收集和可视化功能。

  8. Java Mission Control (JMC):商业级别的JVM监控和管理工具,提供更高级和精细的诊断和调试能力。

  9. GCViewer:用于分析和可视化JVM垃圾收集数据的开源工具。

  10. JConsole:Java GUI工具,可以连接到JVM进程并显示重要的性能指标和变量值。

  11. Perf:Linux平台上常用的性能监控工具,其中包含针对JVM应用程序的性能测量工具。

Minor GC和Full GC分别发生在什么时候

Minor GC和Full GC分别发生在什么时候
  • 在新生代垃圾收集时,年轻代中的Eden区满了就会触发Minor GC。此外,还有一种情况是,当对象经过多次Minor GC后仍然存活并且无法放入Survivor区域时,它们就会被晋升到老年代。如果老年代空间也不够用,就会出现Full GC。

你知道哪些JVM性能调优参数

你知道哪些JVM性能调优参数
  1. -Xms:设置JVM堆最小值,如-Xms2g表示堆的最小值为2GB。

  2. -Xmx:设置JVM堆最大值,如-Xmx4g表示堆的最大值为4GB。

  3. -XX:NewRatio:设置年轻代与老年代的比例,默认为2,如-XX:NewRatio=3表示年轻代与老年代的比例为3:1。

  4. -XX:MaxPermSize:设置永久代的最大大小,如-XX:MaxPermSize=256m表示设置最大永久代大小为256MB。

  5. -XX:+UseParallelGC:启用并行垃圾回收器,适用于多核服务器环境。

  6. -XX:+UseConcMarkSweepGC:启用CMS垃圾回收器,适用于在用户线程不可中断的情况下执行垃圾收集。

  7. -XX:+UseG1GC:启用G1垃圾回收器,适用于大内存和高并发环境。

  8. -verbose:gc:开启JVM堆垃圾回收的详细输出,以便分析GC行为和性能瓶颈。

  9. -XX:+HeapDumpOnOutOfMemoryError:在内存溢出错误发生时自动转储此时的堆内存内容,方便后续分析错误原因。

对象一定分配在堆中吗?有没有了解逃逸分析技术

对象一定分配在堆中吗?有没有了解逃逸分析技术
  • 对象不一定分配在堆中,JVM会对对象进行逃逸分析,即分析对象是否会被方法外的其他代码使用,如果不会,则可以将对象的内存分配在栈上,从而提升性能。
  • 逃逸分析(Escape Analysis)是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编辑器能够分析出一个新的对象的引用的适用范围,从而决定是否要将该对象分配到堆上
  • 逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,我们就成长股对象的指针发生了逃逸
  • 逃逸分析的好处
    1. 栈上分配:可以降低垃圾收集器运行的频率
    2. 同步消除:如果发现某个对象只能从一个线程访问,那么在这个度向上的操作可以不需要同步
    3. 标量替换:把对象分解为一个个基本模型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有:减少内存使用,因为不用生成对象头;程序内存回收效率高,并且GC频率也会减少

虚拟机为什么使用元空间替换了永久代

虚拟机为什么使用元空间替换了永久代
  • 移除永久代主要是解决了几个问题:
  1. 内存溢出问题。永久代的大小是固定的,当加载的类和方法等元数据超出了其大小的限制,就会出现OutOfMemoryError。而元空间的大小是动态变化的,取决于运行时的需求和系统内存大小,因此更加灵活,可以避免因为加载的类和方法等元数据过多而造成的内存溢出问题。

  2. 内存回收效率问题。永久代的内存回收效率非常低,常常需要进行Full GC来回收垃圾,导致程序的性能下降。而元空间采用的是基于标记清除算法的内存回收方式,由于其内存大小的动态变化,所以可以分阶段进行内存回收,避免了Full GC的发生,因此能够更加高效地回收垃圾。

  3. 元数据共享问题。在永久代中,每个虚拟机都有一份私有的永久代,即便是加载相同的类和方法,也会占用不同的内存空间。而在元空间中,可以实现元数据的共享,避免了重复加载相同的类和方法等元数据,节约了内存空间。

什么是Stop The World?什么是OopMap?什么是安全点

什么是Stop The World?什么是OopMap?什么是安全点
  1. Stop The World指的是在Java虚拟机(JVM)中,为了进行垃圾回收或其他一些必须操作,而暂停所有应用程序线程的状态。这种状态的出现是由于垃圾回收器只能在应用程序线程暂停时访问内存。当应用程序线程暂停时,所有线程都被挂起,无法继续执行,直到垃圾回收完成并且内存管理完成。

  2. OopMap是指对象指针映射(Object Oriented Pointer Maps)的简称。在HotSpot虚拟机中,当进行垃圾回收时,需要扫描堆内存中的对象,使用OopMap可以更快地确定哪些对象是可达的。

  3. 安全点(Safepoint)是指在应用程序的执行过程中,合适的位置,安全地停止应用程序,在这个位置上应用程序的堆栈和寄存器信息都是可知的。在这个点上,JVM可以停止所有应用程序线程,以便执行垃圾回收或其他操作。在安全点上停止应用程序线程对应用程序的执行是透明的,并且返回应用程序恢复执行的速度会很快。

说一下JVM的主要组成部分及其作用

说一下JVM的主要组成部分及其作用
  • JVM包含两个子系统和两个组件
    1. 类加载器子系统(Class Loader):负责将.class文件中的二进制字节码加载到内存中,并生成对应的Class对象。
    2. 执行引擎子系统(Execution Engine):负责解释执行字节码或者运行时动态生成的本地代码,是JVM最核心的部分。
    3. 运行时数据区组件(Runtime Data Areas):包括方法区、堆、虚拟机栈、本地方法栈和程序计数器等。其中,方法区用于存储类的信息、常量池、静态变量、即时编译器编译后的代码等;堆用于存储创建的对象实例;虚拟机栈和本地方法栈用于存储方法执行过程中的局部变量表、操作数栈、方法出口等信息;程序计数器用于记录线程执行位置的指针。
    4. 本地方法接口组件(Native Interface):与C/C++等语言交互的接口,可以调用底层系统提供的库函数。

什么是指针碰撞

什么是指针碰撞
  • 一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的内存都被放到一边,空闲内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配的内存仅仅是把那个空闲指针向空闲方向挪动一段与对象大小相等的实例,这种分配方式就是指针碰撞

什么是空闲列表

什么是空闲列表
  • 如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞。虚拟机必须维护一个列表,记录哪些内存是可用的,再分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表

什么是TLAB

什么是TLAB
  • TLAB(Thraed-Local Allocation Buffer)是Java虚拟机针对线程私有对象内存分配的而设计的一种优化手段。他是在Java堆内存中划分出每个线程私有的缓存区域,线程可以在这个缓存区中分配内存,无需同步,从而避免了多线程竞争和同步带来的性能开销。通过VM参数-XX:+UseTLAB来启用

对象头具体包含哪些内容

对象头具体包含哪些内容
  • 对象头包含以下两部分内容
    1. Mark Word:标记字段包含一些标记位和哈希码信息。
      • 其中标记位包含对象的锁状态、GC状态、偏向锁线程ID等信息
      • 哈希码是为了提高JVM在哈希表等数据结构中查找对象的效率而添加的,可以提高查找速度的同时不会涉及到对象的内容
    2. Class Pointer:类指针指向该对象的类元数据信息,JVM通过该指针可以确定对象所属的类,进而获取类的类型信息和方法信息

你知道哪些JVM调优参数

你知道哪些JVM调优参数
  • 堆栈内存相关
    • -Xms 设置初始堆的大小
    • -Xmx 设置最大堆的大小
    • -Xmn 设置年轻代大小,相当于同时配置-XX:NewSize和-XX:MaxNewSize为一样的值
    • -Xss 每个线程的堆栈大小
    • -XX:NewSize 设置年轻代大小(for 1.3/1.4)
    • -XX:MaxNewSize 年轻代最大值(for 1.3/1.4)
    • -XX:NewRatio 年轻代与年老代的比值(除去持久代)
    • -XX:SurvivorRatio Eden区与Survivor区的的比值
    • -XX:PretenureSizeThreshold 当创建的对象超过指定大小时,直接把对象分配在老年代。
    • -XX:MaxTenuringThreshold设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代
  • 垃圾收集器相关
    • -XX:+UseParallelGC:选择垃圾收集器为并行收集器。
    • -XX:ParallelGCThreads=20:配置并行收集器的线程数
    • -XX:+UseConcMarkSweepGC:设置年老代为并发收集。
    • -XX:CMSFullGCsBeforeCompaction=5 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行5次GC以后对内存空间进行压缩、整理。
    • -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
  • 辅助信息相关
    • -XX:+PrintGCDetails 打印GC详细信息
    • -XX:+HeapDumpOnOutOfMemoryError让JVM在发生内存溢出的时候自动生成内存快照,排查问题用
    • -XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FULL GC造成问题.
    • -XX:+PrintTLAB 查看TLAB空间的使用情况

说一下JVM有哪些垃圾回收器

说一下JVM有哪些垃圾回收器
  1. Serial收集器(复制算法):

    • 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  2. ParNew收集器 (复制算法):

    • 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  3. Parallel Scavenge收集器 (复制算法):

    • 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  4. Serial Old收集器 (标记-整理算法):

    • 老年代单线程收集器,Serial收集器的老年代版本;
  5. Parallel Old收集器 (标记-整理算法):

    • 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
  6. CMS(Concurrent Mark Sweep)收集器(标记-清除算法):

    • 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
  7. G1(Garbage First)收集器 (标记-整理算法):

    • Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
  8. ZGC (Z Garbage Collector):

    • 是一款由Oracle公司研发的,以低延迟为首要目标的一款垃圾收集器。它是基于动态Region内存布局,(暂时)不设年龄分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的收集器。在 JDK 11 新加入,还在实验阶段,主要特点是:回收TB级内存(最大4T),停顿时间不超过10ms。优点:低停顿,高吞吐量,ZGC 收集过程中额外耗费的内存小。缺点:浮动垃圾。

如何选择垃圾收集器

如何选择垃圾收集器
  • 如果你的堆大小不是很大(比如 100MB),选择串行收集器一般是效率最高的。

    • 参数:-XX:+UseSerialGC。
  • 如果你的应用运行在单核的机器上,或者你的虚拟机核数只有单核,选择串行收集器依然是合适的,这时候启用一些并行收集器没有任何收益。

    • 参数:-XX:+UseSerialGC。
  • 如果你的应用是“吞吐量”优先的,并且对较长时间的停顿没有什么特别的要求。选择并行收集器是比较好的。

    • 参数:-XX:+UseParallelGC。
  • 如果你的应用对响应时间要求较高,想要较少的停顿。甚至 1 秒的停顿都会引起大量的请求失败,

    • 那么选择 G1、ZGC、CMS 都是合理的。虽然这些收集器的 GC 停顿通常都比较短,但它需要一些额外的资源去处理这些工作,通常吞吐量会低一些。
    • 参数:
      • -XX:+UseConcMarkSweepGC
      • -XX:+UseG1GC
      • -XX:+UseZGC
  • 从上述出发点来看,对于 Web 服务器这样对响应性要求非常高的应用程序,CMS、G1、ZGC可能是最好的选择。对于某些定时任务,使用并行收集器则是一个合理的选择。

什么是类加载器

什么是类加载器
  • 类加载器(Class Loader)是Java虚拟机的一个子系统,主要负责加载Java字节码文件(.class)并将其转换为运行时的Java类。当一个类被首次使用时,类加载器会将其读入内存,并生成一个对应的Class对象,用于在运行时对这个类进行访问。

  • 类加载器是Java虚拟机中一个非常重要的机制,因为它可以实现类的动态加载,也就是说,程序在运行时可以加载之前没有预先定义的类。这个机制主要用于实现Java应用程序的插件化和动态扩展功能。

  • Java虚拟机本身提供了三种类加载器:启动类加载器(Bootstrap Class Loader)、扩展类加载器(Extension Class Loader)和系统类加载器(System Class Loader)。每个类加载器都有其特定的加载范围和加载方式,并且对于同一个类,不同的类加载器可能会产生不同的实例。

什么是Tomcat类加载机制

什么是Tomcat类加载机制
  • Tomcat是一个Java Web应用程序服务器,其类加载机制是基于Java虚拟机(JVM)的标准类加载器体系,并在此基础上进行了一定的扩展和优化。与Java虚拟机的标准类加载器类似,Tomcat的类加载器也采用了委派机制进行类的加载。

  • 当Tomcat需要加载一个类时,它会首先查找父类加载器是否已经加载了该类,如果没加载则会委派给父类加载器加载,直到最终是启动类加载器去加载,如果该类在父类加载器中已经被加载,则Tomcat的加载器会直接返回父类加载器所加载的类。

  • 为了支持Web应用程序的隔离和热部署,Tomcat的类加载器体系还包括了一个专门的Web应用程序类加载器,每个Web应用程序在Tomcat中都会拥有一个独立的类加载器。Web应用程序类加载器会首先加载Web应用程序自身的类,然后委派父类加载器加载其他依赖的类。

  • 这种基于委派机制的类加载器体系,使得Tomcat可以实现Web应用程序的隔离和动态部署,同时也保证了Web应用程序与Tomcat本身的类库不会发生冲突。