关于面试:2021Java后端工程师面试指南JVM

5次阅读

共计 6893 个字符,预计需要花费 18 分钟才能阅读完成。

前言

文本已收录至我的 GitHub 仓库,欢送 Star:https://github.com/bin3923282…
种一棵树最好的工夫是十年前,其次是当初

Tips

面试指南系列,很多状况下不会去深挖细节,是小六六以被面试者的角色去回顾常识的一种形式,所以我默认大部分的货色,作为面试官的你,必定是懂的。

https://www.processon.com/vie…

下面的是脑图地址

叨絮

可能大家感觉有点陈词滥调了,的确也是。面试题,面试宝典,轻易一搜,基本看不完,也看不过去,那我写这个的意义又何在呢?其实嘛我写这个的有以下的目标

  • 第一就是通过一个体系的温习,让本人后面的写的文章再从新的过一遍,总结升华嘛
  • 第二就是通过写文章帮忙大家建设一个温习体系,我会将大部分会问的的知识点以点带面的模式给大家做一个导论

而后上面是后面的文章汇总

  • 2021-Java 后端工程师面试指南 -(引言)
  • 2021-Java 后端工程师面试指南 -(Java 根底篇)
  • 2021-Java 后端工程师面试指南 -(并发 - 多线程)

JVM 作为一个 Java 工程师,必须要把握和了解的一个点

## 聊聊什么是 JVM

JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,引入 Java 语言虚拟机后,Java 语言在不同平台上运行时不须要从新编译。Java 语言应用 Java 虚拟机屏蔽了与具体平台相干的信息,使得 Java 语言编译程序只需生成在 Java 虚拟机上运行的指标代码(字节码),就能够在多种平台上不加批改地运行。

### 什么是类加载器,类加载器有哪些?
实现通过类的全限定名获取该类的二进制字节流的代码块叫做类加载器。

次要有一下四品种加载器:

  1. 启动类加载器 (Bootstrap ClassLoader) 用来加载 java 外围类库,无奈被 java 程序间接援用。
  2. 扩大类加载器(extensions class loader): 它用来加载 Java 的扩大库。Java 虚拟机的实现会提供一个扩大库目录。该类加载器在此目录外面查找并加载 Java 类。
  3. 零碎类加载器(system class loader):它依据 Java 利用的类门路(CLASSPATH)来加载 Java 类。一般来说,Java 利用的类都是由它来实现加载的。能够通过 ClassLoader.getSystemClassLoader()来获取它。
  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader 类的形式实现。

说说 JVM 类的生命周期和加载过程

类的生命周期就蕴含了加载过程了,咱们 JVM 类的生命周期有以下 7 个阶段

  • 加载:

    • 通过全类名获取定义此类的二进制字节流
    • 将字节流所代表的动态存储构造转换为办法区的运行时数据结构
    • 在内存中生成一个代表该类的 Class 对象, 作为办法区这些数据的拜访入口
  • 验证:验证文件格式,字节码验证,魔数验证等
  • 筹备 筹备阶段是正式为类变量分配内存并设置类变量初始值的阶段,如果是根本数据类型,就会给他们设置默认值
  • 解析:解析阶段是虚拟机将常量池内的符号援用替换为间接援用的过程
  • 初始化:首先明确一点的就是,必须存在以下的行为,才会进行类的初始化

    • 当 jvm 执行 new 指令时会初始化类。即当程序创立一个类的实例对象。
    • 当 jvm 执行 getstatic 指令时会初始化类。即程序拜访类的动态变量(不是动态常量,常量会被加载到运行时常量池)。
    • 当 jvm 执行 putstatic 指令时会初始化类。即程序给类的动态变量赋值。
    • 当 jvm 执行 invokestatic 指令时会初始化类。即程序调用类的静态方法。
    • 应用 java.lang.reflect 包的办法对类进行反射调用时如 Class.forname(“…”),newInstance()等等。,如果类没初始化,须要触发其初始化。
    • 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
    • 当虚拟机启动时,用户须要定义一个要执行的主类 (蕴含 main 办法的那个类),虚构机会先初始化这个类。
  • 应用:就是咱们失常应用了
  • 卸载:卸载类即该类的 Class 对象被 GC。卸载类须要满足 3 个要求:

    • 该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
    • 该类没有在其余任何中央被援用
    • 该类的类加载器的实例已被 GC

说说类加载器双亲委派模型机制?说说它的益处

每一个类都有一个对应它的类加载器。零碎中的 ClassLoder 在协同工作的时候会默认应用 双亲委派模型。即在类加载的时候,零碎会首先判断以后类是否被加载过。曾经被加载的类会间接返回,否则才会尝试加载。加载的时候,首先会把该申请委派该父类加载器的 loadClass() 解决,因而所有的申请最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无奈解决时,才由本人来解决。当父类加载器为 null 时,会应用启动类加载器 BootstrapClassLoader 作为父类加载器。

小六六总结一句话总结就是 类加载总是向上查看,向下加载。

双亲委派模型保障了 Java 程序的稳固运行,能够防止类的反复加载(JVM 辨别不同类的形式不仅仅依据类名,雷同的类文件被不同的类加载器加载产生的是两个不同的类),也保障了 Java 的外围 API 不被篡改。如果没有应用双亲委派模型,而是每个类加载器加载本人的话就会呈现一些问题,比方咱们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,零碎就会呈现多个不同的 Object 类。

说说为啥要突破双亲委派模型,如何突破?

JDBC 之所以要毁坏双亲委派模式是因为,JDBC 的外围在 rt.jar 中由启动类加载器加载,而其实现则在各厂商实现的的 jar 包中,依据类加载机制,若 A 类调用 B 类,则 B 类由 A 类的加载器加载,也就是说启动类加载器要加载 jar 包下的类,咱们都晓得这是不可能的,启动类加载器负责加载 $JAVA_HOME 中 jre/lib/rt.jar 里所有的 class,那么 JDBC 是如何加载这些 Driver 实现类的?
通过 Thread.currentThread().getContextClassLoader()失去线程上下文加载器来加载 Driver 实现类。

还有就是咱们能够自定义的加载器继承 ClassLoad 而后批改的 loadClass 和 find classde 办法,从而能够突破双亲的委派机制

聊聊 JVM 内存分哪几个区,每个区的作用是什么?

  • 办法区:

    1. 有时候也成为永恒代(元空间),在该区内很少产生垃圾回收,然而并不代表不产生 GC,在这里进行的 GC 次要是对办法区里的常量池和对类 型的卸载
    2. 办法区次要用来存储已被虚拟机加载的类的信息、常量、动态变量和即时编译器编译后的代码等数据。
    3. 该区域是被线程共享的。
    4. 办法区里有一个运行时常量池,用于寄存动态编译产生的字面量和符号援用。该常量池具备动态性,也就是说常量并不一定是编 译时确定,运行时生成的常量也会存在这个常量池中。
  • 虚拟机栈:

    1. 虚拟机栈也就是咱们平时所称的栈内存, 它为 java 办法服务,每个办法在执行的时候都会创立一个栈帧,用于存储局部变量表、操 作数栈、动静链接和办法进口等信息。
    2. 虚拟机栈是线程公有的,它的生命周期与线程雷同。
    3. 局部变量表里存储的是根本数据类型、returnAddress 类型(指向一条字节码指令的地址)和对象援用,这个对象援用有可能是指 向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的地位。局部变量所需的内存空间在编译器间确定
    4. 操作数栈的作用次要用来存储运算后果以及运算的操作数,它不同于局部变量表通过索引来拜访,而是压栈和出栈的形式
    5. 每个栈帧都蕴含一个指向运行时常量池中该栈帧所属办法的援用,持有这个援用是为了反对办法调用过程中的动静连贯. 动静链接就是将常量池中的符号援用在运行期转化为间接援用。
  • 本地办法栈

    • 本地办法栈和虚拟机栈相似,只不过本地办法栈为 Native 办法服务。
    • Java 堆是所有线程所共享的一块内存,在虚拟机启动时创立,简直所有的对象实例都在这里创立,因而该区域常常产生垃圾回收操作。
  • 程序计数器

    • 内存空间小,字节码解释器工作时通过扭转这个计数值能够选取下一条须要执行的字节码指令,分支、循环、跳转、异样解决和线程复原等性能都须要依赖这个计数器实现。该内存区域是惟一一个 java 虚拟机标准没有规定任何 OOM 状况的区域。

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

  • 虚拟机栈栈帧中援用的变量
  • 本地办法栈中援用的变量
  • 办法区中类动态属性援用的对象
  • 办法区中常量援用的对象

你晓得 Java 中垃圾收集的办法有哪些吗

  • 标记 - 革除算法:该算法分为“标记”和“革除”阶段:首先标记出所有不须要回收的对象,在标记实现后对立回收掉所有没有被标记的对象,然而会产生大量的空间碎片。
  • 复制算法:为了解决效率问题,“复制”收集算法呈现了。它能够将内存分为大小雷同的两块,每次应用其中的一块。当这一块的内存应用完后,就将还存活的对象复制到另一块去,而后再把应用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
  • 标记 - 整顿算法:依据老年代的特点提出的一种标记算法,标记过程依然与“标记 - 革除”算法一样,但后续步骤不是间接对可回收对象回收,而是让所有存活的对象向一端挪动,而后间接清理掉端边界以外的内存。
  • 分代收集算法:以后虚拟机的垃圾收集都采纳分代收集算法,这种算法没有什么新的思维,只是依据对象存活周期的不同将内存分为几块。个别将 java 堆分为新生代和老年代,这样咱们就能够依据各个年代的特点抉择适合的垃圾收集算法。

你有没有遇到过 OutOfMemory 问题?你是怎么来解决这个问题的?解决 过程中有哪些播种?

  • 汇合类中有对对象的援用,应用后未清空,GC 不能进行回收;
  • 代码中存在循环产生过多的反复对象;
  • 启动参数堆内存值小。
  • 频发的创立超大对象

JDK 1.8 之后 Perm Space 有哪些变动? MetaSpace ⼤⼩默认是⽆限的么? 还是你们会通过什么⽅式来指定⼤⼩

JDK 1.8 后用元空间代替了 Perm Space;字符串常量寄存到堆内存中。
MetaSpace 大小默认没有限度,个别依据零碎内存的大小。JVM 会动静扭转此值。
-XX:MetaspaceSize:调配给类元数据空间(以字节计)的初始大小(Oracle 逻辑存储上的初始高水位,the initial high-water-mark)。此值为估计值,MetaspaceSize 的值设置的过大会缩短垃圾回收工夫。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。-XX:MaxMetaspaceSize:调配给类元数据空间的最大值,超过此值就会触发 Full GC,此值默认没有限度,但应取决于零碎内存的大小。JVM 会动静地扭转此值。

说说产生 mior GC 的条件是什么

咱们晓得新生代个别分为三个区 eden s1 和 s2, 而咱们每次创立的对象在新生代外面,他只会调配在 eden 和其中一个 s,当他们都满了,就会产生一次 mior gc , 对象进入老年代。

说说进入老年代的条件吧

  • 第一个就是咱们下面说的 新生代满了 eden 和一个 s 满了,对象通过 mior gc 之后进入老年代
  • 大对象间接进入老年代:如果老年代残余的间断内存空间大于之前 Minor GC 降职老年代对象的均匀大小的话,就进行 Minor GC,如果小于的话就间接进行 Full GC。对于 parNew 是 XX:PretenureSizeThreshold 设置大对象大小
  • 长期存活的对象将进入老年代 XX:MaxTenuringThreshold cms 默认是 6 次,
  • Hotspot 遍历所有对象时,依照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的降职年龄阈值,也就是说当一个 s 的一半以上的对象都是这个,那么他们就会进入老年代了。

说说 jdk1.8 默认的垃圾回收器,你们的线上环境用的是哪个垃圾回收器呢?

1.8 默认的是 UseParallelGC,ParallelGC 默认的是 Parallel Scavenge(新生代)+ Parallel Old(老年代)

小六六本人负责的我的项目是我配置的,用的 parNew+CMS;

为啥要用 CMS 呢?是这样的,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器。它十分合乎在重视用户体验的利用上应用。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

那你说说 CMS 收集的过程

  • 初始标记:暂停所有的其余线程,并记录下间接与 root 相连的对象,速度很快;
  • 并发标记:同时开启 GC 和用户线程,用一个闭包构造去记录可达对象。但在这个阶段完结,这个闭包构造并不能保障蕴含以后所有的可达对象。因为用户线程可能会一直的更新援用域,所以 GC 线程无奈保障可达性剖析的实时性。所以这个算法里会跟踪记录这些产生援用更新的中央。
  • 从新标记:从新标记阶段就是为了修改并发标记期间因为用户程序持续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的进展工夫个别会比初始标记阶段的工夫稍长,远远比并发标记阶段工夫短
  • 并发革除:开启用户线程,同时 GC 线程开始对未标记的区域做打扫。

他只有第一个 和第三个阶段须要 stw

它的毛病:它应用的回收算法 -“标记 - 革除”算法会导致收集完结时会有大量空间碎片产生。

其实我倡议呢?如果本人的内存够大还是用 G1 吧,只有达到 8G 的内存,咱们倡议应用 G1,而且 jdk9 开始曾经没有 cms 了

那你聊聊 G1 吧

G1 (Garbage-First) 是一款面向服务器的垃圾收集器, 次要针对装备多颗处理器及大容量内存的机器. 以极高概率满足 GC 进展工夫要求的同时, 还具备高吞吐量性能特色.

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件劣势,应用多个 CPU(CPU 或者 CPU 外围)来缩短 Stop-The-World 进展工夫。局部其余收集器本来须要进展 Java 线程执行的 GC 动作,G1 收集器依然能够通过并发的形式让 java 程序继续执行。
  • 分代收集:尽管 G1 能够不须要其余收集器配合就能独立治理整个 GC 堆,然而还是保留了分代的概念。
  • 空间整合:与 CMS 的“标记 – 清理”算法不同,G1 从整体来看是基于“标记整顿”算法实现的收集器;从部分上来看是基于“复制”算法实现的。
  • 可预测的进展:这是 G1 绝对于 CMS 的另一个大劣势,升高进展工夫是 G1 和 CMS 独特的关注点,但 G1 除了谋求低进展外,还能建设可预测的进展工夫模型,能让使用者明确指定在一个长度为 M 毫秒的工夫片段内。

G1 收集器在后盾保护了一个优先列表,每次依据容许的收集工夫,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)。这种应用 Region 划分内存空间以及有优先级的区域回收形式,保障了 G1 收集器在无限工夫内能够尽可能高的收集效率(把内存化整为零)。

其实,咱们只有管制了 GC 的每次回收工夫,对于用户来说感知就不会那么大了。

那你说说 G1 和 cms+parNew 的益处体现在哪?

其实呢 G1 和 cms 很大水平上很类似,怎么说,就是当咱们回收垃圾的时候,都是要经验 初始标记,并发标记,从新标记,这些阶段。然而有几个区别

  • 第一个就是假如咱们这个利用很多,咱们把新生代的内存设置的很大,对不对,这样垃圾回收的工夫就会很小,然而如果刚好,有一个用户刚好申请的时候,它刚好再 gc,那你想想这个 GC 的工夫会不会很长,那对于用户来说是不是体验差呢?所以说大内存其实并不是那么适宜 cms
  • G1 就不同,他并不需要说肯定要等到,达到内存的多少才开始回收垃圾,他能够设置咱们垃圾回收的工夫,来判断什么时候来回收,这样对于大部分用户来说,相当于均匀了 gc 的时候,那么体验上会好很多。

说说你个别用来排查问题的工具呗

jps jmap jstat MAT arthas 等,用的比拟多,还有就是最初打印出咱们的 gc 日志,通过 gc 日志去剖析 gc 问题

### 总结一下你的 JVM 调优的一些心得
这个是小六六本人的一些见解,不肯定对哈,其实大部分咱们去剖析 gc 日志,而后去调优并不一定说要去批改 JVM 的参数,很多时候是咱们本人代码的问题,所以咱们要把本人的代码先去排查,如果代码没有问题了,那么就是 JVM 的参数,咱们有以下准则

  • 第一个就是让那些应该在新生代被回收的对象,尽量不要进入到老年代,让他们再新生代被回收
  • 让那些长期存活的对象,尽快的进入到老年代
  • 如果内存够大,尽量应用 G1
  • 写代码的应用 如果你应用完一个对象,最好把那个对象的援用置空

完结

JVM 写完了,可能也不全副,然而呢,这些问题你相熟的话基本上问题不大了,下一章就 MySql 吧

日常求赞

好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是 真粉

创作不易,各位的反对和认可,就是我创作的最大能源,咱们下篇文章见

微信 搜 “ 六脉神剑的程序人生 ” 回复 888 有我找的许多的材料送给大家

正文完
 0