1.JVM的内存模型
2.虚拟机为什么使用元空间替换了永久代
JDK8之后,方法区存在于元空间(Metaspace)。物理内存不再与堆连续,而是直接存在于本地内存中,理论上机器内存有多大,元空间就有多大。
3.引用的四种类型
强引用:把一个对象赋给一个引用变量,这个引用变量就是一个强引用
软引用:对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收
弱引用:只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存
虚引用:虚引用的主要作用是跟踪对象被垃圾回收的状态
4.所有对象都在堆空间上分配内存么
JVM通过「逃逸分析」,那些逃不出方法的对象会在栈上分配。
逃逸分析基于JIT技术使用栈上分配避免一些对象在堆空间分配内存。我们都知道在堆空间上GC有一定时间开销,如果一个对象的作用域不会逃逸出方法调用,那么久没必要在堆上分配空间并产生潜在的GC时间。
而在实际的项目开发中,大部分局部对象不会逃逸出方法调用,因此栈上分配存在的意义极大。然而栈上分配技术只能支持到方法维度,在线程维度上它无能为力。如果一个对象的作用域不会逃逸出方法调用,那么久没必要在堆上分配空间并产生潜在的GC时间。
5.垃圾回收是否能够在任意地方执行
不能,只有在安全点的地方执行,说起安全点需要先了解HotSpot中有一个数据结构叫OopMap,它用于记录对象的什么偏移量下的什么类型的数据,而这个数据结构只在某些特定的位置存在,我们就叫做安全点。
在即时编译过程中,也会在「特定的位置」生成 OopMap,记录下栈上和寄存器里哪些位置是引用。
特定的位置主要在:
1.循环的末尾(非 counted 循环)
2.方法临返回前 / 调用方法的call指令后
3.可能抛异常的位置
6.什么是指针碰撞
一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放到另外一边,
中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是“「指针碰撞」”。
7.什么是空闲列表
如果java为新生对象分配的内存是不规整的,则会有一个列表记录可用内存碎片,当为对象分配内存的时候会到这个列表中去查询可用内存然后再分配内存,这个列表就是空闲列表。 (核心点 分配内存-》内存不规整)
8.什么是TLAB
提高对象在堆上的分配效率而采用的一种手段,就是给每个线程分配一小块私有的堆空间,即TLAB是一块线程私有的堆空间(实际上是Eden区中划出的)
9.说说对ThreadLocal的理解
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,适用于各个线程不共享变量值的操作。
ThreadLocalMap是一个数组,元素为entry,对应的key为threadLocal。
10.为什么 ThreadLocalMap 的 key 是弱引用
key使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除,
这样尽可能的减少内存泄露的情况出现。
11.ThreadLocal类之如何让子类访问父线程的值
通过继承InheritableThreadLocal类,让子线程可以访问父线程中设置的本地变量
12.ThreadLocal是否存放在TLAB中
观点错误,ThreadLocal是在堆中分配的内存,并不是在TLAB。
ThreadLocal是由ThreadLocalMap这个数据结构实现的,ThreadLocalMap是在堆上分配的内存空间,并且为每个线程都维护了一个数组table,然后数组元素Entry的key为线程的引用(弱引用)。
而TLAB是线程单独开辟的私有的一小块内存,当对象在TLAB分配内存失败了之后,才会再去堆上去分配内存。
对象分配流程图:
13.CMS收集器和G1收集器的区别
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
CMS收集器以最小的停顿时间为目标的收集器;
G1收集器可预测垃圾回收的停顿时间
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片
14.谈谈垃圾收集器的回收算法
1.“标记–清除”算法;首先标记出所有需要被回收的对象,然后在标记完成后统一回收掉所有被标记的对象。
2.复制算法;将内存划分为等大的两块,每次只使用其中的一块。
3.“标记–整理”算法。
4.分代收集算法。
15.GCRoot有哪些
16.为什么跨代引用是 GC Roots
对年轻代做可达性分析时,如果一个对象A,没有被 GCRoots A 引用,那它可以被回收吗?不一定,因为它可能被老年代的对象引用了。所以,为了知道对象 A 是否可以被回收,还需要遍历一遍老年代。那这就违背了分代回收节省时间的初衷了。
为了解决问题,引入了「跨代引用是 GC Root」的解决办法:如果老年代的 Old 对象,引用了年轻代的 Young 对象,在对年轻代进行可达性分析时,Old 对象算作 GC Root。这样就不用遍历老年代了。
链接:https://www.zhihu.com/question/389362829/answer/1201617656
17.什么是 CardTable
分代回收算法需要有一个表,用来记录所有的跨代引用,很耗内存。HotSpot 使用 CardTable 记录老年代对年轻代的引用。把老年代按照 4KB 的大小分块,每一块对应在 CardTable 中都是1 bit。当值为1时,表示这4KB 的内存中有对年轻代的引用,需要加入到 GC Roots 中。
链接:https://www.zhihu.com/question/389362829/answer/1201617656
18.CMS并发更新失败
Minor GC后,空间容纳不了剩余对象,将要放入老年带,老年带有碎片或者不能容纳这些对象,就产生了concurrent mode failure, 然后进行stop-the-world的Serial Old收集器。
解决办法:
-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5 或者 调大新生代
CMS是和业务线程并发运行的,在执行CMS的过程中有业务对象需要在老年带直接分配,例如大对象,但是老年带没有足够的空间来分配,所以导致concurrent mode failure, 然后需要进行stop-the-world的Serial Old收集器。
解决办法:
+XX:CMSInitiatingOccupancyFraction,调大老年代的空间,+XX:CMSMaxAbortablePrecleanTime
19.JVM三色标记算法
根据可达性分析算法,从 GC Roots 开始进行遍历访问
初始状态,所有的对象都是白色的,只有 GC Roots 是黑色的
初始标记阶段,GC Roots 标记直接关联对象置为灰色
并发标记阶段,扫描整个引用链
没有子节点的话,将本节点变为黑色
有子节点的话,则当前节点变为黑色,子节点变为灰色
重复并发标记阶段,直至灰色对象没有其它子节点引用时结束