cyc的JVM以及其他JVM常问面试题

其他JVM总结

1、类加载过程

一.Java 类加载过程?
Java 类加载需要经历一下 7 个过程:

  • 1.加载
    加载是类加载的第一个过程,在这个阶段,将完成一下三件事情:
    通过一个类的全限定名获取该类的二进制流。
    将该二进制流中的静态存储结构转化为方法去运行时数据结构。
    在内存中生成该类的 Class 对象,作为该类的数据访问入口。

  • 2.验证
    验证的目的是为了确保 Class 文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:
    文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
    元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
    字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
    符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

  • 3.准备
    准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。
    public static int value=123;//在准备阶段 value 初始值为 0 。在初始化阶段才会变为 123 。

  • 4.解析
    该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

  • 5.初始化
    初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由
    虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的
    Java 程序代码。

  • 6.使用

  • 7.卸载

1、对象的访问定位的两种方式?(句柄和直接指针)

建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种:

  • 句柄: 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
    在这里插入图片描述
  • 直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。

在这里插入图片描述

2、谈谈对内存泄漏的理解?举几个内存泄漏的案例?

内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。内存泄漏简单粗俗的理解就是该被释放的对象没有被释放,一直被某个或者某些实例所持有却不再被使用导致GC不能回收。
内存泄漏会导致内存空间的浪费,大量的内存泄漏会导致程序内存溢出(Out Of Memory)。

比如创建了大量的static对象,由于静态变量的生命周期和应用程序一致,所以程序关闭的时候集合变量占用的空间才会被释放,它们所引用的对象也一直被引用着,无法被GC回收。

3、被GC判断为“垃圾”的对象一定会被回收吗?

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们 暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:

(1)如果对象在进行可达性分析后,发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。(即意味着直接回收)

(2)如果这个对象被判定为有必要执行finalize()方法,那么 这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。 这里所谓的“执行”是指虚拟机会触发这个方,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。

finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己 —— 只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合; 如果对象这时候还没有逃脱,那基本上它就真的被回收了。

4、用的垃圾收集算法有哪些?各自的优缺点是什么?

  • 1、Mark-Sweep(标记-清除)算法  
    这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。
    标记-清除算法分为两个阶段:
    标记阶段和清除阶段:
    a. 标记阶段的任务是标记出所有需要被回收的对象
    b. 清除阶段就是回收被标记的对象所占用的空间。
    优点:标记-清除算法实现起来比较容易
    缺点:但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

  • 2、Copying(复制)算法(不会产生内存碎片) 
    为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
    优点:这种算法虽然实现简单,运行高效且不容易产生内存碎片。
    缺点:但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。

  • 3、Mark-Compact(标记-整理)算法
    为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

  • 4.Generational Collection(分代收集)算法
    分代收集算法是目前大部分JVM的垃圾收集器采用的算法。
    它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。
    一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation)
    老年代的特点是每次垃圾收集时只有少量对象需要被回收
    新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

5、为什么要采用分代收集算法?

它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。
一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation)
老年代的特点是每次垃圾收集时只有少量对象需要被回收
新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

6、对象如何进入老年代

  • 1、大对象直接进入老年代

  • 2、新生代对象年龄到一定程度后进入老年代
    每个对象会有一个Age的计数器,初始值为0,每经过一次GC并且存活,这个对象的Age就会加1,如果增加到一定程度(默认为15)。那么就会进入老年代中。

  • 3、动态对象年龄判定
    如果在新生代存活区中相同年龄所有对象大小的总和大于存活区的一半,年龄大于或等于该年龄的对象就会直接进入老年代。
    比如现在存活区有三个对象,Age分别为2、2、3。那么Age为3的这个对象就会进入老年代。

7、谈谈对Java引用的理解(4种)?

  • 强引用: 指被强引用关联的对象不会被垃圾回收器回收。使用 new 一个新对象的方式来创建强引用。
  • 软引用: 被软引用关联的对象只有在内存不够的情况下才会被回收,允许系统对可调整的缓存进行收缩。使用 SoftReference 类来创建软引用。用途: 软引用可用来实现内存敏感的高速缓存,在实际业务中的一个应用是浏览器的后退按钮,如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,又需要重新构建,所以这里可以采用软引用来将这些对象列入回收范围,如果没有足够的内存才对它进行回收。
  • 弱引用:弱引用的强度比软引用更弱一些,被弱引用关联的对象一定会被回收,它只能存活到下一次垃圾回收发生之前,可以用来规范化表或者其他场景。使用 WeakReference 类来创建弱引用。
  • 虚引用: 是最弱的一种引用。一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用获得一个对象。为对象设置虚引用的唯一目的是能在这个对象被收集器回收时收到一个系统通知。回收器不会将任何虚引用置空,这一操作必须由开发者显式操作。它允许开发者控制回收的顺序和时间。使用 PhantomReference 来创建虚引用。

8、什么是浮动垃圾?

并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理。这些垃圾有个专业名词:浮动垃圾。
重新标记(Remark) 的作用在于:
之前在并发标记时,因为是 GC 和用户程序是并发执行的,可能导致一部分已经标记为 从 GC Roots 不可达 的对象,因为用户程序的(并发)运行,又可达 了,Remark 的作用就是将这部分对象又标记为 可达对象。

9、常用的垃圾收集器有哪些?

新生代:

  • Serial 收集器
  • ParNew 收集器(收集器是 Serial 收集器的多线程版本)
  • Parallel Scavenge 收集器(吞吐量优先收集器)

老年代:

  • Serial Old 收集器
  • Parallel Old 收集器啊(吞吐量优先收集器的老年代版本)
  • CMS (以获取最短回收停顿时间为目标的垃圾收集器)
    它非常重视服务的响应速度,以期给用户最好的体验。。

G1收集器

10、CMS(低停顿)

从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  • 初始标记 需要“Stop the world”,仅仅只是标记一下GC Roots 能直接关联到的对象,速度很快。

  • 并发标记 并发追溯标记,程序不会停顿。

  • 重新标记 需要“Stop the world”修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

  • 并发清除 清理垃圾对象,程序不会停顿

CMS一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:

  • 对 CPU 资源敏感;
  • 无法处理浮动垃圾;
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间 碎片产生。

11、G1(分代、标记整理、可预测的停顿时间、充分利用cpu(缩短STW时间))

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,开发人员希望在未来可以换掉CMS收集器,它有如下特点

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
  • 空间整合:与 CMS 的“标记–清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。这就意味着不会产生大量的内存碎片
  • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

G1 收集器的运作大致分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

G1收集器将整个Java堆内存划分为若干个内存大小相等的Region,年轻代和老年代不再物理隔离,他们都是一部分Region的集合。

12、JVM内存分配的原则?

JVM分配内存机制有三大原则
具体如下所示:

  • 优先分配到eden区
  • 大对象,直接进入到老年代
  • 长期存活的对象分配到老年代

13、空间担保分配原则(把Minor GC时,Survivor无法容纳的对象放到老年代)

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,

  • 如果大于,则此次Minor GC是安全的

  • 如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
    如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

上面提到了Minor GC依然会有风险,是因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。

取平均值仍然是一种概率性的事件,如果某次Minor GC后存活对象陡增,远高于平均值的话,必然导致担保失败,如果出现了分配担保失败,就只能在失败后重新发起一次Full GC。虽然存在发生这种情况的概率,但大部分时候都是能够成功分配担保的,这样就避免了过于频繁执行Full GC。

14、设置最小堆和最大堆大小

-Xmx –Xms:指定最大堆和最小堆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值