JVM面试总结

文章目录

        • 栈帧中存放的信息:
        • 对象的创建过程
        • 对象的内存布局?
        • 对象的访问定位方式?
        • 如何判断对象已死?
        • 可以作为GC Root的点:
        • 谈一下引用
        • 对象再被回收时如何逃脱?
        • 回收方法区
        • 如何判断常量是否废弃?
        • 垃圾回收算法及其优缺点
        • Full GC的触发条件
        • Serial垃圾收集器:
        • ParNew收集器:
        • Parallel Scavenge收集器:
        • Serial Old收集器:
        • Parallerl Old收集器:
        • CMS收集器:
        • 增量式并发收集器(CMS的变种):
        • G1垃圾回收器:
        • 低延迟垃圾收集器:
        • Shenandoah 收集器:
        • 内存分配的规则:
        • Class文件结构:
        • 类加载机制:
        • 双亲委派模型:
        • 破环双亲委派模型:
        • 运行时栈帧结构

栈帧中存放的信息:

存储局部变量 表、操作数栈、动态连接、方法出口等信息

方法区存储着已被Java虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

JDK7把永久代的字符串常量池、静态变量等移出

描述为堆逻辑的一部分

对象的创建过程

  1. 判断对象对应的类是否加载、链接、初始化

    没有将执行类加载过程

  2. 为对象分配内存

    • 指针碰撞(内存规整)

      所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着 一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动 一段与对象大小相等的距离

    • 空闲列表(内存不规整)

      虚拟机就必须维护一个列表,记录上哪些内存块是可 用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的 记录

  3. 处理并发安全问题

    • 对分配内存空间的动作进行同步处理

      CAS加失败重试保证操作的原子性

    • 把分配的内存的动作按照线程划分在不同的空间中

      即每个 线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)

  4. 初始化分配到的空间

  5. 设置对象的对象头

    对对象进行必要的设置:哪个类的实例、 如何才能找到类的元数据信息、对象的哈希码(实际上对象的哈希码会延后到真正调用 Object::hashCode()方法时才计算)、对象的 GC 分代年龄等信息。

  6. 执行init方法进行初始化

对象的内存布局?

  1. 对象头

    • 存储对象自身的运行时数据(Mark Word)

      哈希码

      GC分代年龄

      锁状态标识

      线程持有的锁

      偏向线程ID

      偏向时间戳

    • 类型指针

      对象指向它的类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的实例。

  2. 实例数据

    是对象真正存储的有效信息

    即我们在程序代码里面所定义的 各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录 起来。

  3. 对齐填充

    不是必然存在的,也没什么特别的含义,仅仅起到占位符的作用

对象的访问定位方式?

  1. 句柄访问

    reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自 具体的地址信息

JVM面试总结_第1张图片

好处:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改

  1. 直接指针(HotSpot采用)

JVM面试总结_第2张图片

直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据

好处:速度更快,节省了一次指针的开销

如何判断对象已死?

  1. 引用计数算法

    优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

    缺点:(1)(空间开销)他需要单独的字段存储计数器,这样的做法增加了存储空间的开销。

    ​ (2)(时间开销)每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。

    ​ (3)(循环引用)引用计数器还有一个严重的问题,即无法处理循环引用的问题,这是一条致命的缺陷,导致在Java回收的垃圾回收器中没有使用这类算法。

  2. 可达性分析算法

    优点:解决了不能循环引用的问题

    缺点:多线程情况下会造成误报或者漏报

可以作为GC Root的点:

  • 虚拟机栈中(栈帧中的本地变量表)中引用的对象
  • 在方法区中类静态属性引用的对象
  • 在方法区中常量引用的对象
  • 本地方法中JNI引用的对象
  • 虚拟机内部的引用
  • 所有同步锁持有的对象
  • 反映Java虚拟机内部情况的JMXBean、JVMTI 中注册的回调、本地代码缓存等。

谈一下引用

  1. 强引用

    是指在程序代码之中普遍存在的引用赋值,即类 似“Object obj=new Object()”这种引用关系

  2. 软引用

    用来描述一些还有用,但非必须的对象

  3. 弱引用

    用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引 用关联的对象只能生存到下一次垃圾收集发生为止。

  4. 虚引用

    为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时 收到一个系统通知

  5. 终结器引用

对象再被回收时如何逃脱?

至少经过俩次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,它将会被第一次标记,随后进行筛选,筛选的条件是此对象是否有必要执行finalize()方法,假如没有覆盖或者已经被虚拟机调用过,那么会被视为“没有必要执行”

如果有必要执行finalize()方法,会放置在一个F-Queue队列之中,并在稍后由一条虚拟机自动建立的、低调度优先级的优先级的Finalizer线程去执行finalize()方法。

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

回收方法区

方法区主要回收:废弃的常量和不再使用的类型。

如何判断常量是否废弃?

三个条件:

  • 该类的所有实例都已被回收
  • 加载该类的类加载器已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类

垃圾回收算法及其优缺点

标记-清除算法

标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对 象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

缺点:

  • 执行效率不稳定
  • 内存空间碎片化

标记复制算法(“半区复制”)

它将可用内存按容量划分为大小相等的两块,每次只使用 其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后 再把已使用过的内存空间一次清理掉。

优点:

  • 实现简单
  • 运行高效

缺点:

  • (浪费空间,内存使用率低)产生大量的内存开销内存缩小为原来的一半

标记-整理算法

优点:解决了内存碎片化的问题

缺点:开销大

GC Roots主要在全局性的引用(常量或类静态属性)与执行上下文(栈帧的本地变量表)中

分代收集算法

  • 把一个内存分成多个区域,不同的区域使用不同的回收算法去回收。

增量算法

  • 每次只收集一小片区域内存空间的垃圾,这样就可以减少系统的停顿。

Full GC的触发条件

  • 老年代空间不足(第一是真的不足,第二是内存碎片化)
  • 元空间不足
  • 在某一次新生代回收之后要晋升到老年代对象所占用空间大于老年代的剩余空间
  • 显示调用了System.gc();

Serial垃圾收集器:

特点:串行垃圾收集器

优点:单线程收集效率高

​ 对于内存资源受限的环境,额外内存消耗最小

​ 对于单核处理器或处理器核心数较少的来说,没有线程交互的开销,单线程能做到最高效率

缺点:停顿时间长

ParNew收集器:

特点:Serial的并行版本

Parallel Scavenge收集器:

特点:为达到一个可控制的吞吐量(吞吐量优先收集器)

优点:有较好的吞吐量

缺点:

Serial Old收集器:

Serial的老年代版本

Parallerl Old收集器:

Parallel Scavenge 收集器的老年代版本

CMS收集器:

基于标记清除算法实现的

特点:获取最短的停顿时间给用户较好的体验,并发低停顿收集器

无法处理浮动垃圾,可能导致失败导致STW的Full GC

初始标记–并发标记–重新标记–并发清除

缺点:对处理器资源比较敏感,并发阶段会导致应用程序变慢,总吞吐量降低

收集结束会产生大量的空间碎片

增量式并发收集器(CMS的变种):

垃圾收集的过程会更长,减少了对用户程序的影响(已废弃)

G1垃圾回收器:

优点:不会产生空间碎片,可以精确地控制停顿

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

内存占用方面:G1的记忆集所占的内存更多

执行负载角度:G1垃圾回收实现的异步处理

低延迟垃圾收集器:

Shenandoah 收集器:

九个阶段

内存分配的规则:

  • 对象优先在 Eden 分配
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
  • 动态对象年龄判定:如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的 一半,年龄大于或等于该年龄的对象就可以直接进入老年代
  • 空间分配担保

Class文件结构:

魔数与Class文件的版本

常量池

访问标识

类索引、父类索引与接口索引集合

字段表集合

类加载机制:

(加载过程)生命周期:

  • 加载:

    • 通过一个类的全限定名来获取定义此类的二进制字节流。
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种 数据的访问入口。
  • 验证

    • 文件格式验证:字节流是否符合Class文件格式规范

    • 元数据验证:对字节码进行语义分析,保证符合java语言规范

    • 字节码验证:通过数据流分析和控制 流分析,确定程序语义是合法的、符合逻辑的。

    • 符号引用验证:校验行为发生在虚拟机将符号引用转化为直接引用的时候,是对类自身 以外(常量池中的各种符号引用)的各类信息进行匹配性校验

      通俗来说就是,该类是 否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。

  • 准备:正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存 并设置类变量初始值

  • 解析: Java 虚拟机将常量池内的符号引用替换为直接引用

    • 符号引用:符号引用以一组符号来描述所引用的目标,符 号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
    • 直接引用:直接引用是可以直接指向目标的指针、相对偏移量 或者是一个能间接定位到目标的句柄
  • 初始化:根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源

  • 使用

  • 卸载

双亲委派模型:

JVM面试总结_第3张图片

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会 自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加 载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有 当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时, 子加载器才会尝试自己去完成加载

破环双亲委派模型:

  • 在JDK1.2以前,JDK 1.2 之后的 java.lang.ClassLoader 中添加一个新的 protected 方法 findClass(), 并引导用户编写的类加载逻辑时尽可能去重写这个方法

​ 按照 loadClass()方法的逻辑,如果父类加载失败,会自动调用 自己的 findClass()方法来完成加 载

​ 这样既不影响用户按照自己的意愿去加载类,又可以保证新写 出来的类加载器是符 合双亲委派规则的。

  • 引入了上下文类加载器

    这个类加载器可以通过 java.lang.Thread 类的 setContext-ClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继 承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应 用程序类加载器。

  • 代码热替换、模块热部署

运行时栈帧结构

  • 局部变量表:是一组变量值的存储空间,存放方法参数和方法内部定义的局部变量

    以变量槽为最小单位

  • 操作数栈

  • 动态连接

  • 方法返回地址

  • 附加信息

你可能感兴趣的:(Java,Java基础,jvm,面试,java)