JVM面试 (讲出新意!面试官一定喜欢!)

一、运行时内存区域

1.方法区/元空间(线程共享)

储存: 类型数据(类,接口,枚举,注解的数据),运行时常量池,JIT(即时编译器)的代码缓存.
JDK8前后的区别:
JDK8之前叫方法区或永久代,使用虚拟机内存存储.常量池和静态变量在方法区.
JDK8及以后叫元空间,改为使用本地内存存储.常量池和静态变量挪到了堆里.
元空间扩展详解-1 元空间扩展详解-2

2.堆(线程共享)

储存: 大部分的对象实例都存放在堆里,还有一部分在栈上分配.

2.1 栈上分配 逃逸分析 标量替换

基于逃逸分析,如果确定对象不会逃逸到方法外,就分配到栈帧上(HotSpot虚拟机使用标量替换,将这个对象打散成多个字段来分配),然后随着栈帧弹栈而GC,提升GC效率.

2.2 新生代 老年代 跨代引用 记忆集 TLAB PLAB

内存回收的角度来看: 新生代(又细分为伊甸区,幸存1区,幸存2区),老年代(跨代引用区和非跨代引用区,避免老年代全区扫描)
在分代回收理论基础上,还有一个跨代引用的问题,比如新生代的对象引用老年代的对象,为了避免扫描老年代全区,分为跨代引用区,非跨代用区,在新生代建立一个全局变量 记录对老年代不同区的指向,来避免老年代全区扫描,那个全局变量就叫记忆集.
内存分配的角度来看: 线程本地分配缓存(TLAB),晋升线程本地分配缓存(PLAB) .
线程本地分配缓存:位于新生代的伊甸区,提前分配不同的区域,减少多线程的竞争,提高内存分配的效率.
晋升本地分配缓存:和TLAB类似,是在晋升到幸存区或老年代的线程本地分配缓存.
TLAB与PLAB简解篇 TLAB深入篇

3.虚拟机栈(线程私有)

储存: 线程运行时所需要的数据.每个线程在创建时都会创建一个虚拟机栈.
细分区域:栈内多个栈帧(每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧)
HotSpot将本地方法栈和虚拟机栈合二为一了.
虚拟机栈详解 栈帧详解

4.本地方法栈(线程私有)

储存: 线程调用本地方法(native)所需要的数据.每个线程在创建时都会创建一个本地方法栈.
在HotSpot虚拟机中直接把虚拟机栈和本地方法栈二合为一了.

5.程序计数器(线程私有)

储存:线程当前执行字节码的行号,为了程序跳转、线程切换恢复到正确的执行位置.每个线程都有一个独立的程序计数器.
一块很小的内存区域,也是虚拟机中唯一不会发生OOM的区域.
JVM的程序计数器 操作系统的程序计数器-1 操作系统的程序计数器-2

6.堆外内存/直接内存

零拷贝: 待写
浅拷贝: 目标对象和新对象共用一个内存指针,修改数据时相互影响.速度快,开销小.
深拷贝: 目标对象和新对象不共用一个内存指针,相互独立,互不影响.速度慢,开销大.
延时拷贝: 拷贝时使用浅拷贝快速创建并维护一个计数器(记录几个对象共享数据),当程序修改数据时再根据计数器的数量决定是否深拷贝.注意在某些情况下,循环引用会导致一些问题.速度适中,开销适中.

二.垃圾回收

1.垃圾回收算法

1.1 标记清除算法

简述: 最基础的回收算法,标记然后清除释放内存就行.
特征: 不挪动存活对象的内存地址,执行效率不稳定,可用内存不是连续的,内存碎片化.
示意图

1.2 标记复制算法

简述: 内存分成AB区,A区使用B区备用,将A区活着的对象复制到B区上,清理A区内存,循环使用.
特征: 内存使用率降低,可用内存是连续的.
示意图

1.3 标记整理算法

简述:先标记清理垃圾释放内存,然后让所有存活对象向内存的一端移动.
特征:内存使用率高,可用内存是连续的.挪动已存活对象的效率比标记复制法要低.
示意图

2. 标记法详细

2.1 引用计数算法 内存泄漏

定义:保存对象的被引用次数,标记回收计数器为0的对象.
问题:对象间循环引用导致内存泄漏.主流的虚拟机都不用这个算法.

2.2 可达性分析算法

定义: 从多个根节点(GC-Roots)根据引用关系往下搜索,不可达的对象标记可回收.不存在循环引用的问题
固定可作为GC-Roots节点的对象:

  1. 栈帧(含本地方法栈)中引用的对象,比如方法中的参数对象、局部变量对象、临时变量对象等.
  2. 类静态属性引用的对象,比如类中引用类型的静态变量(类变量).
  3. 常量引用的对象,比如字符串常量池里的引用.
  4. 虚拟机内部常驻对象的引用,比如系统类加载器,常驻异常对象,基本数据类型的包装类对象.
  5. 所有被同步锁(synchronized)所持有的对象.

可达性分析算法-简单易懂

2.3 强引用 软引用 弱引用 虚引用

上述垃圾回收算法都是建立在引用关系上:
强引用:对象不死,即使OOM,也不回收. 例:Object obj=new Object();
软引用:快OOM了,即使对象没死,也要回收. 跟SoftReference类关联实现.
弱引用:只要GC就会回收. 跟weakReference类关联实现. ThreadLocalMap的key就是弱引用.
虚引用:可以在回收之前增加处理逻辑. 跟PhantomReference和ReferenceQueue两个类关联实现.
案例演示虚引用的回收时机
案例演示虚引用的使用场景-直接内存(堆外内存)

2.4 StopTheWorld 安全点 安全区域

StopTheWorld: 可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析,这意味着必须全程冻结用户线程的运行,对于用户来讲就是StopTheWorld.
安全点: 用户线程并不是随意就能被中断的,允许被中断的点叫安全点.
安全区域:用户线程处于sleep或blocked状态无法走到安全点,便划定安全区域,本次GC不回收.

2.5 内存保护陷阱 一致性快照

2.5.1 内存保护陷阱

用户线程如何响应GC事件,一种是抢断式响应,强制停止用户线程,几乎不采用这种方式.另外一种是主动式响应,用户线程主动去轮询是否GC的标志.
定义: 主动式响应,用户线程主动去轮询是否GC的标志.HotSpot将轮询操作精简到只有一条汇编指令,在想要GC时将这块内存区域设置成不可读,用户线程会得到自陷异常信号,在异常处理器中挂起线程,响应GC事件.

2.5.2 一致性快照 增量更新 原始快照 三色标记法

可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析,这意味着必须全程冻结用户线程的运行。
为了减少StopTheWorld的时间,提升GC效率,各个垃圾回收器都致力于GC线程和用户线程并发执行.
但是比如在可达性分析期间,在遍历多个GC-Roots节点引用链组成的对象图期间,对象图某节点发生了新增对象或删除了引用关系,这个算法就出问题了,所以引入增量更新原始快照,过后再扫描改动的节点关系. 这块用三色标记法比较好理解.

3. ZGC-前置知识

上述都是基于G1的分代回收思想,ZGC比G1性能更优秀.

3.1 特性

1.STP时间不超过1毫秒,停顿时间不会随着堆的大小,或者活跃对象的大小而增加.
2.最大支持 16TB 的大堆,最小支持 8MB 的小堆.
3.跟 G1 相比,对应用程序吞吐量的影响小于15%.

3.2 内存多重映射

ZGC参照操作系统中的虚拟地址和物理地址,设计了一套内存和地址的多重映射关系,实现了两级内存管理:虚拟内存和物理内存,把同一块儿物理内存映射为 Marked0、Marked1 和 Remapped 三个虚拟内存。
当应用程序创建对象时,会在堆上申请一个虚拟地址,这时 ZGC 会为这个对象在 Marked0、Marked1 和 Remapped 这三个视图空间分别申请一个虚拟地址,这三个虚拟地址映射到同一个物理地址。
Marked0、Marked1 和 Remapped 这三个视图空间在同一个时间点内只有一个有效。ZGC 就是通过这三个视图空间的切换,来完成并发的垃圾回收。
ZGC内存多重映射

3.3 染色指针

之前的垃圾回收器都把GC信息(标记信息,GC分代年龄等)都放在对象头里,GC时需要访问对象,
ZGC开创性的把GC信息放在指针上,不用访问对象,大大提高了GC效率.
ZGC-染色指针

3.4 内存布局,取消分代管理

G1将堆分成大小相同的Region,在此基础上进行分代管理,
ZGC也是基于region管理,但是没有分代, 并且支持动态创建和销毁,同时分为三种大小:

  1. Small Region:2MB,主要用于放置小于 256 KB 的小对象。
  2. Medium Region:32MB,主要用于放置大于等于 256 KB 小于 4 MB 的对象。
  3. Large Region:N * 2MB。这个类型的 Region 是可以动态变化的,不过必须是 2MB 的整数倍,最小支持 4 MB。每个 Large Region 只放置一个大对象,并且是不会被重分配的。

3.5 读屏障

定义:读屏障是JVM向应用代码插入一小段代码的技术,类似于AOP前置增强.
注意:

  1. 不同于java内存模型中的读屏障,这里的技术是从字节码或编译层面完成.
  2. 只有从堆内存中读取对象的引用时,才会增加读屏障.
    ZGC-读屏障
    作用:ZGC的GC线程绝大部分时间是和用户线程并行的,所以需要在读取对象引用前判断当前视图,用以保证数据的准确性.
    读屏障会影响程序性能.据测试,最多百分之4的性能损耗.但这是ZGC并发转移的基础,为了降低STW,设计者认为这点牺牲是可接受的.

3.6 内存回收算法

如果堆内还有可用空间,那就使用标记复制算法.
如果堆内没有可用空间,那就使用标记整理算法,将活跃对象向区域的一端进行就地搬移.

4. ZGC-GC过程

ZGC的垃圾收集过程分为三个阶段: 标记 转移 重定位.
ZGC初始化后,整个内存空间的地址视图被设置为Remapped.

4.1 标记阶段 可达性分析算法 引用链分析

标记结束后,如果对象地址视图是 Marked0,那就是活跃的,如果对象地址视图是 Remapped,那就是不活跃的。

① 初始标记 (类似于生成原始快照)

STW,耗时很短.找出GC-Roots节点对象,放入活跃对象表,活跃对象的地址视图都是M0.
当标记阶段结束后,ZGC会把所有活跃对象的地址存到对象活跃信息表,。
ZGC-活跃信息表

② 并发标记 (包含增量更新)

1.GC线程把引用链上可达的对象都调整为M0视图.
2.用户线程创建新对象或访问已有对象的的视图都调整为M0视图.(增量更新由用户线程切换视图)
ZGC-并发标记阶段视图

③ 再标记 (对并发标记期发生的改变进行再标记)

STW,耗时很短.在并发标记过程中,可能会有引用关系发生变化而导致的漏标记问题。对这部分进行再标记.

4.2 转移阶段

转移就是把活跃对象(M0)复制到新的内存,视图切换为Remapped,之前的内存空间可以被回收。
如果堆内还有可用空间,那就使用标记复制算法.
如果堆内没有可用空间,那就使用标记整理算法,将活跃对象向区域的一端进行就地搬移.

① 初始转移

STW,耗时很短.找出GC-Roots节点对象进行转移,视图切换为Remapped.

② 并发转移

1.GC线程把引用链上可达的视图是M0的对象进行转移,并把视图切换为Remapped.
2.用户线程新创建的对象设为Remapped,访问位于活跃信息表的对象(GC-Root节点对象),进行转移,并把视图切换为Remapped.
ZGC-转移阶段视图

4.3 重定位

把所有指向对象旧地址的指针调整到对象的新地址上。

实际上重定位动作在标记阶段中执行,在标记的时候如果发现指针还是引用老的地址则会修正成新的地址,然后再进行标记。
值得注意的是:第一次GC发生时,并不会发生重定位动作,因为已经标记完了,这个时候只会记录一下原本的对象被转移到哪儿去了。只有当第二次GC发生时,开始标记的时候发现某个对象被转移了,但引用还是老的,此时才会发生重定位操作,即修改成新的引用地址。

三. JVM调优

1.综合性的图形化工具JConsole,连接java进程,掌握内存的使用情况.
2.命令行工具进行运行时查询,jstat jmap
3.生成堆转储(Heap Dump)文件,然后利用Jhat或Eclipse的MAT等分析工具进行详细分析.
4.查看GC日志
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:…/logs/gc.log 日志文件的输出路径
5.直接内存的调优: 设置打开NMT -XX:NativeMemoryTracking,可以从本地内存分配的角度进行解读.
6.内存泄漏,Eclipse的内存分析工具(MAT)查看引用链

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值