JVM
阅读提示
建议先看题目目录,再按“概念 -> 原理 -> 场景 -> 优化”顺序复习。
每题先讲结论,再补关键机制和项目实践,回答会更稳。
1、JAVA 四种引用类型(了解)
强引用:
描述:强引用是我们最常见的引用类型,只要一个对象具有强引用,它就不会被垃圾回收器回收。
例子:
Object obj = new Object();在这个例子中,
obj是一个强引用,指向Object的一个实例。只要obj还在使用,这个Object实例就不会被回收。软引用:
描述:软引用是一种内存敏感的引用,如果一个对象只具有软引用,那么在内存空间不足时,垃圾回收器会考虑回收它。
例子:
SoftReference<Object> softRef = new SoftReference<>(new Object());在这个例子中,
softRef是一个软引用,指向一个Object实例。如果内存不足,这个实例可能会被回收。弱引用:
描述:弱引用比软引用更加弱,它不足以阻止对象的垃圾回收。无论当前内存空间足够与否,只要垃圾回收器发现了只具有弱引用的对象,就会回收它。
例子:
WeakReference<Object> weakRef = new WeakReference<>(new Object());在这个例子中,
weakRef是一个弱引用,指向一个Object实例。无论内存空间如何,这个实例都可能被回收。虚引用:
描述:虚引用是所有引用类型中最弱的一个。它无法阻止垃圾回收器的回收过程,也无法通过虚引用来获取对象的实例。它的主要作用是跟踪对象被垃圾回收的状态。
例子:
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), new ReferenceQueue<>());- 在这个例子中,
phantomRef是一个虚引用,指向一个Object实例,并且关联了一个引用队列。这个实例随时可能被回收,而且回收时,phantomRef会被加入到关联的引用队列中。
2、类加载过程(按图顺序向面试官介绍,细节了解)

- 加载:加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。
- 验证 :这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 准备:为类变量分配内存并设置类变量(是被static修饰的变量,变量不是常量,所以不是final的,就是static的)初始值的阶段。这些变量所使用的内存在方法区中进行分配。比如
private static int age = 26;
类变量age会在准备阶段过后为 其分配四个(int四个字节)字节的空间,并且设置初始值为0,而
不是26。
若是final的,则在编译期就会设置上最终值。解析:JVM会在此阶段把类的二进制数据中的符号引用替换为直接引用。
符号引用:符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有 了直接引用,那引用的目标必定已经在内存中存在。
初始化 :初始化阶段是执行类构造器
<clinit>()方法的过程,到了初始化阶段,才真正开始执行类定义的Java程序代码(或者说字节码 )。比如准备阶段的那个age初始值是0,到这一步就设置为26。使用:对象都出来了,业务系统直接调用阶段。
卸载 :用完了,可以被GC回收了。
3、类加载器种类以及加载范围

参考回答:
启动类加载器(Bootstrap ClassLoader)
最顶层类加载器,他的父类加载器是个null,也就是没有父类加载器。负责加载jvm的核心类库,比如 java.lang.* 等,从系统属性中的 sun.boot.class.path 所指定的目录中加载类库。他的具体实现由Java虚拟机底层C++代码实现。
扩展类加载器(Extension ClassLoader)
父类加载器是Bootstrap ClassLoader。从 java.ext.dirs 系统属性所指定的目录中加载类库, 或者从JDK的安装目录的 jre/lib/ext 子目录(扩展目录)下加载类库,如果把用户的jar文件放在这个目录下,也会自动由扩展类加载器加载。继承自 java.lang.ClassLoader 。
应用程序类加载器(Application ClassLoader)
父类加载器是Extension ClassLoader。从环境变量classpath或者系统属性 java.class.path 所指定的目录中加载类。继承自 java.lang.ClassLoader 。
自定义类加载器(User ClassLoader)
除了上面三个自带的以外,用户还能制定自己的类加载器,但是所有自定义的类加载器都应该继承自 java.lang.ClassLoader 。比如热部署、tomcat都会用到自定义类加载器。
4、双亲委派是什么?

参考回答:
- 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父 类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中, 只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class),子类加载器才会尝试自己去加载。
- 采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载 器最终得到的都是同样一个 Object 对象。
5、如何破坏双亲委派模型 (了解)
参考回答:
重写loadClass方法
背景:在JDK 1.2之前,Java就已经存在类加载器和ClassLoader抽象类,但双亲委派模型是在JDK 1.2中引入的。为了兼容旧代码,JDK 1.2中的ClassLoader类允许用户重写loadClass方法来实现自定义的类加载逻辑,这导致了双亲委派模型的第一次“被破坏”。
实现方式:通过继承ClassLoader抽象类并重写其loadClass方法,可以在类加载过程中自定义委派逻辑,从而实现打破双亲委派模型。但需要注意的是,JDK官方并不推荐这种方式,因为它可能会破坏Java程序的稳定性和安全性。
使用线程上下文类加载器(Thread Context ClassLoader)
背景:在某些情况下,顶层类加载器(如启动类加载器)加载的类可能需要调用由用户自定义类加载器加载的类。例如,JDBC驱动接口(java.sql.Driver)是由启动类加载器加载的,但其具体实现则是由数据库厂商提供的,并由应用类加载器加载。为了解决这种跨类加载器的调用问题,Java引入了线程上下文类加载器。
实现方式:通过java.lang.Thread类的setContextClassLoader()方法可以设置当前线程的上下文类加载器。当需要加载某些特定类时,可以使用线程上下文类加载器来加载,而不是遵循双亲委派模型。这种方式在Java SPI(服务提供接口)机制中得到了广泛应用。
其他框架和中间件的实现
在一些特定的框架和中间件中(如Tomcat、OSGi等),为了实现特定的功能(如热部署、模块化等),它们也采用了打破双亲委派模型的方式。这些框架和中间件通常会对类加载器进行扩展和定制,以满足其特殊需求。
6、JVM内存结构


参考回答:
程序计数器(线程私有)
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。
正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。
虚拟机栈(线程私有)

是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
本地方法区(线程私有)
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
堆(Heap-线程共享)-运行时数据区
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。
方法区/永久代(线程共享)
也称非堆,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。是被所有线程共享的,会发生OOM。
运行时常量
是方法区的一部分,存常量(比如static final修饰的,比如String 一个字符串)和符号引用。是被所有线程共享的,会发生OOM。
7、判断对象是否能被回收的算法
参考回答:
引用计数法
给对象添加一个引用计数器,每当有一个地方引用他的时候该计数器的值就+1,当引用失效的时候该计数器的值就-1;当计数器的值为0的时候,jvm判定此对象为垃圾对象。存在内存泄漏的bug,比如循环引用的时候,所以jvm虚拟机采取的是可达性分析法。
可达性分析法
有一些根节点GC Roots作为对象起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象为垃圾对象。
补充:哪些可作为GC Roots?
虚拟机栈中的引用的对象
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(native方法)引用的对象
8、JVM 运行时内存
参考回答:

Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。
其中新生代(Young) 被分为 Eden和S0(from)和S1(to)。 默认情况下Edem : from : to = 8 : 1 : 1 ,此比例可以通过 –XX:SurvivorRatio 来设定
新生代
是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
Eden 区:Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
ServivorFrom:上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
ServivorTo:保留了一次 MinorGC 过程中的幸存者。
老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。
元空间
元空间(Metaspace)是Java虚拟机(JVM)用于存储类元数据(如类的结构、方法信息等)的内存区域,从Java 8开始取代了永久代(PermGen)。与PermGen不同,元空间不再位于Java堆内存中,而是使用本地内存(Native Memory),这使得其大小不再受限于Java堆的大小,可以根据需要动态扩展。元空间支持类元数据的垃圾回收,当类被卸载时,其对应的元数据也会被回收,从而提高内存利用率。通过配置参数如
-XX:MetaspaceSize和-XX:MaxMetaspaceSize,可以调整元空间的初始大小和最大容量,以优化应用程序的内存管理。元空间的引入有效解决了PermGen区域容易因类加载过多而导致内存溢出的问题,提升了JVM的稳定性和灵活性。
9、MinorGC 的过程(复制->清空->互换,新生代)
参考回答:
- MinorGC 采用复制算法。
- *eden、servicorFrom 复制到 ServicorTo,年龄+1:*首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
- 清空 eden、servicorFrom :*然后,清空 Eden 和 ServicorFrom 中的对象;*
- *ServicorTo 和 ServicorFrom 互换:*最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。
10、为什么把堆栈分成两个?(了解)
参考回答:
- 栈代表了处理逻辑,堆代表了存储数据,分开后逻辑更清晰,面向对象模块化思想。栈是线程私有,堆是线程共享区,这样分开也节省了空间,比如多个栈中的地址指向同一块堆内存中的对象。
- 栈是运行时的需要,比如方法执行到结束,栈只能向上增长,因此会限制住栈存储内容的能力,而堆中的对象是可以根据需要动态增长的。
11、简述下对象的分配规则(了解)
参考回答:
- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次YGC。并将还活着的对象放到from/to区,若本次YGC后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在 Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次YGC那么对象会进入Survivor区,之后每经过一次YGC那么对象的年龄加1,直到达到阀值对象进入老年区。默认阈值是15。可以通过 -XX:MaxTenuringThreshold 参数来设置。
- 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一 半,年龄大于或等于该年龄的对象可以直接进入老年代。无需等到 -XX:MaxTenuringThreshold 参数要求的年龄。
- 空间分配担保。每次进行YGC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行YGC,如果false则进行Full GC。
12、CMS 收集器(多线程标记清除算法,老年代)
参考回答:

- CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下 4 个阶段:
- 初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
- 并发标记 :进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
- 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
- 并发清除:清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
所以CMS的优点是:
- 并发高
- 停顿低
- STW时间短。
缺点:
- 对cpu资源非常敏感(并发阶段虽然不会影响用户线程,但是会一起占用CPU资源,竞争激烈的话会导致程序变慢)。
- 无法处理浮动垃圾,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,失败后而导致另一次Full GC的产生,由于CMS并发清除阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾产生,这一部分垃圾是出现在标记过程之后的,CMS无法在本次去处理他们,所以只好留在下一次GC时候将其清理掉。
- 内存碎片问题(因为是标记清除算法)。当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
13、三色标记算法(了解)
这个算法基于图论中的图染色问题,通过给对象标记三种不同的颜色来表示其垃圾回收的状态。
这三种颜色是:
- 白色: 表示对象还没有被访问过,即未被扫描和标记。
- 灰色: 表示对象已经被扫描和标记,但其引用对象还没有被扫描。
- 黑色: 表示对象及其引用对象都已经被扫描和标记。
三色标记算法的基本思想是通过多次迭代的方式,通过不同的阶段标记不同颜色的对象,最终达到全局标记的目的。主要步骤:
- 初始标记阶段: 在这个阶段,只标记根对象为灰色,并且只扫描根对象的直接引用,标记它们为灰色。这个阶段是短暂的,通常需要停顿。
- 并发标记阶段: 在这个阶段,垃圾收集器与用户线程并发执行。从灰色对象开始,递归扫描其引用,标记为灰色,并将其引用的对象标记为灰色。这个阶段会持续直到灰色对象为空。
- 重新标记阶段: 在并发标记阶段结束后,可能有新的对象产生,因此需要重新标记一遍。在这个阶段,垃圾收集器会停顿,标记所有的存活对象,将灰色对象标记为黑色。
- 清理阶段: 在这个阶段,清理掉白色的对象,即那些没有被访问到的对象,将它们释放掉
14、G1垃圾回收器(了解)
G1(Garbage First)在JDK7中加入JVM,在JDK9中成为了默认的垃圾收集器,如果在JDK8中使用G1,我们可以使用参数 -XX:+UseG1GC 来开启。
在G1之前的垃圾回收器,如Parallel Scavenge、Parallel Old、CMS等,主要针对Java堆内存中的特定部分(新生代或老年代)进行操作。然而,G1将Java堆内存划分为多个大小相等的区域(Region),并根据每个区域中垃圾对象的数量和大小来优先进行垃圾回收;G1这种基于Region回收的方式,可以预测停顿时间。
G1会根据每个Region里面垃圾“价值”的大小,在后台维护一个优先级列表,每次根据用户设定的允许收集停顿的时间 (-XX:MaxGCPauseMilis,默认为200毫秒)优先处理价值收益最大的Region。
15、CMS垃圾回收器和G1垃圾回收器对比(了解)
因为CMS是基于标记-清除的算法实现的,所以CMS会有空间碎片化的问题。而在G1收集器上是不存在的,G1从整体上来看是基于标记-整理算法实现,从Region之间又是基于标记-复制算法实现的。
由于G1不会产生空间碎片,可以为对象的分配提供更规整的内存。此外还避免了由于分配大对象时找不到连续的内存空间,而不得不提前触发下一次垃圾回收。
由于跨Region引用等大量双向卡表的存在,G1收集器比CMS(只需要处理老年代到新生代的引用)占用更多的内存,
CMS收集器使用写后屏障来更新维护卡表,而G1收集器除了使用写后屏障维护卡表,为了实现SATB的算法,还需要使用写前屏障来跟踪并发时指针变化情况。所以G1收集器会增加程序运行时的额外负载。
16、大型项目如何进行性能瓶颈调优(了解)
参考回答:
- 数据库与SQL优化:一般dba负责数据库优化,比如集群主从等。研发负责SQL优化,比如索引、分库分表等。
- 集群优化:一般OP负责,让整个集群可以很容易的水平扩容,再比如tomcat/nginx的一些配置优化等。
- 硬件升级:选择最合适的硬件,充分利用资源。
- 代码优化:很多细节,可以参照阿里巴巴规范手册和安装sonar插件这种检测代码质量的工具。也可以适当的运用并行,比如CountDownLatch等工具。
- jvm优化:内存区域大小设置、对象年龄达到次数晋升老年代参数的调整、选择合适的垃圾收集器以及合适的垃圾收集器参数、打印详细的GC日志和oom的时候自动生成dump。
- 操作系统优化
17、系统内存飙高,如何查找问题?
参考回答:
- 找出哪个进程内存占用高(top命令)
- 查看jvm进程号(jps命令)
- 导出堆内存 (jmap命令生成dump文件,注意:线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿,所以操作前最好先从负载均衡里摘掉。)
- 分析dump文件 (比如mat软件)
18、常用的调优工具有哪些?(了解)
参考回答:
- JDK内置的命令行:jps(查看jvm进程信息)、jstat(监视jvm运行状态的,比如gc情况、jvm内存情况、类加载情况等)、jinfo(查看jvm参数的,也可动态调整)、jmap(生成dump文件的,在dump的时候会影响线上服务)、jhat(分析dump的,但是一般都将dump导出放到mat上分析)、jstack(查看线程的)。
- JDK内置的可视化界面:JConsole、VisualVM,这两个在QA环境压测的时候很有用。
- 阿里巴巴开源的arthas:神器,线上调优很方便,安装和显示效果都很友好。
19、GC常用参数(了解)
参考回答:
堆设置:
-Xms:设置堆的初始大小。
-Xmx:设置堆的最大大小。
栈设置:
-Xss:设置每个线程的栈大小。
垃圾回收器设置:
-XX:+UseG1GC:使用 G1 垃圾回收器。
-XX:+UseParallelGC:使用并行垃圾回收器。
性能调优:
-XX:PermSize 和 -XX:MaxPermSize:在 Java 8 之前设置永久代的初始大小和最大大小。
-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize:在 Java 8 及以上版本设置 Metaspace 的初始大小和最大大小。
-XX:+PrintGCDetails:打印垃圾回收的详细信息。
调试和分析:
-verbose:gc:输出垃圾回收的详细信息。
-XX:+HeapDumpOnOutOfMemoryError:在内存溢出时生成堆转储。
20、YoungGC和FullGC的触发条件是什么?(了解)
参考回答:
Young GC和Full GC的触发条件在Java虚拟机(JVM)中有一些差异。以下是它们的主要触发条件:
Young GC的触发条件:
时间间隔:可以通过
-XX:MaxGCPauseMillis设定的时间间隔来触发Young GC。堆大小:当Young GC的堆大小超过设定的目标大小时,Young GC会被触发。
频繁的Minor GC:当连续发生许多次Minor GC时,Full GC可能会被触发。
Eden区可用内存不足:Eden区是Young Generation的一部分,当Eden区的可用内存不足时,Young GC会被触发。
Full GC的触发条件:
老年代达到某一阈值:老年代(Old Generation)的堆内存使用率达到某一阈值(默认是92%)时,会触发Full GC。
方法区可用内存不足:方法区(PermGen space或Metaspace)的可用内存不足时,也会触发Full GC。在Java 8及之后的版本中,PermGen space已经被Metaspace所取代。
旧生代空间不足:当新生代对象转入及创建为大对象、大数组时,旧生代空间可能会出现不足的现象。如果执行Full GC后空间仍然不足,JVM会抛出
java.lang.OutOfMemoryError: Java heap space错误。Permanet Generation空间满:Permanet Generation中存放的是一些class的信息等。当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,从而触发Full GC。
21、Java中的类什么时候会被加载?(了解)
参考回答:
- 创建类的实例:当通过
new关键字创建一个类的实例时,该类会被加载。 - 访问类的静态变量、静态方法或静态初始化块:当程序首次主动使用某个类或接口的静态变量,或者对该静态变量进行赋值,或者调用类的静态方法时,Java虚拟机就会加载这个类到内存中。此外,类的静态初始化块也会在类加载的时候被执行。
- 通过反射加载类:使用Java的反射API(如
Class.forName())也可以触发类的加载。 - 初始化类的子类:在初始化一个类的子类之前,其父类必须先被加载。
- 使用类加载器显式加载:通过类加载器(ClassLoader)的
loadClass()方法,可以显式地加载指定的类。 - JVM启动时被标记为需要启动的类:在JVM启动时,会加载一些特殊的类,这些类在Java虚拟机的启动过程中就被标记为需要启动的类。
- Java的类加载器采用了双亲委派模型(Parents Delegation Model)。当一个类加载器需要加载一个类时,它首先会把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这种机制保证了Java核心类库的类型安全,防止了核心API被篡改。
22、为什么基本类型不放在堆里(了解)
参考回答:
- 因为基本类型占用的空间一般都是1-8个字节(所需空间很少),而且因为是基本类型,所以不会出现动态增长的情况(长度是固定的),所以存到栈上是比较合适的。反而存到可动态增长的堆上意义不大。
23、JVM调优(先了解,后面需要结合项目)
参考回答:
JVM的调优是为了优化应用程序的性能、资源利用率以及响应时间。调优的目标包括减小垃圾回收停顿时间、提高吞吐量、降低内存消耗等。实际中,要根据业务的实际情况去调整和优化。
- 使用JVM监控工具,如VisualVM、JConsole、Mission Control等,对应用程序进行性能分析。通过查看内存使用情况、垃圾回收统计、线程活动等信息,找到性能瓶颈和潜在问题。
- 启用GC日志并分析日志文件。GC日志包含了垃圾回收的详细信息,可以了解各种垃圾回收事件的发生时间、停顿时间、频率等。通过分析GC日志,可以确定是否需要调整垃圾回收器的类型、内存大小等参数。
- 在应用程序出现内存泄漏或者内存占用过高时,获取Heap Dump并分析。Heap Dump是一个内存快照,可以帮助你查找对象的引用链,找到内存泄漏的根本原因。
- 调整堆大小: 根据性能分析的结果,调整堆内存的大小。可以通过调整
-Xms和-Xmx参数来设置堆的初始大小和最大大小,以及通过-Xmn参数来设置新生代大小。 - 选择垃圾回收器: 根据应用程序的特性,选择合适的垃圾回收器。例如,对于响应时间敏感的应用,可以选择G1垃圾回收器。
- 调整垃圾回收器参数: 根据GC日志的分析,调整垃圾回收器的相关参数。例如,可以通过
-XX:MaxGCPauseMillis设置最大垃圾回收停顿时间,通过-XX:GCTimeRatio设置垃圾回收时间与应用执行时间的比率等。 - 代码优化: 通过代码审查和性能测试,对应用程序进行优化,避免不必要的资源消耗和垃圾回收压力。特别注意避免创建过多的临时对象。
