JVM常考面试题

1.JVM调优的6大步骤

  1. 监控GC的状态
  2. 生成堆的dump文件
  3. 分析dump文件
  4. 分析结果,判断是否需要优化
  5. 调整GC类型和内存分配
  6. 不断地分析和调整

2.什么是垃圾?

内存里大部分的对象都是随着方法的执行而创建,方法执行完毕后这些对象就不会被再次使用了,但是这些生成的对象不会被清除掉,内存里面的对象会越来越多,这时我们就需要一种机制把这种不会被再次使用的对象清除掉,而这种不会被再次使用的对象我们就称之为垃圾。

3.如何判断对象已经死亡,需要回收?

一、引用计数法
程序给对象添加一个引用计数器,每有一个变量引用它时,计数器加1。当引用断开时,计数器减1。当计数器为0时,代表着没有任何变量引用它,该对象就是死亡状态,JVM需要对此类对象进行回收。

引用计数法的实现简单,效率也很高。但计数算法无法回收相互循环引用的对象,此类对象确实已经不再被使用,但由于互相引用着对方,导致各自的计数器都不为0,因此JVM无法回收它们。
二、可达性分析法
程序创建一系列的GC Roots作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象与GC Roots没有任何引用链相连的话,即此对象到GC Roots不可达,则证明此对象是不可用的,JVM稍后将会对此类对象进行回收。

大多数主流的JVM都采用这样的算法来管理内存,它能够解决对象间循环引用的问题。对象与对象之间虽然有循环引用,当他们到GC Roots没有任何引用链,系统还是判定它们为可回收对象。

4.哪些对象我们称之为"GC Roots"对象呢

作为GC Roots 对象那么它自身肯定得满足一个条件,那就是他自己一定在很长一段时间内都不会被GC 回收掉。那么只有满足这个条件的对象才可能作为GC Roots了,大致如下:

  • 1 、 方法区中的静态变量和常量引用的对象。
  • 2、 本地方法栈中JNI(即一般说的native方法)引用的对象。
  • 3、 虚拟机栈(栈帧中的本地变量表)中引用的对象。

我的理解是 上面是从内存分区和虚拟机真实工作流程角度来说的。即虚拟机寻找 gc root 时会去这三个地方找:虚拟机栈,本地方法区,方法区

从程序的角度来说就是,找到一段程序运行的整个过程中始终会存活的对象,这些对象的特点是始终会存活,不会死亡。即一些静态变量 和常量所引用的对象等。

5.四大GC算法

标记清除法
标记清除法分为“标记”和“清除”阶段,标记阶段标记对象是否存活,清除阶段把已死的对象统一进行清除。
缺点:

  • 1、会造成不连续的内存空间:这样的空间碎片太多不利于下次分配,而且当有大对象创建的时候,我们明明有可以容纳的总空间,但是空间都不是连续的造成对象无法分配,从而不得不提前触发GC。

  • 2、性能不稳定:当内存中大量对象都是需要回收的时候,标记和清除过程都会比较耗时,性能不稳定。

复制算法
它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将存活的对象复制到另一块去,然后再把使用的空间一次清理掉。标记复制法可以说完美解决了空间空间碎片问题,但是标记复制法也存在着不足的地方。
缺点:

  • 1、会浪费一部分内存

  • 2、存活对象多会非常耗时。

标记-整理算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让存活的对象向一端移动,然后直接清理掉端边界以外的内存。

标记整理法的问题

缺点:
整理过程比较耗时
因为移动对象需要对其引用的地址进行变更,所以这个过程需要停止所有“用户线程”(也就是 stop the word ),从而造成应用程序的停顿,如果存活的对象越多那么迁移对象的过程时间就可能越长。
分代收集算法

一般将 java 堆分为新生代和老年代,根据各个年代的特点选择合适的垃圾收集算法。

新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
老年代的对象存活几率是比较高的,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

6.JVM中类加载机制,类加载过程,什么是双亲委派模型? 类加载器有哪些?

Class 文件需要加载到虚拟机中之后才能运行和使用,系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
首先是加载阶段(Loading),主要完成下面3件事情:

  1. 通过全类名获取定义此类的二进制字节流
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。
第二阶段是链接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转化入 JVM 运行的过程中。这里可进一步细分为三个步骤:

  • 验证(Verification),这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是VerifyError,这样就防止了恶意信息或者不合规的信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。
  • 准备(Preparation),创建类或接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显式初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的JVM 指令。
  • 解析(Resolution),在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。在Java虚拟机规范中,详细介绍了类、接口、方法和字段等各个方面的解析。

最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。
双亲委派模型:
当类加载器(Class-Loader)试图加载某个类时,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型。
类加载器有哪些?
1.启动类加载器:加载 jre/lib 下面的 jar 文件
2.扩展类加载器,负责加载我们放到 jre/lib/ext/ 目录下面的 jar 包,
3.应用类加载器(Application Class-Loader),就是加载我们最熟悉的 classpath 的内容。
双亲委派模型的好处
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。
如果我们不想用双亲委派模型怎么办?
自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法

7.JVM四种引用

强引用:强引用就是我们平常用的类似于“Object obj = new Object()”的引用,只要obj的生命周期没结束,或者没有显示地把obj指向为null,那么JVM永远不会回收这种对象。

软引用: 软引用相对强引用来说就要脆弱一点,JVM正常运行时,软引用和强引用没什么区别,但是当内存不够用,濒临溢出时,JVM的垃圾收集器就会把软引用的对象回收。在JDK中提供了SoftReference类来实现软引用

弱引用:弱引用比软引用更加脆弱,弱引用的对象将会在下一次的gc被回收,不管JVM内存被占用多还是少。在JDK中使用WeakReference来实现弱引用

虚引用: 虚引用是最脆弱的引用,随时都有可能被回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。在JDK中提供了PhantomReference来实现虚引用,虚引用一般和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。
在这里插入图片描述
四大引用链接

8.jvm内存泄漏问题分析过程

jvm内存泄漏问题分析过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值