https://www.zhihu.com/search?q=jvm%E9%9D%A2%E8%AF%95%E9%A2%98&utm_content=search_history&type=content

jvm的加载过程

1、加载;2、校验;3、准备;4、解析;5、初始化。

简述 java 类加载机制?

什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
1、启动类加载器(Bootstrap ClassLoader)
2、扩展类加载器(extensions class loader)
3、系统\应用类加载器(system class loader)
4、用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式,重写classLoad()方法实现。

jvm的双亲委派机制,哪些场景是打破双亲委派模式

image.png
双亲委派机制的优点:
1、避免类的重复加载
2、保护程序安全,防止核心API被随意篡改
https://blog.csdn.net/weixin_43884884/article/details/107719529(哪些场景)
打破双亲委派机制的场景有很多:JDBC、JNDI、Tomcat等,我们以Tomcat为例来说明

Tomcat为什么要打破双亲委派机制

Tomcat是一个web容器,可能要部署两个或多个应用程序,不同的应用程序之间可能会依赖同一个第三方类库的不同版本,因此要保证每个应用程序的类库都是独立的、相互隔离的

Tomcat打破双亲委派

Java项目在打war包的时候,tomcat自动生成的类加载器
每一个项目打成war包,tomcat都会自动生成一个类加载器,专门用来加载这个war包,而这个类加载器打破了双亲委派机制,我们可以想象一下,加入这个webapp类没有打破双亲委派机制会怎么样?
如果没有打破,它就会委托父类加载器去加载,一旦加载到了,紫烈加载器就没有机会加载了,那么Spring4和Spring5的项目就没有可能共存了。所以,这一部分它打破了双亲委派机制,这样一来webapp类加载器就不需要在让上级类去加载,它自己就可以加载对应的war里的class文件,当然了,其它的项目文件还是要委托上级加载的。

如何打破双亲委派机制?双亲委派机制是在那里实现的呢?

双亲委派机制是在ClassLoader类的loadClass()中实现的,如果我们不想使用系统自带的双亲委派模型,只需要重新实现ClassLoader的loadClass()方法即可,下面是ClassLoader中定义的loadClass()方法,里面实现了双亲委派机制

OOM

https://blog.csdn.net/qq_36194388/article/details/118926960

  • 堆溢出
  • 虚拟机栈和本地方法栈溢出
  • 方法区和运行时常量池溢出
  • 本机直接内存溢出 ```java 一旦面试官问及 JVM 或者 JVM 调优方面的问题时,你可以对面试官说:“对于这个问题,我能否从 Java 虚拟机结构的角度开始讲起?这样我能更加透彻地回答您的问题。”当得到允许后,你可以通过如下的步骤,系统地展示这方面的能力。 1、围绕 JVM 处理字节码的流程,边画边讲虚拟机体系结构,着重突出堆区的作用。 2、讲述堆区分代的结构,在此基础上讲述垃圾回收的流程。 3、讲一下你在代码中提升内存性能的做法。 4、“发现、分析和解决”OOM 问题的说辞,展示你内存调优方面的经验。

```

JVM 调优的过程

原文链接:https://blog.csdn.net/m0_55070913/article/details/123759804
第1步:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
第2步:确定JVM调优量化目标;
第3步:确定JVM调优参数(根据历史JVM参数来调整);
第4步:调优一台服务器,对比观察调优前后的差异;
第5步:不断的分析和调整,直到找到合适的JVM参数配置;
第6步:找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

JVM 运行时数据区

JVM 有哪些垃圾回收算法,优缺点

标记清除法:
第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
第二步:在遍历一遍,将所有标记的对象回收掉;
特点:效率不行,标记和清除的效率都不高;标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC;
标记整理法:
第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
第二步:将所有的存活的对象向一段移动,将端边界以外的对象都回收掉;
特点:适用于存活对象多,垃圾少的情况;需要整理的过程,无空间碎片产生;
复制算法:
将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除;
特点:不会产生空间碎片;内存使用率极低;
分代收集算法:
根据内存对象的存活周期不同,将内存划分成几块,java虚拟机一般将内存分成新生代和老生代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收;

JVM 有哪些垃圾回收器

Serial收集器:年轻代和老年代版本,单线程,gc时会造成SWT;
Parallel收集器:年轻代和老年代版本,多线程处理gc,但gc时仍会造成SWT;(线程数与CPU核数相当)。
java8默认收集器
优点:多线程,吞吐量高,效率高
缺点:2-3G内存的时候,效率还OK的,但是4G乃至更大内存,SWT时间比较长,用户体验不太好;
image.png
ParNew收集器+CMS收集器
ParNew收集器跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。新生代采用复制算法
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。实现了让垃圾收集线程与用户线程(基本上)同时工作。如果不分成五个步骤,实现并发效果,则SWT时间会很长,影响用户效果

CMS 垃圾回收器整个过程

初始标记: 暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快
并发标记 占用80%时间;并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。如何改变:之前的对象不是垃圾,但应用的持续运行,导致变成了垃圾;或者之前的对象是垃圾,但是应用的持续运行,导致不在是垃圾;
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法(见下面详解)做重新标记。
并发清理: 开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑
色不做任何处理(见下面三色标记算法详解)。
并发重置:重置本次GC过程中的标记数据。
image.png
主要优点:并发收集、低停顿
缺点:1,对CPU资源敏感(会和服务抢资源);
2,无法处理浮动垃圾(在并发清理阶段,清理完了User对象以后用户线程执行又产生垃圾User对象,这种浮动垃圾只能等到下一次gc再清理了);
3,它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数-
XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理.
4,执行过程中的不确定性,由于用户线程的存在,在并发标记的过程中,有大对象产生,将被放入老年代,但是老年代放不下,导致垃圾回收又被触发,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是”concurrentmode failure”,此时会进入stop the world,用serial old垃圾收集器来回收。而serial old收集器是单线程的,SWT时间会比较长,用户体验很差。

CMS的相关核心参数

  1. -XX:+UseConcMarkSweepGC:启用cms
    2. -XX:ConcGCThreads:并发的GC线程数
    3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
    4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一

    5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
    为啥不是百分百:防止在并发标记的时候,有大对象产生,导致并发失败,用serial old收集器来回收
    6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设
    定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
    7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引
    用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
    8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
    9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
    image.png
    配置年代次数:比如25秒进行一次young gc,那其实设置为5次会比较合适,5次以后还会存在的对象,我们就把他放入老年代,减少young gc的次数。还有大对象的设置:一般设置为1M比较好

    三色标记算法

    黑色:表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描
    过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。
    灰色:表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
    白色:表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若
    在分析结束的阶段, 仍然是白色的对象, 即代表不可达。
    image.png
    多标-浮动垃圾
    漏标-读写屏障
    漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(IncrementalUpdate) 和原始快照(Snapshot At The Beginning,SATB) 。
    增量更新:就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。
    原始快照:就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)。
    以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的

    写屏障

    对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:
    CMS:写屏障 + 增量更新
    G1,Shenandoah:写屏障 + SATB
    ZGC:读屏障

    G1收集器

    G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量(8G-几百G)内存的机器. 以极高概率满足GC 停顿时间要求的同时,还具备高吞吐量性能特征.

    G1收集器一次GC的运作过程

    初始标记(initial mark,STW):暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快
    并发标记(Concurrent Marking):同CMS的并发标记
    最终标记(Remark,STW):同CMS的重新标记
    筛选回收(Cleanup,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序根据用户所期 望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis指定,默认200毫秒,zgc能达到<=10毫秒)来制定回收计划,比如说老年代此时有1000个 Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Region刚好需要200ms,那么就只会回收800Region(Collection Set,要回收的集 合),尽量把GC导致的停顿时间控制在我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。不管是年轻代或是老 年代,回收算法主要用的是复制算法将一个region中的存活对象复制到另一个region中,这种不会像CMS那样 回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。(注意:CMS回收阶段是跟用户线程一起并发执行的,G1因为内部实现太复杂暂时没实现并发回收,不过到了Shenandoah就实现了并发收集,Shenandoah可以看成是G1的升级版本)
    JVM面试题汇总 - 图6

G1收集器维护了一个优先列表

G1垃圾收集分类

YoungGC、MixedGC、Full GC

G1收集器参数设置

-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合 收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值,存活对象过多,回收的的意义不大。
-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一 会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。

G1垃圾收集器优化建议

核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑 每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc.

什么场景适合使用G1

为什么G1用SATB?CMS用增量更新?

我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

如何选择垃圾收集器

image.png

每秒几十万并发的系统如何优化JVM

什么时候用Parallel,什么时候用ParNew+CMS,什么时候用G1

4G一下内存Parallel,4-8G用ParNew+CMS,8G以上用G1
内存不太高的时候用G1的话,可能效果还比用ParNew+CMS差一些,虽然G1停顿时间可控,但内部有很多细节算法,那就代表了算法的复杂度;如果内存比较大的话,用G1就比较合适了,内存越大,停顿时间的优势就体现出来了

简述分代垃圾回收器是怎么工作的

如和判断一个对象是否存活?(或者 GC 对象的判定方法)

JVM中一次完整的GC是什么样子的?

对象如何晋升到老年代?

在java中可以作为GC Roots的对象有以下几种

怎么判断对象是否可以被回收?

SWT机制

简述java垃圾回收机制

  1. GC 的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
  2. GC 收集器有哪些? CMS 收集器与 G1 收集器的特点。
  3. JVM 调优的工具
  4. 常用的 JVM 调优的参数都有哪些