内容简介:GC在回收内存时 :在几个线程私有的运行时区域:它们的内存分配和回收
GC在回收内存时 :
- 怎么判断哪些内存需要回收 ?
- 什么时候回收?
在几个线程私有的运行时区域:
- 虚拟机栈
- 程序计数器
- 本地方法栈
它们的内存分配和回收 大多 都具有确定性,随着线程的创建而产生,随着线程的停止而被回收。栈帧中的内存大小基本在类的结构确定下来时就已知。
而在线程共有的 Java堆(Heap)
和 方法区(Class(Method) Area)
这两个区域则不同:
比如,一个接口有不同的实现类(类的信息在方法区中),这几个实现类的内存大小肯定不一,没法在运行前就已知需要多大的内存,只有在运行期间才知道创建的对象的大小。
一,哪些内存需要回收?
在知道哪些内存需要回收之前,我们要知道怎么判断一个对象是否还存活,当它不再存活时,就回收它。
而 引用计数算法
就是用来判断对象是否存活的一个算法。
1,引用计数算法(Reference Counting)
算法描述:给对象添加一个引用计数器,当有一个地方引用了它,计数器+1,当引用失效,计数器-1,在任何时刻,计数器为0时此对象将不能再被使用。
引用计数法在大多数情况下表现都不错,也有被很多公司采用的应用案例。但是在JVM中并没有采用这种算法,原因是: 无法解决对象之间存在相互引用的问题 。
public class Person { Object instance = null; public static void main(String[] args) { Person a = new Person(); Person b = new Person(); a.instance = b; b.instance = a; a = null; b = null;// 正常情况下在这里GC就会把a,b回收掉 } } 复制代码
正常情况下在执行11-12行代码时,JVM的GC会把a,b两个对象回收,但是在引用计数算法的情况下:
a=null b=null
2,可达性分析(Reachability Analysis)算法
在 Java 语言中是通过可达性分析来判断对象是否存活。
算法描述 : 通过一系列的 GC Roots
作为起始点,从这些起始点开始向下搜索,能搜索的到的对象说明其可用,不会被GC回收掉,搜索所走过的路径称为 引用链(Reference Chain)
。相反,如果一个对象没有到达GC Roots的路径,则说明它不可用,被判定为可被GC回收的对象。
如图 : 1区域的对象虽然互相关联,但是它们不可到达GC Roots,所以他们会被回收掉,而2区域的对象与GC Roots之间是有可到达路径的,所以它们不会被回收。
什么是GC Roots ?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类的静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
这些都可作为GC Roots.
3,什么是引用(Reference)
我们在上面的 引用计数算法
和 可达性分析
中,都提到了 对象之间的 引用
关系。
在Java1.2之前,关于 引用
的定义 :
如果 reference
类型的数据存储的数值代表的是另一块内存的起始地址,就说这块内存代表一个引用。
JDK1,2之后,又引入了 强引用
, 软引用
, 弱引用
, 虚引用
,这四个概念,并且这四种表现的引用关系越来越弱。
- 强引用(Strong Reference) :
例:
Object o = new Object(); 复制代码
只要强引用还在,GC永远不会回收掉被引用的对象。
- 软引用(Soft Reference) :
有用,但非必须,在将要发生内存溢出时,会把 软引用
的对象回收掉,如果内存依然不够用,则抛出OOM异常。
- 弱引用(Weak Reference):
非必需对象,只要GC发生了垃圾回收,不管此时内存是否充足, 弱引用
的对象都会被回收掉。
- 虚引用(Phantom Reference):
- 最弱的引用关系
- 无法通过虚引用构造市实例。
- 唯一的作用就是在虚引用关联的对象被GC回收掉时,可以接受到一个信号。
4,如何判断一个对象可回收(已死)?
一个对象仅仅通过上面说的可达性分析看它没有与GC ROOTS关联来判定这个对象是否可被回收是不够的。
一个对象要经过下面一段判断过程来判断它是否要被回收(建议收藏( ^__^ ) 嘻嘻……):
5,方法区的回收
上面我们说的是存在于Java堆中的对象的回收,但其实在方法区还要回收以下东西:
① 回收废弃常量
假如常量池中有一个字符串 "abc" ,但是系统中没有一个String 对象指向它,也就是这个常量没有被引用,
当GC在回收时会回收此字面量。
② 回收废弃的类(无用的类)
- 该类的实例都已被回收,Java堆中不存在任何该类的实例。
- 加载该类的ClassLoader已经被回收。
- 该类的Java.lang.class对象没有被引用(在反射中会被用到这点)。
③ 方法区的回收策略:
GC在回收方法区时会采用一下2种方式:
- 标记-整理
- 标记-清除
二,如何回收?
GC在回收内存时会采用多种垃圾收集算法,这些算法各有优劣。
1.标记-清除(Mark-Sweep)算法
此算法是最基础也是最古老的垃圾回收算法,该算法主要经过2个过程
① 算法描述
- 标记阶段:经过 如何判断一个对象可被回收 所述,对可被回收的对象进行标记。
- 清除阶段:将被标记的对象统一回收。
② 算法缺陷
- 效率问题:此种算法标记和清除的效率都不高。
- 标记清除后产生大量不连续的内存空间碎片。
2.复制(Copying)算法
复制算法针对效率问题进行了优化,它将内存区域划分为2块,每次只使用其中一块。
- 活动区域
- 空闲区域
① 算法描述
如图:
- 回收前 : 内存被划分为左右两侧区域,右侧为空闲区域,暂时不使用它
- 回收时 : 将左侧要被回收的部分(黑色) 回收掉,然后将4个存活对象(淡灰色)移动到右侧的空闲区域,并且做了2件事
- 将移动到空闲区域的存活对象按内存地址进行排列。
- 将存活对象指向的旧地址指向新内存地址。
- 回收后 : 原先的右侧空闲区域变为活动区域,左侧的活动区域变为空闲区域。
左右两侧的区域状态在每一次回收后都来回转换...
② 算法缺陷
- 很显然,这种算法浪费了一般内存。
- 当活动区域的100%的对象都还在活跃,那么在回收时需要将全部的对象复制到右侧的空闲区域,此时的效率就很低。
③ 算法应用
IBM公司经研究表明,Java堆新生代种的对象98%是 '朝生夕死' 的对象,比如临时变量等作用域很少的对象。
所以现在的虚拟机并不会按照 1:1的比例划分两个区域。
现在的JVM虚拟机中,将新生代划分为一块 Eden
区域,和2块较小的 Survivor
区域(from ,to区)。
每次使用Eden区和1块Survivor区(from区)最为活动区域,当发生内存回收时,将这2块内存中的存活对象复制到另一块Survivor区(to区)。
在 HotSpot
虚拟机中,Eden区和Survivor的划分是: 8: 1,这样,活动区域占新生代的 (8+1)/10 *100% = 90%,只有10%的内存浪费。
老年代: 当将存活对象从活动区域(Eden,from) 复制到 to区时,如果to区不够用,则将剩下的存活对象放到 老年代。
(完~)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- JVM(三)对象的生死判定和算法详解
- 网络安全等级保护2.0等级测评结论判定方法
- 正则表达式判定保留2位小数的实现代码
- 十一贝:航延险智能判定,公平消费环境惠及消费者
- 云服务商封杀AI客户:因判定其Python脚本是恶意脚本
- 垃圾回收算法(7)-分代回收算法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First Python
Paul Barry / O'Reilly Media / 2010-11-30 / USD 49.99
Are you keen to add Python to your programming skills? Learn quickly and have some fun at the same time with Head First Python. This book takes you beyond typical how-to manuals with engaging images, ......一起来看看 《Head First Python》 这本书的介绍吧!