JVM 数据区域及垃圾回收

概述

谈到JVM的面试考点,集中在数据区域,及垃圾回收。为什么呢?
我的理解是:首先在写Java程序的时候,是否真的明白声明一个变量,在内存里如何存储,以及为什么会有数据区域,为什么不放在一起?其次就是写Java程序不用关心回收对象,那JVM如何判定一个对象什么时候可以回收,以及回收采用什么样的算法,自己在写程序的时候哪些是常驻内存的,哪些是被回收的。

本文分两部分,第一部分:数据区域与垃圾回收基本概念,第二部分:考点部分,集中在垃圾回收及算法。

数据区域与垃圾回收

数据区域

程序计数器

线程隔离,记录程序运行的位置,例如程序运行到第140行

虚拟机栈

每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息

本地方法栈

与虚拟机栈类似,用于存放执行本地方法(Native)的栈

所有的对象实例以及数组在堆上分配

方法区

各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。运行时常量池(Runtime Constant Pool)是方法区的一部分。用于存放编译期生成的各种字面量与符号引用。

通过实践《深入理解JVM》的数据区域程序demo,去测试stackoverflow heapOutOfMemory等等

垃圾回收

哪些对象可以垃圾回收?

思想:JVM使用可达性分析来判断对象是否可以被回收。(引用计数,无法解决相互引用的对象,被回收。)

GCRoot:

  1. 虚拟机栈:栈帧中的本地变量表引用的对象;
  2. native方法引用的对象;
  3. 方法区中的静态变量和常量引用的对象。

回收使用的算法

三个核心算法:标记-清除,标记-整理,复制

标记-清除

过程:先将可以回收的对象进行标记,然后进行清除。
特点:效率快,但会产生内存碎片。

标记-整理

过程:先将不回收的对象进行标记,然后将不回收的对象移到内存的一端。
特点:执行效率低于标记-清除,但不产生内存碎片。

复制

过程:将内存分为两块,将存活的对象移动到没有被使用的一块内存。
特点:执行效率很快,但会造成内存浪费,因为总有一部分内存不能使用。

实际在JDK1.8以前,使用的都是分代算法。新生代使用复制,老年代使用标记-清除/整理

垃圾收集器

垃圾收集器是垃圾回收算法的实现

Serial收集器,新生代收集器,采用复制算法,单线程工作。
ParNew是Serial多线程版本,若ParNew用在单核机器上,由于要来回切换线程,实际效率不如Serial。
Parallel Scavenge收集器是关注“吞吐量优先收集器”
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
Parallel Scavenge加Parallel Old搭配使用。
ParNew 与 CMS搭配使用。


CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上。
四个步骤:初始标记,并发标记,重新标记,并发清除。
存在三个缺点:对cpu非常敏感。并发标记,无法处理浮动垃圾。内存碎片问题。

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
G1收集器开创的基于Region的堆内存布局。Region中还有一类特殊的Humongous区域,专门用来存储大对象。
G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。
后台维护个优先列表,优先处理回收价值收益最大的那些Region。
四个步骤:初始标记,并发标记,最终标记,筛选回收。
最先进的垃圾收集器的设计导向都不约而同地变为追求能够应付应用的内存分配速率(Allocation Rate),而不追求一次把整个Java堆全部清理干净。这样,应用在分配,同时收集器在收集,只要收集的速度能跟得上对象分配的速度,那一切就能运作得很完美。

CMS和G1的区别:

  • CMS是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用,G1收集范围是老年代和新生代,不需要结合其他收集器使用;
  • CMS以最小的停顿时间为目标的收集器,G1可预测垃圾回收的停顿时间;
  • CMS是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片,G1使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

JDK默认的GC收集器

关于数据区域及GC的考点

1.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代?
答:
思路:先描述一下Java堆内存划分,再解释Minor GC,Major GC,full GC,描述它们之间转化流程。
新生代分为eden,survivor区, E,S0,S1 新生代的收集统称为MinorGC,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年代。即长期存活的对象进入老年代。
老年代无法容纳更多对象,则会进行MajorGC,一般都会触发fullGC

2.新生代为什么要有Eden,还有survivor0 survivor1?
答:因为如果只有eden,那进行一次MinorGC,则对象进入老年代,老年代很快被填满,则进行MajorGC,MajorGC的效率比MinorGC慢很多(标记-整理)
Survivor存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生
第二块survivor保证了eden和s0将存活的对象拷贝到s1,防止了内存碎片。

3.CMS与G1的区别?
答:见上文

0%