JVM内存区域
在讲述jvm垃圾回收之前,我们先介绍一下jvm的内存区域划分,主要分为以下五块:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 方法区
- 堆内存
内存区域 | 是否线程私有 | 含义 |
---|---|---|
程序计数器 | 私有 | 由于jvm多线程执行,所以每个线程需要记录线程执行到哪个字节码的位置,这样当线程获得cpu资源时才能知道从哪里继续执行 |
虚拟机栈 | 私有 | 虚拟机栈是java方法执行的内存模型,每个方法的执行就会创建一个栈帧,每个栈帧中包含了该函数的返回地址和局部变量。每个方法从调用到执行完毕,是一个入栈到出栈的过程,先入后出。虚拟机栈也是线程私有 |
本地方法栈 | 私有 | 和虚拟机栈类似,存放java中native方法 |
方法区 | 共享 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。是所有线程共享区域 |
堆内存 | 共享 | Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存 |
另外说明一下堆栈区别:
- 栈是运行时单位,堆是存储单位
- 栈代表的是运行逻辑,堆代表的是对象存储
- 栈报错的是系统运行上下文,栈是向上增长,而堆可以动态伸缩
- 栈中存放的是对对数据的引用,和基本数据类型
对象存活分析
在介绍jvm垃圾回收算法之前,我们需要明确什么样的对象才能被标注为垃圾;对象存活分析主要有两种分析方法:
- 引用计数算法
- 可达性分析
引用计数算法
这是一种比较老的回收算法,其原理是每个对象都维护一个引用计数器,当一个对象存在一个引用就增加一个计数,删除一个引用则减少一个计数,垃圾回收时只回收计数器为0的对象。
其缺点是,当两个对象循环引用时无法回收。
可达性分析
通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,从而引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的(注意一定要是到达GC roots)。
那么那些点可以作为GC Roots呢?
- 虚拟机栈(栈桢中的本地变量表)中的引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(Native方法)的引用的对象
几种垃圾回收的算法
标记清除算法
- 从根节点标记被引用的对象。
- 将未被标记的对象进行清理。 此方法的缺点是,被清理后的内存空间并不连续,会产生内存碎片,在分配大对象的时候,可能导致无法分配。
复制算法
- 吧内存空间分为两个相同大小的独立的区域,每次只使用其中一个。
- 垃圾回收时将需要保留的对象复制到另一个内存区域中。
- 清除需要回收的内存区域。 此方法的缺点是,会导致内存空间浪费
标记整理算法
- 从根节点标记被引用的对象。
- 遍历堆,在清除未被标记的对象的同时,将存活的对象顺序存放到堆内存中的一块 此方法解决了内存碎片的问题,也避免了内存空间浪费的问题
分代回收策略
在进行垃圾回收的时候,基于“不同的对象的生命周期是不一样的”这一情况,因此可以对不同的对象进行不同的回收方式。
例如某些对象是长时间存在的:http session、线程等,但某些对象诸如临时变量只是使用一次就可能不再使用;如果使用同一的内存回收方式,则每次都要遍历整个堆进行回收,会降低回收的效率。
如何分代
将堆内存分为三个区:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。
- 年轻代
年轻代又被分为三个区:伊甸取(Eden区)、两个存活区(survivor)。
- 新产生的对象都存放于eden区。
- 当eden区域满了的时候,将还存活的放入其中一个Survivor01。
- 当Survivor01区满了的时候,将其中还存活的对象放入Survivor02。
- 当Survivor02满了的时候,将经经历过上一个survivor区的放入年老代,将只经历过一个survivor区域的复制到另一个survivor区。
-
年老代
年轻代中可以配置N个survivor区域,当一个对象经历过N个survivor区域后,会被放到年老代中。 -
持久代
存放静态文件,如今Java类、方法等,持久代对垃圾回收没有显著影响;如果需要频繁动态调用一些类,例如反射调用的情况下,可以适当调整持久代的内存大小。
垃圾回收的触发
垃圾回收的触发主要是触发Scavenge GC和full GC。
- Scavenge GC: 当新的对象生成,分配到eden区时,会触发Eden区域的垃圾回收,并且吧尚且存活的对象复制到survivor区域。
- Full GC:老年代、持久代被写满;system gc被显示调用;full gc会对整个年轻代、年老代、持久代进行整理,故会耗时较长。
JVM优化主要优化fullGC的频率。
垃圾回的方式
垃圾回收器在进行垃圾回收时,一般使用以下三种方式:
- 串行回收:单核单线程执行,执行过程中挂起用户进程
- 并行回收:多线程执行,执行过程总挂起用户进程,适用于多核机器,理论上核数越多效率越高
- 并发回收:不挂起用户线程,回收线程和用户线程同时进行
垃圾回收器
名称 | 使用算法 | 备注 |
---|---|---|
串行收集器Serial | 复制算法 | 最古老的收集器,单线程收集,用于新生代 |
并行收集器ParNew | 复制算法 | Serial多线程版本,用于新生代 |
Parallel Seaverage | 复制算法 | 多线程,更加关注吞吐量,自适应调节策略达到控制吞吐量;用于新生代 |
Serial Old | 标记-整理算法 | 单线程,用于年老代 |
Parallel Old | 标记-整理算法 | 多线程,用于年老代 |
CMS | 标记清除算法 | 并发多线程,老年代收集器 |
G1 | 复制算法+标记-整理算法 | 目前最先进的回收器,不区分新生代和年老代 |