面试官:来聊聊JVM吧

374 阅读3分钟

1. jvm中有哪些内存区域

内存区域作用是否线程独有是否会OOM
java堆内存用来存放创建的对象
Metaspace用来存放类的元信息
java虚拟机栈用来保存方法的执行信息,包括局部变量表、操作数栈、动态链接、方法出口等信息,线程执行到某个方法,就会给这个方法创建一个栈帧,压入当前线程的栈中,当方法执行完就会将栈帧出栈。
本地方法栈和虚拟机栈类似,用来保存native方法的执行信息
直接内存直接内存并不属于jvm的内存结构,它是物理机的内存,但是jvm虚拟机可以调用这部分内存
程序计数器用来记录字节码指令执行到了哪里

2. 聊聊jvm的分代模型

jvm内存有一个分代模型:年轻代、老年代、永久代。

大多数对象出生时会先进入年轻代,其中的大部分生命周期又是非常短暂的,很快就被回收了,还有少数长期存活的对象会进入老年代

永久代主要用来存放类的一些类信息

分代的目的就是为了更高效的进行垃圾回收

3. 对象在jvm内存中如何分配?如何流转?

大部分对象会优先在新生代分配内存,当新生代快满了,没有足够的空间分配给新对象的时候,就会触发一次Young GC,会尝试将垃圾对象给回收掉,释放内存空间分配给新对象。

大多数对象都是这样,生存周期非常短,很快就会被回收掉,但是也有少部分对象能扛住多次垃圾回收,每扛住一次,它的年龄就会+1,当它年龄足够大了,默认是15次垃圾回收后,就可以去老年代“养老”了。

但也有2种场景对象可以提前进入老年代,

第1种情况是:对于大对象的创建,如果超过了“-XX:PretenureSizeThreshold”指定的大小,就会直接放在老年代

第2种情况是:jvm有动态对象年龄判断机制,大概就是说,年龄1+年龄2+...+年龄n的对象总和如果超过了Survivor区内存空间的50%,就会把年龄n以上的对象转移到老年代。

当然,随着老年代对象越来越多,内存也会有用完的时候,这时候就会触发一次Full GC,尝试回收垃圾对象释放内存空间。

4. Young GC和Full GC分别在什么情况下会发生

先统一概念,Young GC指的是年轻代的gc,Old GC指老年代gc,一般习惯把Old GC直接当成Full GC,也没有太大问题,因为一般Old GC会和Young GC一起发生,严格意义上的Full GC指的是年轻代 + 老年代 + 永久代gc

Young GC一般在新生代的Eden区满了就会触发

Old GC的触发有几种情况:

(1)在发生Young GC之前先检查一下,如果 老年代可用的连续内存空间大小 < 新生代历次Young GC后升入老年代的对象总和的平均大小,说明本次Young GC是有风险的,老年代空间大概率可能会不够用,所以就需要触发一次Old GC释放一些内存空间

(2)如果上面检查是通过的,但是实际执行Young GC后发现需要放入老年代的对象太多,老年代放不下,这时也会触发一次Old GC

(3)老年代内存使用率过高,也会直接触发Old GC,这个比例可以通过参数调整

5. 什么情况下jvm内存中的一个对象能被垃圾回收

在触发垃圾回收时,垃圾回收器会找出那些不可达的对象,

所谓不可达,就是找不到这个对象的GC Roots,例如,想回收A对象,先去找A对象的引用,发现是B对象,但B对象不是GC Root,就继续去找B对象的引用,发现没了,这种情况A对象就是不可达的,可以被回收。

需要注意的是,方法的局部变量和类的静态变量可以做出GC Roots,类的实例变量不行。

另外,在实际进行垃圾回收时,对不同引用类型的对象还会有不同的处理策略:

对于强引用对象,垃圾回收器是不会进行回收的,

对于软引用对象,只有在进行垃圾回收后发现内存仍然不够用时才会把对象给回收掉,

对于弱引用对象,只要发生垃圾回收就会给回收掉

6. 年轻代和老年代分别适合什么样的垃圾回收算法?

年轻代的大多数对象生存周期都是非常短暂的,很难撑到进入老年代,所以垃圾回收器对年轻代使用的是复制算法,大概思想就是把内存分成两块,在垃圾回收时会将存活的对象复制到其中一块,然后再把另一块内存回收掉,因为存活的对象只是少部分,所以移动这部分存活对象的成本相对会低一些。

在jvm实际实现时要考虑到资源的利用率,将年轻代分成了三块,一块叫Eden区,两块叫Survivor区,默认内存比例是8:1:1,每轮垃圾回收的存活对象会放在其中一块Survivor区,也就是说,只会占用年轻代少部分内存,符合年轻代对象“朝生夕死”的特点。

老年代的对象一般是会长期存活的,使用复制算法的成本会比较高,所以对这部分对象的垃圾回收采用了标记整理算法,就是把存活的对象标记出来,统一移动到老年代内存的一端,再把剩下的内存区间回收掉,给后面的对象使用。

7. 永久代的对象能不能被回收?

可以。永久代的对象回收条件相对比较苛刻,首先是所有这个Class的实例对象都已经被回收,其次加载这个Class的ClassLoader也被回收了,最后这个Class对象没有再被引用了,这种情况下Class才能被回收。

8. jvm中有哪些常见的垃圾回收器,各自的特点是什么

Serial和Serial Old

Serial负责新生代的垃圾回收,Serial Old负责老年代的垃圾回收

是一种单线程的垃圾回收组合,特点是执行垃圾回收的时候停掉其它工作线程,一般不会使用,了解一下既可。

ParNew和CMS

ParNew负责新生代的垃圾回收,CMS负责老年代的垃圾回收

这个组合目前在生产环境用的仍然比较多,多线程并行回收,性能较好

ParNew相比Serial,由于使用了多个线程进行垃圾回收,所以效率相对更高

CMS的工作原理

采用的是标记清理算法,

首先,会去追踪GC Roots,标记出垃圾对象,然后一次性把垃圾对象给回收掉。

缺点是,会出现很多内存碎片。

CMS的一次回收过程分为4个阶段:

  1. 初始标记

这个阶段会stop工作线程,然后标记出GC Roots直接引用的对象,间接引用的对象不算。

这个过程很快就能完成。

  1. 并发标记

这个阶段工作线程也可以同时在跑。

垃圾回收线程对已有对象会尽可能的进行GC Roots追踪,但因为工作线程也在运行,所以在这个过程中难免会存在一些新对象被创建,一些老对象变成了垃圾。

这个过程会非常耗时,但不影响系统运行。

  1. 重新标记

这个阶段也会stop工作线程,然后标记出第二阶段那些新创建的对象,还有变成垃圾的老对象。

这个过程也很快就能搞定。

  1. 并发清理

这个阶段工作线程也可以同时跑。

这个阶段开始清理之前标记的垃圾对象

这个过程也会比较耗时。

G1垃圾回收器的工作原理

与之前不同的是,G1虽然也有会新生代和老年代的概念,不过只是逻辑上的概念。

它会把java堆内存分成很多大小相等的Region,新生代包含一部分Region,老年代包含一部分Region。

G1允许设置一个预期的垃圾回收停顿时间,

以前还需要优化各种参数来达到尽可能少的停顿时间,

现在只需要告诉它你的需求,它就能够帮你自动搞定。

为了做到这点,G1有一套自己的算法,它会评估每个Region的回收成本,包括回收这个Region需要多长时间,能回收多少垃圾,然后再综合评估成本选择要回收的Region。


欢迎关注我微信公众号《倔强的文哥》(一个表面冷酷,内心热乎的互联网码农),不定时分享各种Java技术经验、面试热题、Python实用小技巧。