摘要:引言垃圾收集技术并不是语言首创的,年诞生于的是第一门真正使用内存动态分配和垃圾收集技术的语言。垃圾收集器所关注的就是这部分内存。收集器是收集器的多线程版,它是第一款并发收集器。经常出现大对象会导致多次出发垃圾收集。
引言
垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。垃圾收集技术需要考虑的三个问题是:
哪些内存需要回收?
什么时候回收?
如何回收?
http://segmentfault.com/a/119... 中讲到java内存运行时区域的分布,其中程序计数器,虚拟机栈,本地方法区都是随着线程而生,随线程而灭,所以这几个区域就不需要过多考虑回收问题。但是堆和方法区就不一样了,只有在程序运行期间我们才知道会创建哪些对象,这部分内存的分配和回收都是动态的。垃圾收集器所关注的就是这部分内存。
一 对象死亡判据垃圾收集器在对一个对象回收之前,首先要判断对象在程序中是否还有使用的可能性,充要条件就是没有被程序可访问的引用再指向这个对象实例。最简单的办法就是给对象实例添加中添加一个引用计数器,每当有一个引用指向它时,计数器就加一,当引用失效时,计数器就减一,如果计数器值为0则说明没有引用指向它,可以进行回收。但是这个方法中计数器为0并不是一个必要条件,例如,生成两个对象实例,每个对象实例的属性都指向对方,那么这个两个对象实例分别最少有一个引用。
java采用的是可达性分析算法,即找一部分对象作为"GC Roots"节点,从这些节点开始向下搜索,当某个对象到"GC Roots"节点没有可达路径时,说明此对象是不可用的。在java中作为"GC Roots"的节点包括:
虚拟机栈中引用的对象,
方法区静态属性引用的对象,
方法区常量引用的对象,
本地方法区中本地调用所引用的对象。
引用扩充如果reference类型的数据中存储的数值是另一块内存的起始地址,那么这块内存就代表着一个引用。一个对象在这种状态下,只能有被引用和没有被引用两种状态。java对引用概念进行了扩充,将引用分为强引用(new),软引用(softReference),弱引用(WeakReference),虚引用(PhantomReference)。如果强引用存在,则垃圾收集器不会回收该对象。如果系统即将发生内存溢出异常,那么垃圾回收集器则会回收软引用对象。弱引用对象只能存活到下一次垃圾收集之前。虚引用对象不会对其生存时间构成任何影响。
对象的自我救赎在垃圾收集器发现某一个对象到"GC Roots"路径不可达时,先会判断该对象是否覆盖finalize()方法,或是否执行过finalize()方法。如果覆盖了且没有执行过该方法,则会将该对象放到低优先级的Finalizer线程队列中去执行finalize()方法,如果在finalize()方法中该对象又被引用,则会有一次逃脱被回收的命运。
方法区的回收方法区中主要回收废弃的常量和无用的类。对于常量,如果没有引用指向常量,则该常量会被回收。对于类的回收则麻烦许多,首先要判断该类是无用的类,无用的类要满足三个条件:
所有类的实例被回收。
加载该类的ClassLoader已经被回收。
Class没有被引用,不会通过反射访问该类的方法。
二 垃圾回收算法 标记-清除算法(Mark-Sweep)该算法分为两个阶段:首先标记处要回收的对象,标记完成后统一回收所有被标记的对象。
存在的问题:
标记和清除效率都不高
标记清除后会产生大量内存碎片,分配大对象时可能触发另一次垃圾收集。
复制算法(Copying)该算法将内存分为两个等大小的区域,每次只使用一个区域。当一个区域快用完了,就将这个区域中存活的对象复制到另一个区域。
优点是避免了内存碎片的产生,缺点是浪费内存空间。
有公司研究表明,新生代的对象98%都是朝生暮死,所以虚拟机把新生代内存划分为一个较大的Eden空间和两个较小的Survivor空间。每次只是用Eden空间和一个Survior空间,当进行复制清理时,将Survivor空间和Eden空间中存活的对象复制到另一块Survivor空间。当Survivor空间不够用时,就会依赖老年代进行分配担保。
标记-整理算法(Mark-Compact)针对老年代对象存活率高的情况,复制算法明显不合适,于是采用标记整理算法,标记和标记清除算法相同,二后边的整理则是让所有存活的对象都向一端移动,然后清理掉边界外的内存。
分代收集当前虚拟机都采用分代收集,分代的依据是对象的存活周期。一般新生代存活率低,采用复制算法。老年代存活率高采用标记整理或标记清除。
一般来讲,新生代空间会小很多,具体比例一般要看应用场景。而默认的大小一般是老年代的1/4到1/3。
三 垃圾收集器
由于虚拟机采用了分代收集,所以针对不同代收集器也不同。上图是HotSpot虚拟机的垃圾收集器,连线表示可以协同工作。
Serial收集器,复制算法,它是一个单线程的收集器,并且在进行收集时会暂停其他线程,它默认是client模式下的新生代收集器。
ParNew收集器是Serial收集器的多线程版,它是第一款并发收集器。
Parallel Scavenge收集器可以精确控制吞吐量(用户代码运行时间/(用户代码时间+垃圾收集时间))
SerialOld收集器是serial收集器的老年版,采用标记整理算法,同样是单线程收集器。
ParallelOld是ParallelScavenge收集器的老年版,使用多线程和标记整理算法。
CMS收集器是以最短回收停顿时间为目标的收集器,采用标记清除算法,在重视响应速度的系统中得以应用。但是缺点是对CPU资源敏感,无法处理浮动垃圾,易产生内存碎片。
G1收集器是最新推出的收集器,可应用在JDK1.7u4及以上版本。它将内存分为多个Region,新生代和老年代分别包含多个Region。G1跟踪各个Region,判断垃圾价值大小,优先回收价值最大的Region。
安全点安全点的概念是指当进行GC时,应当让工作线程停止,这时会更容易对对象是否存活进行判断。而停止线程应当在安全的时刻,所以会有安全点的概念。
暂停线程有两种方式,第一种是强制暂停,如果某些线程没有到达安全点则再让他运行到安全点,这叫做抢先式中断。第二种是在编译过程中在安全点加入一个条件判断判断0x160100内存页是否可读,如果不可读则会暂停,这叫做主动式中断。
四 内存分配与回收策略
对象的分配,就是在堆上分配,对象主要分配在新生代的Eden区域中,如果启动了本地线程分配缓冲,则按线程优先在TLAB中分配。少数情况也有可能直接分配到老年代。
对象在Eden区域分配时,当Eden区域没有足够空间,虚拟机会发起一次新生代垃圾收集。
如果对象需要大量连续内存空间,例如String类型和数组。大对象对于虚拟机内存分配来说是一个坏消息,朝生暮死的大对象是要命的坏消息。经常出现大对象会导致多次出发垃圾收集。对于这类对象,可以设置参数将大对象直接存入老年代。
每一个对象都有一个年龄计数器,当对象在Eden区域出生,每经过一次GC,并且存入Survivor,计数器加一。当年龄增加到一定程度(默认15),则会被存入老年代。同时,如果Survivor空间中相同年龄对象占空间超过50%,则也会直接进入老年代。
总结垃圾收集算法:复制算法,标记-清除算法,标记-清理算法。
垃圾收集器特点:新生代用复制,老年代用标记清理,CMS用标记清除。
Eden空间大小和Survivor空间大小默认比率为8:1,即新生代10%的空间用来存放复制后的对象。
更多文章:http://blog.gavinzh.com
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64403.html
摘要:目录往期博客课堂篇初识常量池简单理解字符串常量池静态常量池大整型常量池为什么要了解垃圾收集和内存分配如何判断对象已死引用计数算法可达性分析算法之后引用的扩充回收方法区垃圾收集算法分代收集理论标记清除标记复制标记整理对象分 ...
摘要:虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。虚拟机总共运行了分钟,其中垃圾收集花掉分钟,那么吞吐量就是。收集器线程所占用的数量为。 本文主要从GC(垃圾回收)的角度试着对jvm中的内存分配策略与相应的垃圾收集器做一个介绍。 注:还是老规矩,本着能画图就不BB原则,尽量将各知识点通过思维导图或者其他模型图的方式进行说明。文字仅记录额外的思考与心得,以及其他特殊情况 内存...
摘要:当两个对象相互引用时,这两个对象就不会被回收引用计数算法不被主流虚拟机采用,主要原因是它很难解决对象之间相互循环引用的问题。 垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 《深入理解Java虚拟机》第三章 ,为了整理思路,简单记录一下,方便后期查阅。 3.2 对象已死吗 在垃圾收集器进行回收前,第一件事就是确定这些对象哪些还存活,哪些已经死去。 3.2.1 引用...
摘要:堆和方法区只有在程序运行时才能确定内存的使用情况,垃圾回收器所关注的主要就是这部分内存。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整比率参数以提供最合适的停顿时间或最大的吞吐量。 Tip:内容为对《深入理解Java虚拟机》(周志明 著)第三章内容的总结和笔记。这是第一次拜读时读到的一些重点,做个分享,也为后面再次阅读和实践做保障。 3.1 概述 程序计数器、虚拟机栈、本地...
摘要:第章内存区域与内存溢出异常运行时数据区域虚拟机在执行程序的过程中会把它所管理的内存划分为若干个不同的数据区域。即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 第2章 Java内存区域与内存溢出异常 2.2 运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。根据《Java虚拟机规范(Java SE 7版)...
阅读 3280·2023-04-26 02:42
阅读 790·2021-10-09 09:41
阅读 3189·2021-09-06 15:02
阅读 699·2019-08-26 10:45
阅读 480·2019-08-23 15:53
阅读 733·2019-08-22 18:10
阅读 550·2019-08-22 18:01
阅读 3516·2019-08-22 17:34