垃圾回收

垃圾回收

标签(空格分隔): Java算法


那些对象须要回收

JVM内存结构主要包括: 方法区, 堆区, 程序计数器, 本地方法区, 虚拟机栈. 其中的程序计数器, 本地方法区, 虚拟机栈这三个区域是的生命周期随线程生灭, 因此不须要过多考虑这方面的GC问题.数据结构


引用

在JDK1.2以后, Java对引用的概念进行了补充, 整体分为四类: 强引用, 软引用, 弱引用, 虚引用. 这四种引用强度逐渐减弱.多线程

  • 强引用 : 指在代码中广泛存在的, 相似 Object object = new Object();这类的引用, 只有强引用还存在, GC就永远不会收集被引用的对象.
  • 软引用 : 指一些还有用但而且非必须的对象. 直到内存空间不够时(抛出OutofMemoryError以前), 才会被垃圾回收. 采用SoftReference类来实现软引用.
  • 弱引用 : 用来描述非必须对象. 当垃圾收集器工做时就会回收掉此类对象. 采用WeakReference类来实现弱引用.
  • 虚引用 : 一个对象是否有虚引用的存在, 彻底不会对其生存空间构成影响, 惟一目的就是能在这个对象被回收的时候收到一个系统通知, 采用PhantomRenference类实现.

如何判断对象已死


1. 引用计数法

为每个对象的的数据结构添加一个引用计数器, 用于统计指向该对象的引用的数量, 每当多一个引用的时候, 其引用计数器就加一, 当引用再也不指向该地址以后计数器减一. 一旦计数器的值为0,则表示没有引用指向该对象, 则对象已经死亡 ( <<寻梦环游记>> 说的: 人真正的死亡是全部活着的人的都忘了你的时候).布局

优势:线程

  1. 效率很高.

缺点:3d

  1. 每一个对象都须要在其原有的数据结构之上添加一个引用计数器的属性.
  2. 循环引用的话就永远不会被标记为死亡.( 对象A引用了对象B, 对象B也引用了对象A )

2. 可达性分析.

目前JVM的主流垃圾回收器采起的可达性分析算法, 这个算法的实质在于将一系列GC Roots做为初始的存活对象的集合(Live Set), 而后从该集合出发, 探索全部能过被集合直接或间接引用的对象, 而且将其加入集合之中, 这个过程就是标记过程. 最终, 未被探索到的对象即是死亡, 是能够回收的.代理

什么是GC Roots, 其实就是由堆外指向堆内的引用.code

  1. JMM虚拟机栈 栈帧中的局部变量.
  2. JMM方法区中的静态属性, 常量引用对象.
  3. JMM本地方法区中的, JNI中引用的对象.

可达性分析, 能够解决循环引用问题, 可是自身也存在一些问题, 好比说在多线程的状况下, 其余线程可能会更新已经访问过的对象的引用, 形成漏删. 解决方案是 进行两次可达性分析, 若是两次某对象都被标记则进行删除.对象


如何删除无效对象.


1. 标记清除算法

由名可得: 标记->清除. 获得须要清除的对象以后就直接进行清除.blog

优势: 速度快
缺点: 屡次GC以后, 形成大量的碎片空间. 对于须要连续存储的较大对象没法存储, OutofMemoryError


2. 标记整理算法

由名可得: 标记->清除->移动整理. 对标记清除算法的一次改进, 可是由于移动操做, 因此时间成本较高.

优势: 没有内存碎片.
缺点: 时间成本较高.


3. 复制算法.

将可用的内存按容量分为大小两块, 每次只是用其中一块, 当这一块的内存用完了, 就将还存活着的对象复制到另外一块内存上, 而后再把已使用的内存空间清理掉.

此处输入图片的描述

每次当内存分配的时候空间不够的时候, 都进行复制算法进行内存整理.

优势: 实现简单, 效率高. 解决了标记清除算法致使的内存碎片问题.
缺点: 代价太大, 将内存缩小了一半. 效率随对象的存活率升高而下降.

3.1 HotSpot虚拟机的改良算法

  1. 弱代理论
  • 分代垃圾收集基于弱代理论, 具体描述以下:
  • 大多分配了内存的对象并不会存活太长时间, 在处于年轻时代的时候就会死掉.
  • 不多有对象会从老年代变为年轻代.
  • 其中IBM研究代表: 新生代中的98%的对象都是 朝生夕死 ; 因此不须要按照1:1比例来划份内存.
  1. Hotspot虚拟机新生代内存布局以及算法.
  • 新生代内存分配一块较大的Eden和两块较小的Survivor空间.
  • 每次使用Eden和其中Survivor空间.
  • 回收时将EdenSurvivor空间中存活的对象一次性的复制到另外一块Survivor空间上.
  • 最后清理掉Eden和使用过的Survivor空间.

Hotspot默认EdenSurvivor的大小比例是8:1 .

分配担保:
若是另外一块Survivor空间没有足够的内存来存放上一次新生代收集下来的存活对象, 那么这些对象则直接经过担保机制进入老年代.


4. 分代收集算法

当前商业虚拟机的垃圾收集器都是采用分代收集算法, 根据对象存活周期的不一样将内存划分为几块. 通常把Java堆分为新生代, 老年代. JVM根据各个年代的特色采用不一样的手机算法.

  • 新生代中, 每次进行垃圾回收都会发现大量对象死去, 只有少许存活, 所以比较适合复制算法. 只须要付出少许的存活对象的复制成本就能够完成收集.
  • 老年代中, 由于对象的存活率比较高, 没有额外的空间进行分配担保, 因此比较适合标记-清理, 标记-整理算法来进行回收.

此处输入图片的描述

Java堆是JVM所管理的内存的最大的一块, 也是GC主要的工做区. 其主要分为两个区 年轻代老年代, 其中年轻代又分为EdenSurvivor区, 其中Survivor区又分为FROMTo两个区. 可能这个时候你们又会有疑问, 为何须要Survivor区, 为何Survivor还要分为两个区?

Eden区 : IBM表示有98%的对象是朝生夕死, 因此针对这一现状, 大多数状况下, 对象会在新生代Eden区中进行分配, 当Eden区没有足够的空间进行分配的时候, 虚拟机会发起一次GC. 经过此次GC以后,Eden会被清空, Eden区中绝大部分的对象会被回收, 而那些无需回收的存货对象, 将会进到SurvivorFROM区(若FROM不够, 则直接进入Old区).

Survivor区: 至关因而Eden区和Old区的一个缓冲, 相似于咱们交通中的黄灯. Survivor又分为两个区, 一个为From区, 一个是To区. 每次执行Minor GC 会将Eden区和FROM存活的对象放到SurvivorTo区(若是To则直接进入Old区).

不是新生代到老年代么, 直接中EdenOld很差了么,为何要这么复杂. 若是没有Survivor区, Eden区每一次GC, 存活的对象就会被送到老年代, 老年代很快就会被填满, 而虽然有不少对象虽然一次没有被消灭掉,可是也存活不了太屡次, 这个时候将其移入Old区会很快的将其填满.

因此Survivor的存在乎义就是减小被送到老年代的对象, 进而减小GC的发生. Survivor的筛选保证, 只有经历16此的GC还能再新生代存活的对象,才会被送到老年代.

Old区占据着2/3的堆内存空间,只有在Marjor GC的时候才会进行清理, 每次GC都会出发stop-the-world. 内存越大, SWT的时间也越长, 因此内存也不只仅是越大越好, 因为复制算法的对象存活率较高的老年代会进行不少次的复制操做, 效率很低, 因此老年代这里采用的是 标记整理算法.


除了上述所说, 在内存担保机制的状况下, 没法安置的对象也会直接进入老年代, 如下几种状况也会进入老年代.

  • 大对象: 指须要大量连续内存空间的对象, 这部分对象不管是不是"朝生夕死", 都会直接进入到老年代, 这样作主要是为了不在Eden区以及两个Survivor区之间发生大量的内存复制, 当你的系统有很是多的"朝生夕死"的大对象的时候, 就值得注意了.
  • 长期存活对象: 虚拟机给每一个对象定义了一个对象年龄(Age)计数器. 正常状况下的对象会不断的在SurvivorFROMTo之间进行移动, 对象在每经历一次Minor GC, 年龄就会自增1, 当年龄增长到15岁的时候, 就会被移入老年代. 这里的15是能够进行自定义的.
  • 动态对象年龄: 虚拟机并不重视对象的年龄必须大于15岁, 才会放入老年区, 若是Survivor空间中相同年龄全部对象的总和大于Survivor空间的通常, 年龄大于等于该年龄的对象就能够直接进入老年区.
相关文章
相关标签/搜索