【面试题总结】JVM01-组成及垃圾回收

一、概念

1JVM组成及作用

1)组成:类加载器、运行时数据区(Java内存模型)、执行引擎、本地库接口

  

2)作用:

类加载器(ClassLoader)把class文件转换成字节码;

运行时数据区(Runtime Data Area)把字节码加载到内存中

特定的命令解析器执行引擎Execution Engine),将内存中的字节码翻译成底层系统指令,再交 CPU去执行

调用其他语言的本地库接口(Native Interface)来实现整个程序的功能

 

2、内存模型JMM/运行时数据区

1)组成

线程共享:堆、方法区(非堆,内存元空间,包括常量池)/永久代

线程私有:栈(虚拟机栈、本地方法栈)、程序计数器

2)线程共享

a. 堆(GC堆):存放对象实例,是垃圾回收器的区域,可以细分为新生代和老年代;

更好地进行内存分配与回收,可以划分为多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB),存储的依旧是对象实例。

堆在逻辑上连续,物理上不连续,大小可固定可扩展。

堆中无法完成实例分配且无法扩展时,会抛出OOMOutOfMemoryError)异常/内存溢出。

堆中一个空对象大小占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回收

0Minor GC,频率高,无需等到eden区满后才触发,主要采用Copying算法

1)新生成的对象均放在年轻代,目标是收集生命周期短的对象

2)按8:1:1的比例分为eden区和两个survivor区(survivor0survivor1

复制清空

先将对象生成到eden区,回收时将存活对象复制到survivor0区,然后清空eden区;

survivor0区存满时,将eden区和survivor0区存活对象复制到survivor1区,并清空eden区和survivor0区,并将survivor0区和survivor1区交换,保持survivor1区为空;

3)当 survivor1 区不足以存放 Eden 区 和 survivor0区的存活对象时,将存活对象放至老年代;

4)老年代满了,就会触发一次Full GCmajor GC),同时回收新生代和老年代

 

8、老年代回收

1)年轻代经历N次回收后仍然存活的对象(生命周期较长),就会进入老年代

2)老年代内存是新生代的一倍,老年代对象存活率比较高

 

6、浮动垃圾

GC线程和工作线程并行运行,在垃圾回收过程中产生的垃圾,就产生了浮动垃圾,这些垃圾在下次垃圾回收时才能被回收

 

7、内存碎片

不同对象存活时间不同,不进行内存整理,就会出现内存碎片

复制和标记整理算法,均可以解决碎片问题

 

8、常见的垃圾回收器

1SerialOld)串行收集器-单线程

使用复制算法,是client默认的方式

通过 -XX:+UseSerialGC 来强制指定

2ParNew收集器-多线程

ParNewSerial的多线程版本

3Parallel Scavenge-高吞吐量的并行收集器

使用停止-复制算法,是server级别的默认GC方式

使用-XX:+UseParallelGC强制指定,用-XX:ParallelGCThreads=4指定线程数

4Parallel Old收集器-并行收集器

Parallel 收集器的老年代版本

5CMS(Concurrent Mark Sweep)收集器-并发收集器(同时开始工作)

使用标记-清除算法

以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器

加上-XX:+UseConcMarkSweepGC”指定

注意:产生大量碎片,剩余内存无法运行,出现Concurrent Mode Failure

临时CMS采用Serial Old回收器进行垃圾清除,此时的性能将会被降低

6G1Garbage-First-分区模型

将内存分为多个Region

后台维护优先列表,根据收集时间,选择回收价值最大的Region

7)〇暂停ZGCZero pause GC

采用颜色指针,根据颜色指针决定是否做相应操作

从根开始找

标记位标记指针的状态,初始标记为M0

M0就是有用的,没有 M0,是remapped就是垃圾

线程访问M0时,对应对象的标记就会换成M0

 

8CMS垃圾回收器

1)含义

Concurrent Mark-Sweep,并发标记-清除,以牺牲吞吐量为代价来获得最短回收停顿时间

2)过程

初始标记:快速记录与root直接相连的对象,暂停其他线程;

并发标记:GC和用户线程同时开始,由于不断更新引用域,闭包内不能包含所有的可达对象,会记录引用更新位置;

重新标记:修正引用更新的位置,

并发清除:对未标记区域进行清扫

 

9G1垃圾回收器

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

posted @ 2022-01-30 21:58  哥们要飞  阅读(28)  评论(0编辑  收藏  举报