JVM面经

目录

11. 解释下对象栈上分配,逃逸分析与标量替换

18.解释下垃圾收集底层三色标记算法

19.解释下对向漏标的处理方案增量更新与原始快照

20.解释下G1收集器垃圾收集过程

 21.G1垃圾收集器最大停顿时间是如何实现的

22.内存泄漏到底是怎么回事,怎么快速排查与解决

23.GC是什么时候都能做吗?知道GC安全点与安全区域是怎么回事吗?

26.Redis到底是单线程还是多线程

27.Redis单线程为什么还能这么快

28.Redis底层数据是如何用跳表来存储的

29.Redis Key过期了为什么内存没有被释放

30.Redis Key没设置过期时间为什么被Redis主动删除了

31.Redis淘汰Key的算法LRU和LFU的区别


11. 解释下对象栈上分配,逃逸分析与标量替换

逃逸分析:    

  • 逃逸分析分基本行为就是分析对象动态作用域:当一个对象被定义后,它可能被外部方法引用,例如作为调用参数被传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或者可以在其他线程中访问的实例变量,称为线程逃逸。
  • 如果能证明一个对象不会逃逸到线程或方法以外,也就是别的线程或方法无法通过任何的形式访问到这个对象,则可能为这个变量进行一些高效的优化。
    • 栈上分配
    • 同步消除
    • 标量替换
  • 逃逸分析大概分为一下类型:
    • 全部变量赋值逃逸
    • 方法返回值逃逸
    • 实例引用发生逃逸
    • 线程逃逸

标量替换:

         首先什么是标量,所谓的标量指的是不能进一步分解的量。像 Java 的基础数据类型(int、long等数值类型以及 reference 类型等)以及对象的地址引用都是标量,因为它们是没有办法继续分解的。与标量对应的是聚合量,聚合量指的是可以进一步分解的量,比如字符串就是一个聚合量,因为字符串是用字节数组实现的,可以分解。又比如我们自己定义的变量也都是聚合量。

        标量替换:根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换,如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那么程序真正执行的时候将不创建这个对象,而该为创建它的若干个被这个方法使用到的成员变量来代替。将对象拆分后,除了可以让对象的成员变量在栈上(栈上存储的数据,很大机会会被虚拟机分配至物理机器的高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件.

        可以使用这个参数:-XXEliminateAllocations开启标量替换,JDK8默认开启

栈上分配

        指的是通过逃逸分析确认对象不会被外部访问到的话,那么就直接在栈上分配对象,那么在栈上分配对象的话,这个对象占用的空间在出栈的时候就销毁了,所以栈上分配可以降低垃圾回收的压力.

18.解释下垃圾收集底层三色标记算法

三色标记算法把GC roots可达性分析遍历过程中遇到的对象,按照是否访问过这个条件标记成一下三种颜色。

        黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的
对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫
描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
        灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描
过。
        白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。

19.解释下对向漏标的处理方案增量更新与原始快照

漏标

增量更新:当黑色对象插入新的指向白色对象的引用时,就将这个新加入的引用记录下来,待并发标记完成后,重新对这种新增的引用记录进行扫描;可以简单理解为,黑色对象增加了对白色对象的引用,那么这个黑色对象就被变灰.

原始快照就是当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫到白色的对象,将白色对象直接标记为黑色.(目的就是让这种对象在本轮gc清理中能存活下来,带下一轮gc的时候重新扫描,这个对象也可能是浮动垃圾)

对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:

CMS:写屏障 + 增量更新
G1:写屏障 + SATB(原始快照)
ZGC:读屏障

20.解释下G1收集器垃圾收集过程

G1的垃圾收集(主要指Mixed GC)过程可以分为下面几个步骤:

初始标记:暂停所有的其他线程,并记录下gc roots直接引用的对象,速度很快

并发标记:可以与应用线程一起工作,进行可达性分析,与CMS的并发标记一样

最终标记:可以与应用线程一起工作,进行可达性分析,与CMS的并发标记一样

筛选回收:筛选回收阶段首先对各个Region的回收价值和成本进行排序根据用户所期望的GC停顿SWT时间(可以用JVM参数 -XX:MaxFCPauseMillis指定)来制定回收计划,比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Regionl刚好需要200ms,那么就只会回收800个Region(Collection Set,要回收的集合),尽量把GC导致的停顿时间控制在我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。不管是年轻代或是老年代,回收算法主要用的是复制算法,将一个region中的存活对象复制到另一个region中,这种不会像CMS那样回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多的内存碎片。(注意:CMS回收阶段是跟用户线程一起并发执行的,G1因为内部实现太复杂暂时没实现并发回收,不过到了ZGC,Shenandoah就实现了并发收集,Shenandoahi可以看成是G1的升级版本)

 21.G1垃圾收集器最大停顿时间是如何实现的

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region,比如一个Region花费200ms能回收1M的垃圾,另一个化50ms能回收2M的垃圾,在回收时间有限的情况下G1会有限选择后面的Region回收,这种使用划分内存空间以及优先级的区域回收方式,保证了G1收集器在有限的时间内可以尽可能高的收集效率。

22.内存泄漏到底是怎么回事,怎么快速排查与解决

一般的电商架构可能会使用多级缓存架构,就是redis加上JVM级缓存,如果对JVM级缓存就简单的使用一个hashmap,于是不断的往里面放缓存数据,但是很少考虑这个map的容量问题,结果这个map越来越大,一直占用着老年代的很多空间,时间长了就会导致full gc非常频繁,这就是一种内存泄漏,对于一些老旧数据没有及时清理导致一直占用宝贵的内存资源,时间长了除了导致full gc还会导致OOM。

这种情况可以考虑采用一些成熟的JVM级缓冲框架来解决,比如ehcache等自带一些LRU数据淘汰算法的框架来作为JVM级 的缓存。

23.GC是什么时候都能做吗?知道GC安全点与安全区域是怎么回事吗?

GC不是任何时候都能做的,需要运行到安全点或安全区域才能做到。

 安全点就是指代码中一些特定的位置,当线程运行到这些位置时,它的状态是确定的,这样JVM就可以安全的运行一些操作,比如GC等,所以GC不是想什么时候做就立即触发的,是需要等所有线程运行到安全点后才能触发。

这些特定的安全点位置主要有一下几种

  • 方法返回之前
  • 调用某个方法之后
  • 抛出异常的位置
  • 循环的末尾

大体实现思想是当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时在上面说的那些安全点位置都会检查这个标志,一旦发现中断标志为真时就自己在安全点上主动中断挂起。

安全区域又是什么

安全点是对正在执行的线程设定的,如果一个线程处于Sleep或中断状态,他就不能响应JVM的中断请求,在运行到安全点上。

因此JVM引入了安全区域,指在一段代码片段中,引用关系不会发生变化,在这个区域内的任意地方开始GC都是安全的。

26.Redis到底是单线程还是多线程

Rdis6.0版本之前的单线程指的是其网络IO和键值对读写是由一个线程完成的.
Rdis6.0引入的多线程指的是网络请求过程采用了多线程,而键值对读写命令仍然是单线程处
理的,所以Redis依然是并发安全的.
也就是只有网络请求模块和数据操作模块是单线程的,而其它的持久化、集群数据同步等,其
实是由额外的线程执行的.

27.Redis单线程为什么还能这么快

1、命令执行基于内存操作,一条命令在两存里操作的时间是几十纳秒
2、命令执行是单线程操作,没有线程切换开销
3、基于IO多路复用机制提升Redis的I/O利用率
4、高效的数据存储结构:全局hash表以及多种高效数据结构,比如:跳表,压缩列表,链表等等

28.Redis底层数据是如何用跳表来存储的

 跳表:将有序链表改造为支持近似“折半查找”算法,可以进行快速的插入、删除、查找操作

29.Redis Key过期了为什么内存没有被释放

1.在使用set命令时,如果刚开始就设置了过期时间,那么之后修改这个key,也务必要加上过期时间避免过期时间丢失。

2.Redis对于过期key的处理一般有惰性删除和定时删除两种策略

惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,判断key是否过期,如果过期了直接删除这个key

定时删除:由于惰性删除策略无法保证冷数据被及时删除,所以Redis会定期(默认100ms)主动淘汰一批已过期的key,这里的一批只是部分过期key,所以可能会出现部分key已经过期但还没有被清理掉的情况,导致内存并没有被释放。

30.Redis Key没设置过期时间为什么被Redis主动删除了

当Redisi已用内存超过maxmemory限定时,触发主动清理策略
主动清理策略在Redis4.0之前一共实现了6种内存淘汰策略,在4.0之后,又增加了2种策略,总共8种:
a)针对设置了过期时间的key做处理:
        1.volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
        2.volatile-.random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
        3.volatile-lru:会使用LRU算法筛选设置了过期时间的键值对删除。
        4.volatile-lfu:会使用LFU算法筛选设置了过期时间的键值对删除。
b)针对所有的key做处理:
        5.allkeys-random:从所有键值对中随机选择并删除数据。
        6.allkeys-lru:使用LRU算法在所有数据中进行筛选删除。
        7.allkeys-lfu:使用LFU算法在所有数据中进行筛选删除。
c)不处理:
        8.noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)OOM command not allowed when used memory'",此时Redis只响应读操作。

31.Redis淘汰Key的算法LRU和LFU的区别

LRU(最近最少使用):淘汰很久没被访问的数据,以最近一次访问时间作为参考。

LFU(最不经常使用):淘汰最近一段时间被访问次数最少的数据,以次数作为参考。

绝大多数情况我们都可以使用LRU策略,当存在大量热点数据时,LFU可能会更好点。

32.删除Key的命令会阻塞Redis吗

首先需要搞清楚key的执行过程,对于不同的key删除过程是不一样的,如果删除的是一个字符串那么时间复杂度为O(1),删除单个列表,集合,有序集合或哈希类型的key,时间复杂度为O(M),M为以上数据结构内的元素数量,当删除的是列表,集合或哈希表类型的时候会耗费比较多的时候会阻塞redis,即使删除的是字符串如果字符串对应的值比较大的时候其实也会耗费一定的时间所以也会阻塞 redis;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值