【面试题总结】JVM01-组成及垃圾回收
一、概念
1、JVM组成及作用
(1)组成:类加载器、运行时数据区(Java内存模型)、执行引擎、本地库接口
(2)作用:
类加载器(ClassLoader)把class文件转换成字节码;
运行时数据区(Runtime Data Area)把字节码加载到内存中;
特定的命令解析器执行引擎(Execution Engine),将内存中的字节码翻译成底层系统指令,再交由 CPU去执行;
调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
2、内存模型JMM/运行时数据区
(1)组成
线程共享:堆、方法区(非堆,内存元空间,包括常量池)/永久代
线程私有:栈(虚拟机栈、本地方法栈)、程序计数器
(2)线程共享
a. 堆(GC堆):存放对象实例,是垃圾回收器的区域,可以细分为新生代和老年代;
更好地进行内存分配与回收,可以划分为多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB),存储的依旧是对象实例。
堆在逻辑上连续,物理上不连续,大小可固定可扩展。
堆中无法完成实例分配且无法扩展时,会抛出OOM(OutOfMemoryError)异常/内存溢出。
堆中一个空对象大小占8字节byte
b. 方法区:存储被JVM加载的常量、静态变量、类信息、即时编译代码等数据。
JVM中将其描述为堆的一个逻辑部分,也被称为非堆(Non-Heap)。
又可以分为运行时常量池和直接内存。
(3)线程独享
a. 程序计数器:当前线程所执行字节码的行号指示器
可以完成程序的分支、循环、跳转、异常处理、线程恢复等基础功能
为了便于多线程切换时能恢复到正确的执行位置,不会出现OOM
b. 栈:存放基本类型和堆中对象的引用
可以分为虚拟机栈和本地方法栈
虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,本地方法栈为虚拟机使用到的Native方法服务
可能会抛出StackOverflowError异常/栈溢出
一个对象只对应了一个4字节的引用(堆栈分离的好处)
3、为什么要进行堆栈分离
栈代表了处理逻辑,而堆代表了数据,分开使得处理逻辑更为清晰
堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象),栈中只需要记录堆的地址。
提供了一种有效的数据交互方式(如:共享内存),也可以节省内存
栈固定,而堆可以根据需要动态增长
4、对象的访问定位方式
使用句柄:堆中会划分出句柄池,池中存储对象的句柄地址,句柄中包含数据的具体地址
直接指针:存储类型和数据,修改时需要变两次指针
二、垃圾回收
1、判断可以回收的方法
引用计数法
可达性分析算法
2、从哪回收
由栈指向堆,从堆中回收
3、标为垃圾的对象会回收吗
不一定,至少经历两次标记
第一次标记:可达性分析后没有引用链,就会被标记
第二次标记:筛选没有重新与引用链建立联系的对象,进行第二次标记,执行finalize()方法,并进行回收;如果在finalize()方法中重新与引用链建立关联,则逃离本次回收,继续存活。
4、引用概念
引用分为强引用、软引用、弱引用、虚引用,四种引用强度依次减弱。
强引用:永不被回收
软引用:有用但非必须,内存溢出前进行回收,并将其加入引用队列
弱引用:只要被GC线程扫描到,就会被回收
虚引用:随时被回收,用于对象被回收时收到系统通知
5、垃圾回收算法
标记-清除(Mark-Sweep):会造成内存碎片
复制算法(Copying):有用的复制到另一边
标记-整理(Mark-compact):存活对象移动到最左端,解决内存碎片问题
分代收集:大部分JVM采取,划分为新生代、老年代,以及非堆的永久代(方法区)
6、为什么使用分代收集
(1)对象的生命周期不同,针对不同对象,采用不同收集方式,可以提高回收效率
(2)如String对象等临时变量,有些甚至一次回收;Session对象与业务挂钩,生命周期长
7、新生代Young回收
(0)Minor GC,频率高,无需等到eden区满后才触发,主要采用Copying算法
(1)新生成的对象均放在年轻代,目标是收集生命周期短的对象
(2)按8:1:1的比例分为eden区和两个survivor区(survivor0、survivor1)
复制清空
先将对象生成到eden区,回收时将存活对象复制到survivor0区,然后清空eden区;
当survivor0区存满时,将eden区和survivor0区存活对象复制到survivor1区,并清空eden区和survivor0区,并将survivor0区和survivor1区交换,保持survivor1区为空;
(3)当 survivor1 区不足以存放 Eden 区 和 survivor0区的存活对象时,将存活对象放至老年代;
(4)老年代满了,就会触发一次Full GC(major GC),同时回收新生代和老年代
8、老年代回收
(1)年轻代经历N次回收后仍然存活的对象(生命周期较长),就会进入老年代
(2)老年代内存是新生代的一倍,老年代对象存活率比较高
6、浮动垃圾
GC线程和工作线程并行运行,在垃圾回收过程中产生的垃圾,就产生了浮动垃圾,这些垃圾在下次垃圾回收时才能被回收
7、内存碎片
不同对象存活时间不同,不进行内存整理,就会出现内存碎片
复制和标记整理算法,均可以解决碎片问题
8、常见的垃圾回收器
(1)Serial(Old)串行收集器-单线程
使用复制算法,是client默认的方式
通过 -XX:+UseSerialGC 来强制指定
(2)ParNew收集器-多线程
ParNew:Serial的多线程版本
(3)Parallel Scavenge-高吞吐量的并行收集器
使用停止-复制算法,是server级别的默认GC方式
使用-XX:+UseParallelGC强制指定,用-XX:ParallelGCThreads=4指定线程数
(4)Parallel Old收集器-并行收集器
Parallel 收集器的老年代版本
(5)CMS(Concurrent Mark Sweep)收集器-并发收集器(同时开始工作)
使用标记-清除算法
以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器
加上“-XX:+UseConcMarkSweepGC”指定
注意:产生大量碎片,剩余内存无法运行,出现Concurrent Mode Failure
临时CMS采用Serial Old回收器进行垃圾清除,此时的性能将会被降低
(6)G1(Garbage-First)-分区模型
将内存分为多个Region
后台维护优先列表,根据收集时间,选择回收价值最大的Region
(7)〇暂停ZGC(Zero pause GC)
采用颜色指针,根据颜色指针决定是否做相应操作
从根开始找
标记位标记指针的状态,初始标记为M0
有M0就是有用的,没有 M0,是remapped就是垃圾
线程访问M0时,对应对象的标记就会换成M0
8、CMS垃圾回收器
(1)含义
Concurrent Mark-Sweep,并发标记-清除,以牺牲吞吐量为代价来获得最短回收停顿时间
(2)过程
初始标记:快速记录与root直接相连的对象,暂停其他线程;
并发标记:GC和用户线程同时开始,由于不断更新引用域,闭包内不能包含所有的可达对象,会记录引用更新位置;
重新标记:修正引用更新的位置,
并发清除:对未标记区域进行清扫
9、G1垃圾回收器
(1)分区:把整个堆划分为一个一个等大小的区域(region)。内存的回收和划分都以region为单位
(2)支持分代的垃圾回收
(3)区域优先:收集那些活跃对象小的 region,以便快速回收空间
10、垃圾回收策略/时机
(1)新生代的Minor / Scavenge GC
Eden区满后触发新生代的 Minor GC
Eden区不会分配很大,所以Eden区的GC会频繁进行
(2)老年代的Major GC-速度慢
何时会触发?
对于大对象,首先在Eden尝试创建,创建不了,就会触发Minor GC
随后继续尝试在Eden区存放,仍然放不下
尝试进入老年代,老年代也放不下
触发 Major GC 清理老年代的空间
(3)整个堆的Full GC
减少Full GC次数,对JVM的调优即对Full GC的调节
11、何时会导致Full GC
调用System.gc(),会建议虚拟机执行Full GC
大对象进入,导致老年代空间不足
永久代空间不足
空间分配担保失败:使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。
本文来自博客园,作者:哥们要飞,转载请注明原文链接:https://www.cnblogs.com/liujinhui/p/15857680.html