面试题:虚拟机篇

目录

一、JVM 内存结构

1.概览

2.会发生内存溢出的区域

3.方法区、永久代、元空间

二、JVM 内存参数

三、JVM 垃圾回收 

1.三种垃圾回收算法  

2.GC 与分代回收算法

3.分代回收与 GC 规模 

4.三色标记与并发漏标问题 

5.垃圾回收器

四、内存溢出(项目中什么情况下会内存溢出,怎么解决的项目中什么情况下会内存溢出,怎么解决的)

五、类加载

1.类加载过程的三个阶段

2.双亲委派机制

 六、四种引用(对象引用类型分为哪几类?)

1.强引用

2.软引用(SoftReference)

3.弱引用(WeakReference)

4.虚引用(PhantomReference)

 七、finalize

1.finalize的理解

2.finalize 原理

3.finalize 缺点 


一、JVM 内存结构

1.概览

        线程私有

                ①程序计数器

                ②虚拟机栈

        线程共享

                ①

                ②方法区

  • 执行 javac 命令编译源代码为字节码

  • 执行 java 命令

    1. 创建 JVM,调用类加载子系统加载 class,将类的信息存入方法区

    2. 创建 main 线程,使用的内存区域是 JVM 虚拟机栈,开始执行 main 方法代码

    3. 如果遇到了未见过的类,会继续触发类加载过程,同样会存入方法区

    4. 需要创建对象,会使用内存来存储对象

    5. 不再使用的对象,会由垃圾回收器在内存不足时回收其内存

    6. 调用方法时,方法内的局部变量、方法参数所使用的是 JVM 虚拟机栈中的栈帧内存

    7. 调用方法时,先要到方法区获得到该方法的字节码指令,由解释器将字节码指令解释为机器码执行

    8. 调用方法时,会将要执行的指令行号读到程序计数器,这样当发生了线程切换,恢复时就可以从中断的位置继续

    9. 对于非 java 实现的方法调用,使用内存称为本地方法栈(见说明)

    10. 对于热点方法调用,或者频繁的循环代码,由 JIT 即时编译器将这些代码编译成机器码缓存,提高执行性能

说明

  • 加粗字体代表了 JVM 虚拟机组件

  • 对于 Oracle 的 Hotspot 虚拟机实现,不区分虚拟机栈和本地方法栈

2.会发生内存溢出的区域

  • 不会出现内存溢出的区域 – 程序计数器

  • 出现 OutOfMemoryError 的情况

    • 堆内存耗尽 – 对象越来越多,又一直在使用,不能被垃圾回收

    • 方法区内存耗尽 – 加载的类越来越多,很多框架都会在运行期间动态产生新的类

    • 虚拟机栈累积 – 每个线程最多会占用 1 M 内存,线程个数越来越多,而又长时间运行不销毁时

  • 出现 StackOverflowError 的区域

    • JVM 虚拟机栈,原因有方法递归调用未正确结束、反序列化 json 时循环引用

3.方法区、永久代、元空间

  • 方法区是 JVM 规范中定义的一块内存区域,用来存储类元数据、方法字节码、即时编译器需要的信息等

  • 永久代是 Hotspot 虚拟机对 JVM 规范的实现(1.8 之前)

  • 元空间是 Hotspot 虚拟机对 JVM 规范的另一种实现(1.8 以后),使用本地内存作为这些信息的存储空间

从这张图学到三点

  • 当第一次用到某个类时,由类加载器将 class 文件的类元信息读入,并存储于元空间

  • X,Y 的类元信息是存储于元空间中,无法直接访问

  • 可以用 X.class,Y.class 间接访问类元信息,它们俩属于 java 对象,我们的代码中可以使用

从这张图可以学到

  • 堆内存中:当一个类加载器对象,这个类加载器对象加载的所有类对象,这些类对象对应的所有实例对象都没人引用时,GC 时就会对它们占用的对内存进行释放

  • 元空间中:内存释放以类加载器为单位,当堆中类加载器内存释放时,对应的元空间中的类元信息也会释放

二、JVM 内存参数

 对于JVM内存配置参数:-Xmx10240m -Xms10240m -Xmn5120m -XX:SurvivorRatio=3 其最小内存值和Survivor区总大小分别是

堆内存,按大小设置

解释:

  • -Xms 最小堆内存(包括新生代和老年代)

  • -Xmx 最大对内存(包括新生代和老年代)

  • 通常建议将 -Xms 与 -Xmx 设置为大小相等,即不需要保留内存,不需要从小到大增长,这样性能较好

  • -XX:NewSize 与 -XX:MaxNewSize 设置新生代的最小与最大值,但一般不建议设置,由 JVM 自己控制

  • -Xmn 设置新生代大小,相当于同时设置了 -XX:NewSize 与 -XX:MaxNewSize 并且取值相等

  • 保留是指,一开始不会占用那么多内存,随着使用内存越来越多,会逐步使用这部分保留内存。下同

 堆内存,按比例设置

解释:

  • -XX:NewRatio=2:1 表示老年代占两份,新生代占一份

  • -XX:SurvivorRatio=4:1 表示新生代分成六份,伊甸园占四份,from 和 to 各占一份

元空间内存设置 

解释:

* class space 存储类的基本信息,最大值受 -XX:CompressedClassSpaceSize 控制
* non-class space 存储除类的基本信息以外的其它信息(如方法字节码、注解等)
* class space 和 non-class space 总大小受 -XX:MaxMetaspaceSize 控制

注意:

* 这里 -XX:CompressedClassSpaceSize 这段空间还与是否开启了指针压缩有关,这里暂不深入展开,可以简单认为指针压缩默认开启

代码缓存内存设置

解释:

  • 如果 -XX:ReservedCodeCacheSize < 240m,所有优化机器代码不加区分存在一起

  • 否则,分成三个区域(图中笔误 mthod 拼写错误,少一个 e)

    • non-nmethods - JVM 自己用的代码

    • profiled nmethods - 部分优化的机器码

    • non-profiled nmethods - 完全优化的机器码

线程内存设置

三、JVM 垃圾回收 

1.三种垃圾回收算法  

① 标记清除法

解释:

1. 找到 GC Root 对象,即那些一定不会被回收的对象,如正执行方法内局部变量引用的对象、静态变量引用的对象
2. 标记阶段:沿着 GC Root 对象的引用链找,直接或间接引用到的对象加上标记
3. 清除阶段:释放未加标记的对象占用的内存

要点:

  • 标记速度与存活对象线性关系

  • 清除速度与内存大小线性关系

  • 缺点是会产生内存碎片

② 标记整理法

解释:

1. 前面的标记阶段、清理阶段与标记清除法类似
2. 多了一步整理的动作,将存活对象向一端移动,可以避免内存碎片产生

特点:

  • 标记速度与存活对象线性关系

  • 清除与整理速度与内存大小成线性关系

  • 缺点是性能上较慢

③ 标记复制法

 

解释:

1. 将整个内存分成两个大小相等的区域,from 和 to,其中 to 总是处于空闲,from 存储新创建的对象
2. 标记阶段与前面的算法类似
3. 在找出存活对象后,会将它们从 from 复制到 to 区域,复制的过程中自然完成了碎片整理
4. 复制完成后,交换 from 和 to 的位置即可

特点:

  • 标记与复制速度与存活对象成线性关系

  • 缺点是会占用成倍的空间

2.GC 与分代回收算法

  • GC 的目的在于实现无用对象内存自动释放,减少内存碎片、加快分配速度
  • GC 要点
    • 回收区域是堆内存,不包括虚拟机栈,在方法调用结束会自动释放方法占用内存
    • 判断无用对象,使用可达性分析算法三色标记法标记存活对象,回收未标记对象
    • GC 具体的实现称为垃圾回收器
    • GC 大都采用了分代回收思想,理论依据是大部分对象朝生夕灭,用完立刻就可以回收,另有少部分对象会长时间存活,每次很难回收,根据这两类对象的特性将回收区域分为新生代老年代,不同区域应用不同的回收策略
    • 根据 GC 的规模可以分成 Minor GCMixed GCFull GC

3.分代回收与 GC 规模 

  • 分代回收
    • 伊甸园 eden,最初对象都分配到这里,与幸存区合称新生代
    • 幸存区 survivor,当伊甸园内存不足,回收后的幸存对象到这里,分成 from to,采用标记复制算法
    • 老年代 old,当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)
  • GC 规模
    • Minor GC 发生在新生代的垃圾回收,暂停时间短
    • Mixed GC 新生代 + 老年代部分区域的垃圾回收,G1 收集器特有
    • Full GC 新生代 + 老年代完整垃圾回收,暂停时间长,应尽力避免

 分代回收过程演示 

1.伊甸园 eden,最初对象都分配到这里,与幸存区 survivor(分成 from 和 to)合称新生代

2.当伊甸园内存不足,标记伊甸园与 from(现阶段没有)的存活对象 

  3.将存活对象采用复制算法复制到 to 中,复制完毕后,伊甸园和 from 内存都得到释放

4. 将 from 和 to 交换位置

5. 经过一段时间后伊甸园的内存又出现不足

 6.标记伊甸园与 from(现阶段没有)的存活对象

 7.将存活对象采用复制算法复制到 to 中

 8.复制完毕后,伊甸园和 from 内存都得到释放

 9.将 from 和 to 交换位置

 10.老年代 old,当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)

4.三色标记与并发漏标问题 

        1.用三种颜色记录对象的标记状态

                ①黑色 已标记

                ②灰色 标记中

                ③白色 还未标记

        2.漏标问题 记录标记过程中变化

                ①Incremental Update

                        ①只要赋值发生,被赋值的对象就会被记录

                ②Snapshot At The BeginningSATB

                        ①新加对象会被记录

                        ②被删除引用关系的对象也被记录

 

5.垃圾回收器

Parallel GC
eden 内存不足发生 Minor GC ,标记复制 STW
old 内存不足发生 Full GC ,标记整理 STW
注重吞吐量
ConcurrentMarkSweep GC
old 并发标记 ,重新标记时需要 STW 并发清除
Failback Full GC
注重响应时间
G1 GC
响应时间与吞吐量兼顾
划分成多个区域,每个区域都可以充当 eden survivor old humongous
新生代回收: eden 内存不足,标记复制 STW
并发标记: old 并发标记 ,重新标记时需要 STW
混合收集:并发标记完成,开始 混合收集 ,参与复制的有 eden survivor old ,其中 old 会根据 暂停时间目标 ,选择部分回收价值高的区域,复制时 STW
Failback Full GC

 

四、内存溢出(项目中什么情况下会内存溢出,怎么解决的项目中什么情况下会内存溢出,怎么解决的)

  • 误用线程池导致的内存溢出
  • 查询数据量太大导致的内存溢出
  • 动态生成类导致的内存溢出

五、类加载

1.类加载过程的三个阶段

  1. 加载

    1. 将类的字节码载入方法区,并创建类.class 对象

    2. 如果此类的父类没有加载,先加载父类

    3. 加载是懒惰执行

  2. 链接

    1. 验证 – 验证类是否符合 Class 规范,合法性、安全性检查

    2. 准备 – 为 static 变量分配空间,设置默认值

    3. 解析 – 将常量池的符号引用解析为直接引用

  3. 初始化

    1. 静态代码块、static 修饰的变量赋值、static final 修饰的引用类型变量赋值,会被合并成一个 <cinit> 方法,在初始化时被调用

    2. static final 修饰的基本类型变量赋值,在链接阶段就已完成

    3. 初始化是懒惰执行

名称

加载哪的类

说明

Bootstrap ClassLoader

JAVA_HOME/jre/lib

无法直接访问

Extension ClassLoader

JAVA_HOME/jre/lib/ext

上级为 Bootstrap,显示为 null

Application ClassLoader

classpath

上级为 Extension

自定义类加载器

自定义

上级为 Application

2.双亲委派机制

所谓的双亲委派,就是指优先委派上级类加载器进行加载,如果上级类加载器

* 能找到这个类,由上级加载,加载后该类也对下级加载器可见
* 找不到这个类,则下级类加载器才有资格执行加载

双亲委派的目的有两点

1. 让上级类加载器中的类对下级共享(反之不行),即能让你的类能依赖到 jdk 提供的核心类

2. 让类的加载有优先次序,保证核心类优先加载

 六、四种引用(对象引用类型分为哪几类?)

1.强引用

        ①普通变量赋值即为强引用,如 A a = new A();

        ②通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收

​​​​​​​

2.软引用(SoftReference)

        ①例如:SoftReference a = new SoftReference(new A()); 

        ②如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象

        ③软引用自身需要配合引用队列来释放

        ④典型例子是反射数据​​​​​​​

3.弱引用(WeakReference)

        ①例如:WeakReference a = new WeakReference(new A());

        ②如果仅有引用引用该对象时,只要发生垃圾回收,就会释放该对象

        ③弱引用自身需要配合引用队列来释放

        ④典型例子是 ThreadLocalMap 中的 Entry 对象

4.虚引用(PhantomReference)

        ①例如: PhantomReference a = new PhantomReference(new A());

        ②必须配合引用队列一起使用,当虚引用引用的对象被回收时,会将虚引用对象入队,由 Reference Handler 线程释放其关联的外部资源

        ③典型例子是 Cleaner 释放 DirectByteBuffer 占用的直接内存

 七、finalize

1.finalize的理解

  •  一般的回答是:它是 Object 中的一个方法,子类重写它,垃圾回收时此方法会被调用,可以在其中进行一些资源释放和清理工作
  • 较为优秀的回答是:将资源释放和清理放在 finalize 方法中非常不好,非常影响性能,严重时甚至会引起 OOM,从 Java9 开始就被标注为 @Deprecated,不建议被使用了

2.finalize 原理

  • 对 finalize 方法进行处理的核心逻辑位于 java.lang.ref.Finalizer 类中,它包含了名为 unfinalized 的静态变量(双向链表结构),Finalizer 也可被视为另一种引用对象(地位与软、弱、虚相当,只是不对外,无法直接使用)
  • 当重写了 finalize 方法的对象,在构造方法调用之时,JVM 都会将其包装成一个 Finalizer 对象,并加入 unfinalized 链表中

 

 

  • Finalizer 类中还有另一个重要的静态变量,即 ReferenceQueue 引用队列,刚开始它是空的。当狗对象可以被当作垃圾回收时,就会把这些狗对象对应的 Finalizer 对象加入此引用队列
  • 但此时 Dog 对象还没法被立刻回收,因为 unfinalized -> Finalizer 这一引用链还在引用它嘛,为的是【先别着急回收啊,等我调完 finalize 方法,再回收】
  • FinalizerThread 线程会从 ReferenceQueue 中逐一取出每个 Finalizer 对象,把它们从链表断开并真正调用 finallize 方法

 

 

  • 由于整个 Finalizer 对象已经从 unfinalized 链表中断开,这样没谁能引用到它和狗对象,所以下次 gc 时就被回收了

3.finalize 缺点 

  • 无法保证资源释放:FinalizerThread 是守护线程,代码很有可能没来得及执行完,线程就结束了

  • 无法判断是否发生错误:执行 finalize 方法时,会吞掉任意异常(Throwable)

  • 内存释放不及时:重写了 finalize 方法的对象在第一次被 gc 时,并不能及时释放它占用的内存,因为要等着 FinalizerThread 调用完 finalize,把它从 unfinalized 队列移除后,第二次 gc 时才能真正释放内存

  • 有的文章提到【Finalizer 线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的CPU时间较少,因此它永远也赶不上主线程的步伐】这个显然是错误的,FinalizerThread 的优先级较普通线程更高,原因应该是 finalize 串行执行慢等原因综合导致

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值