JVM内存模型
栈、本地方法栈、程序计数器都是线程独占,堆、方法区都是线程共享的。面试时主要回答两个点:
- 各部分的功能
- 哪些是线程共享的,哪些是线程独占的
栈
是线程私有的,线程在执行每个方法时候都会创建一个栈帧,用来存储局部变量表和动态链接,方法出口等信息,调用方法时执行入栈,方法返回时执行出栈。
本地方法栈
与栈类似,也是用来保存线程执行方法时的一些信息,不同的是执行java方法时使用栈,执行native方法(一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C)时使用本地栈。
程序计数器
保存着当前线程执行的字节码位置,每个线程工作时都有一个独立的计数器。程序计数器只为执行java方法服务,执行native方法时程序计数器为空。
堆
是JVM管理中最大的一块,堆被所有线程共享,目的是为了存放对象的实例,几乎所有对象的实例都会存放在这里。当堆内存没有可用的空间时,会抛出OOM异常。
根据堆对象存活的周期不同,JVM把堆内存进行分代管理,由垃圾回收器来进行垃圾的回收管理。
方法区
也是JVM中各个线程共享的区域,又叫C堆区,用于存储已被虚拟机加载的类信息、常量、静态变量、JVM优化后的代码等等。JDK1.7前的永久代和1.8后的MeteSpace都是方法区的一种实现。
JMM内存模型
是java的内存模型,和JVM内存模型是不一样的,JMM的目标定义程序中变量的访问规则。如上图所示,所有的共享变量都存储在主内存中共享,每个每个线程有自己的工作内存,工作内存中保存的是主内存变量的副本,线程中所有对变量的操作必须在自己的工作内存中执行,而不能直接读写主内存中的变量。
在多线程进行数据交互时,例如线程A、B,先由线程A读取这个变量,线程A修改完了变量是修改在自己的工作内存区中,线程B是不可见的,只有从A的工作内存区写回到主内存,线程B从主内存读取到后才能进行进一步的操作。由于指令重排序的存在,这个写和读的顺序有可能会被打乱,因此JMM需要提供原子性、可见性、有序性的保证
类的加载与卸载
类的加载
双亲委派的好处:
- 避免类的重复加载
- 避免java的核心api被篡改
分代回收
堆内存被分代管理,为什么?
主要是为了方便垃圾回收,这样做是基于两个事实:第一是大部分对象很快就不会使用,第二是还有一部分不会立即不使用但也不会持续很长时间。
虚拟机中划分为年轻代、老年代、永久代。看上图,
- 年轻代主要是用来存放新创建的对象,分为Eden区和两个Surivor(幸存)区,大部分对象是在Eden区,当Eden区满时还存活的对象会在两个Survivor区交替保存,达到一定次数后,对象会晋升到老年代。
- 老年代主要保存年轻代晋升来的对象信息。
- 永久代主要来保存类信息
根据引用计数法、复制法、标记清楚法几种:
- 引用计数法:通过对象被引用的次数来确定对象是否还在被使用,缺点是无法解决循环引用的问题。
- 复制算法:需要from、to两块大小相同的内存空间,对象分配时只在from块进行,回收时把存活对象复制到to块中,并清空from块,然后交换两块的分工,把from块做为to块,把to块作为from块。缺点是内存使用率较低。
- 标记清除算法:分为标记和清楚不再使用的对象两个阶段,缺点是会产生内存碎片。例如CMS、G1、ZGC
CMS算法
是JDK1.7以前最主流的算法。
第一个阶段是初始标记,标记的结果集只是从root直接可达的对象,会出现stop the world。
第二个阶段是并发标记,GC线程和应用线程并发执行,主要是标记可达的对象。
第三个阶段是重新标记,这个阶段是第二个stop the world阶段,停顿时间比并发标记要小很多,但比初始标记稍长,主要对对象进行重新扫描并标记。
第四个阶段,是并发清理阶段,主要对垃圾的清理。
最后一个阶段是并发重置阶段,为下一次GC准备相关数据结构
G1算法
在JDK1.9后成为了JVM的默认垃圾回收算法,G1的特点是保持高回收率的同时,减少停顿。G1算法取消了年轻代和老年代的物理划分,但它任然属于分代回收,G1算法将堆分为若干个区域reigen,如上图小方格所示,一部分区域用作年轻代,一部分用作老年代,还有另外一种专门用于存储巨型对象的分区,G1和CMS一样都遍历全部对象,然后标记对象引用情况,在清楚对象后,会对区域进行整合异动,整合碎片空间。
上图右边是G1的回收过程:
ZGC算法
可以支持tb级别的堆,它非常高效,能够做到10ms以下的回收停顿时间,这么短的停顿时间,ZGC是如何做到的呢?我们来了解下ZGC的黑科技:
- 着色指针:一个指针最大是64位,ZGC限制了指针只能占42位,这样寻址只会使用到42位 ,那么剩余的22位就可以用来保存额外的信息,ZGC就是利用指针的额外信息位在指针上对对象进行着色标记。
- 读屏障:来解决GC贤程和应用线程可能会并发修改对象状态的问题,而不是简单粗暴的使用STW来做全局的锁定,锁定只会在单个对象上产生,这样垃圾回收的大部分时候都不需要STW,因此ZGC的大部分时间都是并发处理
- 基于Region:不像G1是固定大小,而是动态决定大小,Region可以动态创建和销毁。这样可以更好的对大对象进行分配和管理。
- 压缩整理:CMS算法清理垃圾时是原地回收,会存在碎片问题。ZGC和G1一样也会在回收后对Region对象进行合并。
上图就是ZGC的回收过程
面试考察点
1深入理解JVM内存模型
2了解类加载机制
3了解内存可见性
4了解常用的GC算法实现和适用场景
5能够根据业务场景选择合适JVM参数和GC算法
加分项
1编译器优化
编译器优化方面有深入了解的话,会让面试官觉着你对技术深度角有追求。例如如何利用栈上分配减少内存压力,如何编写适合内联代码的问题等等
2问题排查经验和思路
面试官都喜欢动手能力强的同学,例如解决过线上经常fullGC的问题,排查过内存泄露的问题
3JVM调优经验与思路
针对特定场景的优化经验或思路,例如针对高平发低延迟的场景如何调整jvm参数尽量降低停顿时间,针对队列处理如何尽可能提高吞吐率等等
4了解最新的技术趋势(例如ZGC、Graalvm)
真题汇总
1.简单描述下JVM的内存模型
2.什么情况下会触发FullGC
年轻代晋升时,老年底内存不足时
3.Java类加载器有几种,关系是什么?
4双亲委派机制的加载流程是怎样的?有什么好处?
51.8为什么使用Metespace替换掉PermGen?Metespace保存在哪里?
6 编译期会对指令做哪些优化?(简单描述编译器的指令重排)
7简单描述下volatie可以解决什么问题?如何做到的?
强制主内存读写同步及防止指令重排序两点
8 简单描述下GC的分代回收
9 G1回收算法与CMS算法的区别在哪里?
10 对象引用有哪几种方式,有什么特定?
强弱软虚四种引用,以及在Jav中的处理方式
11使用过哪些JVM调试工具,主要分析哪些内容?
java自带的这些工具,例如堆分析工具Mat,线程分析工具jstack,获取堆信息的JMap等等