垃圾回收算法手册
‘壹’ 老鸟对于刚入行的程序猿有什么忠告
我认为首先在IT这行应该是很公平的也很公正的,它是靠着技术来吃饭的,就算你用在厚的关系你要没技术在这,那肯定是不是的,当然老板出外哦。所以入这行不管你以前的背景多么的光鲜亮丽或拿不出手,只要能够去学习,和谦卑的状态去请教。
知识要牢固
我认为不管在那一行,毕竟你是新人首先就是姿态放低,心态放平,即使同事水平没你高,也别说出来。写你自己的代码就好。操作服务器要谨慎+谨慎,尤其是线上执行sql的时候。热爱编程,周末有时间就写些自己感兴趣的大小项目。
总结
学习这俩字应该是最好的,不管是扎实的基础知识,和人事的处理关系等等的一些方面吧,总之是一定要抱着学习的态度的。
‘贰’ 《垃圾回收的算法与实现》pdf下载在线阅读,求百度网盘云资源
《垃圾回收的算法与实现》(中村成洋)电子书网盘下载免费在线阅读
资源链接:
链接: https://pan..com/s/1F0QUPK8Y3isGgD28oxt9Gw
书名:垃圾回收的算法与实现
作者:中村成洋
译者:丁灵
豆瓣评分:8.2
出版社:人民邮电出版社
出版年份:2016-7-1
页数:456
内容简介:
★ Ruby之父Matz作推荐语:上古传承的魔法,彻底揭开垃圾回收的秘密!
★ 日本天才程序员兼Lisp黑客竹内郁雄审校
本书前半介绍基本GC算法,包括标记-清除GC、引用计数、复制算法的GC、串行GC的算法、并发GC的算法等。后半介绍V8、Rubinius、Dalvik、CPython等几种具体GC的实现。本书适合各领域程序员阅读。
‘叁’ jvm垃圾回收算法有哪些
1.标记–清除算法
执行步骤:
标记:遍历内存区域,对需要回收的对象打上标记。
清除:再次遍历内存,对已经标记过的内存进行回收。
2.复制算法
将内存划分为等大的两块,每次只使用其中的一块。当一块用完了,触发GC时,将该块中存活的对象复制到另一块区域,然后一次性清理掉这块没有用的内存。下次触发GC时将那块中存活的的又复制到这块,然后抹掉那块,循环往复。
3. 标记–整理算法
因为前面的复制算法当对象的存活率比较高时,这样一直复制过来,复制过去,没啥意义,且浪费时间。所以针对老年代提出了“标记整理”算法。
执行步骤:
标记:对需要回收的进行标记
整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。
4. 分代收集算法
当前大多商用虚拟机都采用这种分代收集算法,这个算法并没有新的内容,只是根据对象的存活的时间的长短,将内存分为了新生代和老年代,这样就可以针对不同的区域,采取对应的算法。如:
新生代,每次都有大量对象死亡,有老年代作为内存担保,采取复制算法。
老年代,对象存活时间长,采用标记整理,或者标记清理算法都可。
‘肆’ JVM有哪些垃圾回收算法
标记-清除,标记-复制,标记-整理
‘伍’ 各种编程语言的实现都采用了哪些垃圾回收算法
java语言:
. 采用Reference Counting的垃圾回收器
对于采用Reference Counting的垃圾回收器,系统为堆上每一个对象都维护一个计数器,当一个对象被创建并且别引用时,这个计数就被置为1。当有新的变量引用该对象,计数器进行自加运算。当一个引用超出作用范围或者被赋予新值的时候,计数器进行自减运算。引用计数为0的对象,会被作为垃圾回收。当一个对象被回收,该对象所引用的对象的引用计数都会相应减少,因而,一个对象的回收有时会引起其它对象的回收。
Reference Counting方式的垃圾回收器,好处在于可以在很短的时间内运行,不会长时间的中断普通的程序运行,因而在RealTime的系统中应用较为普遍。 Reference Counting方式的垃圾回收器,问题在于无法识别循环引用,比如父类对象还有子类引用的情况,即便父类和子类都已经不再能被访问到(unreachable),引用计数也把它们清除。另外一个问题是引用计数器的加减运算会增加系统的计算开销。 2. 采用Tracing的垃圾回收器
采用Tracing的垃圾回收器,遍历由根节点(root nodes)出发的引用关系图。在遍历过程中遇到的对象,就被标记为活动。标记既可以是对应对象中的某一个标志,也可以是独立的位图中的标志。当遍历完成以后,那些没有被标记的对象,就被作为垃圾回收了。最基本Tracing算法是"Mark and Sweep" 垃圾回收器的另外一个责任是清除堆上的碎片(Fragmentation)。对于Mark and Sweep的垃圾回收器通常有两种实现方法来减少堆上的碎片: 压缩(Compacting)和拷贝(Copying)
在编程语言Python中,使用也是引用计数算法。
节点拷贝算法
节点拷贝算法是把整个堆分成两个半区(From,To), GC的过程其实就是把存活对象从一个半区From拷贝到另外一个半区To的过程,而在下一次回收时,两个半区再互换角色。在移动结束后,再更新对象的指针引用。
‘陆’ 5、垃圾回收机制
JVM的垃圾回收机制主要涉及三个方面的问题:
1.JVM有哪些垃圾回收算法?各自有什么优势?
2.CMS垃圾回收器是如何工作的?有哪些阶段?
3.服务卡顿的元兇到底是什么?
Java不用程序来管理内存的回收,但这些内存是如何回收的?
其实,JVM有专门的线程在做这件事情。当内容空间达到一定条件时,会自动触发,这个过程就叫GC,负责GC的组件被称为垃圾回收器。JVM规范没有规定垃圾回收器怎么实现,它只需要保证不要把正在使用的对象回收掉就可以。在现在的服务器环境中,经常被使用的垃圾回收器有CMS和G1,但JVM还有其它几个常见的垃圾回收器。
GC的过程是先找到活跃的对象,然后把其他不活跃的对象判定为垃圾,然后删除,所以GC只与活跃的对象有关,和堆的大小无关。
接下来学习下分代垃圾回收的内存划分和GC过程,再有就是常见的垃圾回收器。
这篇比较重要,因为几乎所有的垃圾回收器都是在这些基本思想上演化出来的。
GC的第一步就是找出活跃的对象,根据GC Roots遍历所有的可达对象,这个过程就叫作标记。
如上图所示,圆圈代表对象,绿色的代表GC Roots,红色的代表可以追溯到的对象,标记后,有多个灰色的圆圈,代表都是可被回收的对象。
清除阶段就是把未被标记的对象回收掉。
这种方式有一个明显的问题,会产生碎片空间。
比如申请了1k、2k、3k、4k、5k的内存
由于某些原因,2k和4k的内存不再使用,交给垃圾回收器回收。
解决碎片问题,就需要进行内存整理。
有一个思路就是提送一个对等的内存空间,将存活的对象复制过去,然后清除员内存空间。
在程序设计时,一般遇到扩缩容或者碎片整理问题时,复制算法都是非常有效的。比如:HashMap的扩容使用的是同样的思路,Redis的rehash也是如此。
整个过程如下图
这种方式看似完美,解决了碎片问题,但是弊端也非常明显,它浪费了一半的内存空间来做这个事情,如果原本资源就有限,这就是一种无法容忍的浪费。
不用分配一个对等的空间也是可以完成内存的整理工作。
可以把内存想象成一个非常大的数组,根据随机的index删除了一些数据,那么对数组的清理不需要另外一个数组来进行支持的,使用程序就可以。
主要思路是移动所有的存活对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部收回。
对象的引用关系一般是非常复杂的,从效率上来说,一般整理算法是要低于复制算法的。
JVM的垃圾回收器,都是对以上几种朴素算法的结合使用,简单看一下它们的特点:
效率一般,缺点是回造成内存碎片的问题。
复制算法是所有算法里面效率最高的,缺点是造成一定的空间浪费。
效率比前两者要差,但没有空间浪费,也消除了内存碎片问题。
所以没有最优的算法,只有最合适的算法。
JVM是计算节点,而不是存储节点。最理想的情况就是对象使用完成之后,它的生命周期立马就结束了,而那些被频繁访问的资源,我们希望它能够常驻在内存里。
对象大致可以分为两类:
1.大部分对象的生命周期都很短
2.其他对象则很可能会存活很长时间
现在的垃圾回收器都会在物理上或者逻辑上,把这两类对象进行分区。我们把死的快的对象所占的区域叫年轻代(Young Generation)。把其他活的长的对象所占的区域叫作老年代(Old Generation),老年代在有时候会叫作Tenured Generation。
年轻代使用的垃圾回收算法是复制算法,因为年轻代发生GC后,会有非常少的对象存活,复制这部分对象是非常高效的
年轻代的内部分区
如图所示,年轻代分为:一个伊甸园空间(Eden),两个幸存者空间(Survivor)。
当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(Minor GC),具体过程如下
1.在Eden区执行了第一次GC之后,存活的对象会被移动到其中一个Suvivor分区(from);
2.Eden区再次GC,这是会采用复制算法,将Eden和from区一起清理,存活的对象会被复制到to区;接下来只需要清空from区就可以了
在整个过程中总会有一个Survivor分区是空置的。Eden、from、to的默认比例是8:1:1,所以只会造成10%的空间浪费。
这个比例是由参数-XX:SurvivorRatio进行配置的(默认为8)。
补充下不常提到的TLAB。TLAB全称是Thread Local Allocation Buffer,JVM默认给每个线程开辟一个buffer区域,用来加速对象分配。这个buffer就放在Eden区中。
这个道理和Java语言中的ThreadLocal类似,避免了对公共区的操作,以及一些锁竞争。
老年代一般使用"标记-清除"、"标记-整理"算法。因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,不如采取就地收集的方式。
对象进入老年代的途径分类
如果对象够老,会通过"提升"进入老年代。关于对象老不老,是通过它的年龄来判断的。每发生一次Minor GC,存活下来的对象年龄都会加1,直到达到一定的阀值,就会提升到老年代,
这些对象如果变的不可达,直到老年代发生GC的时候才会被清理掉。
这个阀值可以通过参数 -XX:+MaxTenuringThreshold进行配置,最大值是15,因为它是用4bit存储的(所以把这个值调的很大的文章,是没有什么根据的)。
每次存活的对象,都会放入其中一个幸存区,这个区域默认比例是10%,但无法保证每次存活的对象都小于10%,当Survivor空间不够,就需要依赖其它内存(老年代)进行分配担保。这个时候,对象也会直接在老年代上分配。
超出某个大小的对象直接在老年代分配,通过参数设置-XX:PretenureSizeThreshold进行配置的,默认为0,默认全部在Eden区进行分配。
有的垃圾回收算法,并不要求age必须达到15才能晋升到老年代,它会使用一些动态的计算方法。比如,如果幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或者等于age的对象将会直接进入老年代。
这些动态判定一半不受外部控制
对象的引用关系时一个巨大的网状,有的对象在Eden区,有的可能在老年代,那么这种跨代的引用是如何处理的呢?由于Minor GC是单独发生的,如果一个老年代的对象引用了它,如何确保能够让年轻代的对象存活呢?
对于是、否的判断,我们通常都会用到Bitmap(位图)和布隆过滤器来加快搜索的速度,需要另外再学习下(如果不知道这两个概念的话)
JVM也是用了类似的方法。其实,老年代是被分成众多的卡页(Card Page)的(一般数量是2的次幂)
卡表(Card Table)就是用于标记卡页状态的一个集合,每个卡表对应一个卡页。
如果年轻代有对象分配,而且老年代有对象指向这个新对象,那么这个老年代对象所对应内存的卡页就会被标识为dirty,卡表只需要非常小的存储空间就可以保留这些状态,垃圾回收时,就可以先读这个卡表,进行快速的判断。
接下来学习HotSpot的几个垃圾回收器,每种回收器都有各自的特点。在平常的GC优化时,一定要清楚现在用的是那种垃圾回收器。
下图包含了年轻代和老年代的划分,方便接下来的学习参考
处理GC的只有一条线程,并且在垃圾回收的过程中暂停一切用户线程。
这是最简单的垃圾回收器,虽然简单,但十分高效,通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。
ParNew是Serial的多线程版本,由多条GC线程并行地进行垃圾清理。清理过程依然要停止用户线程。追求低停顿时间,与Serial唯一区别就是使用了多线程进行垃圾回收,在多CPU环境下性能比Serial会有一定程度的提升;但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial。
另一个多线程版本的垃圾回收器。但与ParNew是有区别的
1.Parallel Scavenge:追求CPU吞吐量,能够在较短时间内完成指定任务,适合没有交互的后台计算,弱交互强计算。
2.ParNew:追求降低用户停顿时间,适合交互式应用,强交互弱计算。
与年轻代的Serial垃圾回收器对应,都是单线程版本,同样适合客户端使用。
年轻代Serial,使用复制算法。
老年代的Old Serial,使用标记-整理算法。
Parallel Old回收器是Parallel Scavenge 的老年代版本,追求CPU吞吐量。
CMS(Concurrent Mark Sweep)回收器是以获取最短GC停顿时间为目标的收集器,它在垃圾回收时使得用户线程和GC线程能够并发执行,因此在垃圾回收过程中用户也不会感到明显的卡顿。
长期看来,CMS垃圾回收器,是要被G1等垃圾回收器替换掉的,在Java8之后,使用它将会抛出一个警告!
除了上面几个垃圾回收器,我们还有G1、ZGC等更加高级的垃圾回收器,它们都有专门的配置参数来使其生效。
通过-XX:PrintCommandLineFlags参数,可以查看当前Java版本默认使用的垃圾回收器。在Java13中,默认的回收器就是G1。
以下是一些配置参数:
1.-XX:+UseSerialGC 年轻代和年老代回收器
2.-XX:+UseParNewGC 年轻代使用ParNew,老年代使用Serial Old。
3.-XX:+UseParallelOldGC 年轻代和老年代哦都市用并行回收器。
4.-XX:+UseConcMarkSweepGC 表示年轻代使用ParNew,老年代使用CMS。
5.-XX:+UseG1GC 使用G1垃圾回收器
6.-XX:+UseZGC 使用ZGC垃圾回收器
这些垃圾回收器的关系还是比较复杂的,请看下图
目前Java8还是主流使用版本,从Java8升级到高版本的Java体系是有一定成本的,所以CMS垃圾回收器还会持续一段时间
抛个问题,如果在垃圾回收的时候,又有新的对象进入怎么办?
为了保住程序不乱套,最好的办法就是暂停用户的一切线程,也就是在这段时间,是不能new对象的,只能等待,表象是在JVM上就是短暂的卡顿,什么都干不了,这个现象叫作Stop The World。
标记阶段,大多数是要STW的。如果不暂停用户进程,在标记对象的时候,有可能有其它用户线程会产生一些新的对象和引用,造成混乱。
现在的垃圾回收器,都会尽量去减少这个过程。但即使最先进的ZGC回收器,也会有短暂的STW过程。我们要做的就是在现有基础设施上,尽量减少GC停顿。
举例说明下
某个高并发服务的峰值流量是10万次/秒,后面有10台负载均衡的机器,那么每台机器平均下来需要1w/s。假如某台机器在这段时间内发生了STW,持续了一秒,那么至少需要10ms就可以返回的1万个请求,需要至少等待1秒。
在用户那里的表现就是系统发生了卡顿。如果我们的GC非常的频繁。这种卡顿就会特别的明显,严重影响用户体验。
虽然说Java为我们提供了非常棒的自动内存管理机制,但也不能滥用,因为它是有STW硬伤的。
介绍了堆的具体分区,年轻代和老年代。介绍了多个常用的垃圾回收器,不同的垃圾回收器有不同的特点。各种垃圾回收器都是为了解决头疼的STW问题,让GC时间更短,停顿更短,吞吐量更大。
接触了很多名词,总结如下
1.Mark
2.Sweep
3.Copy
4.Compact
1.Young generation
2.Survivor
3.Eden
4.Old Generation |Tenured Generation
5.GC
--1.Minor GC
--2.Major GC
1.weak generational hypothesis
2.分配担保
3.提升
4.卡片标记
5.STW
‘柒’ 《垃圾回收的算法与实现》pdf下载在线阅读全文,求百度网盘云资源
《垃圾回收的算法与实现》网络网盘pdf最新全集下载:
链接: https://pan..com/s/1bFL1ICR6xlb3au_B5sBhcQ
简介: 本书分为“算法篇”和“实现篇”两大部分。算法篇介绍了标记-清除算法、引用计数法、复制算法、标记-压缩算法、保守式GC、分代垃圾回收、增量式垃圾回收、RCImmix算法等几种重要的算法;实现篇介绍了垃圾回收在Python、DalvikVM、Rubinius、V8等几种语言处理程序中的具体实现。
‘捌’ java垃圾是怎么回收的,回收算法
Java ,C#语言与C/C++语言一个很大的区别是java与C#具有自动垃圾回收机制。C++程序员经常需要绞尽脑汁的分析哪里出现了内存泄漏。而在java,C#中,虽然有时也会出现内存泄漏,但大部分情况下程序员不需要考虑对象或者数据何时需要被销毁。因此程序员不会因为错误的释放内存而导致程序崩溃。垃圾回收的缺点是加大了程序的负担,有可能影响程序的性能。
1.垃圾收集器的主要功能有
(1) 定期发现那些对象不再被引用,并把这些对象占据的堆空间释放出来。
(2) 类似于操作系统的内存管理,垃圾收集器还需要处理由于对象动态生成与销毁产生的堆碎块,以便更有效的利用虚拟机内存。
2.区分活动对象与垃圾的算法
(1)引用计数法
堆中每一个对象都有一个引用计数。当新创建一个对象,或者有变量被赋值为这个对象的引用,则这个对象的引用计数加1;当一个对象的引用超过生存期或者被设置一个新的值时,这个对象的引用计数减1。当对象的引用计数变为0时,就可以被当作垃圾收集。
这种方法的好处是垃圾收集较快,适用于实时环境。缺点是这种方法无法监测出循环引用。例如对象A引用对象B,对象B也引用对象A,则这两个对象可能无法被垃圾收集器收集。因此这种方法是垃圾收集的早期策略,现在很少使用。
(2)跟踪法
这种方法把每个对象看作图中一个节点,对象之间的引用关系为图中各节点的邻接关系。垃圾收集器从一个或数个根结点遍历对象图,如果有些对象节点永远无法到达,则这个对象可以被当作垃圾回收。
容易发现,这种方法可以检测出循环引用,避免了引用计数法的缺点,较为常用。
3.常用垃圾回收机制
(1)标记-清除收集器
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。
(2)标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
(3)复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,虚拟机生成的新对象则放在另一半空间中。垃圾回收器运行时,它把可到达对象复制到另一半空间,没有被复制的的对象都是不可达对象,可以被回收。这种方法适用于短生存期的对象,持续复制长生存期的对象由于多次拷贝,导致效率降低。缺点是只有一半的虚拟机空间得到使用。
(4)增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
(5)分代收集器
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。虚拟机生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。这样可以减少复制对象的时间。
(6)并发收集器
并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。
(7)并行收集器
并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多CPU机器上使用多线程技术可以显着的提高java应用程序的可扩展性。
(8)自适应收集器
根据程序运行状况以及堆的使用状况,自动选一种合适的垃圾回收算法。这样可以不局限与一种垃圾回收算法。
4. 火车算法
垃圾收集算法一个很大的缺点就是难以控制垃圾回收所占用的CPU时间,以及何时需要进行垃圾回收。火车算法是分代收集器所用的算法,目的是在成熟对象空间中提供限定时间的渐进收集。目前应用于SUN公司的Hotspot虚拟机上。
在火车算法中,内存被分为块,多个块组成一个集合。为了形象化,一节车厢代表一个块,一列火车代表一个集合,见图一
注意每个车厢大小相等,但每个火车包含的车厢数不一定相等。垃圾收集以车厢为单位,收集顺序依次为1.1,1.2,1.3,1.4,2.1,2.2,2.3,3.1,3.2,3.3。这个顺序也是块被创建的先后顺序。
垃圾收集器先从块1.1开始扫描直到1.4,如果火车1四个块中的所有对象没有被火车2和火车3的对象引用,而只有火车1内部的对象相互引用,则整个火车1都是垃圾,可以被回收。
如图二,车厢1.1中有对象A和对象B,1.3中有对象C,1.4中有对象D,车厢2.2中有对象E,车厢3.3中有对象F。在火车1中,对象C引用对象A,对象B引用对象D,可见,火车2和火车3没有引用火车1的对象,则整个火车1都是垃圾。
如果火车1中有对象被其它火车引用,见图三,扫描车厢1.1时发现对象A被火车2中的E引用,则将对象A从车厢1.1转移到车厢2.2,然后扫描A引用的对象D,把D也转移到车厢2.2,然后扫描D,看D是否引用其它对象,如果引用了其它对象则也要转移,依次类推。扫描完火车1的所有对象后,剩下的没有转移的对象都是垃圾,可以把整个火车1都作为垃圾回收。注意如果在转移时,如果车厢2.2空间满了,则要在火车2末尾开辟新的车厢2.4,将新转移的对象都放到2.4,即火车的尾部)
补充说明:垃圾回收器一次只扫描一个车厢。图三中的对象B与C并不是立即被回收,而是先会被转移到火车1的尾部车厢。即扫描完1.1后,B被转移到火车1尾部,扫描完1.3后,C被转移到车尾。等垃圾收集器扫描到火车1尾部时,如果仍然没有外部对象引用它们,则B和C会被收集。
火车算法最大的好处是它可以保证大的循环结构可以被完全收集,因为成为垃圾的循环结构中的对象,无论多大,都会被移入同一列火车,最终一起被收集。还有一个好处是这种算法在大多数情况下可以保证一次垃圾收集所耗时间在一定限度之内,因为一次垃圾回收只收集一个车厢,而车厢的大小是有限度的。
‘玖’ java有哪些垃圾回收算法
常用的垃圾回收算法有:
(1).引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
引用计数算法实现简单,效率很高,微软的COM技术、ActionScript、Python等都使用了引用计数算法进行内存管理,但是引用计数算法对于对象之间相互循环引用问题难以解决,因此java并没有使用引用计数算法。
(2).根搜索算法:
通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。
主流的商用程序语言C#、java和Lisp都使用根搜素算法进行内存管理。
在java语言中,可作为GC Root的对象包括以下几种对象:
a. java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b.方法区中的类静态属性引用的对象。
c.方法区中的常量引用的对象。
d.本地方法栈中JNI本地方法的引用对象。
java方法区在Sun HotSpot虚拟机中被称为永久代,很多人认为该部分的内存是不用回收的,java虚拟机规范也没有对该部分内存的垃圾收集做规定,但是方法区中的废弃常量和无用的类还是需要回收以保证永久代不会发生内存溢出。
判断废弃常量的方法:如果常量池中的某个常量没有被任何引用所引用,则该常量是废弃常量。
判断无用的类:
(1).该类的所有实例都已经被回收,即java堆中不存在该类的实例对象。
(2).加载该类的类加载器已经被回收。
(3).该类所对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射机制访问该类的方法。
Java中常用的垃圾收集算法:
(1).标记-清除算法:
最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(2).复制算法:
将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
复制算法的缺点显而易见,可使用的内存降为原来一半。
(3).标记-整理算法:
标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。
(4).分代收集算法:
根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。