1.垃圾回收(GC,garbage collector)机制简介
该能力是由Java虚拟机(JVM)提供的。在空闲时间不定时的回收无任何引用的对象所占用的内存空间,也可以清除内存记录碎片。
注意:
①回收的是对象所占的内存空间而不是对象本身。
②system.gc();该语句只是显式的通知JVM进行一次垃圾回收,但不知何时回收。
③Java中的一个显著特点就是引入了垃圾回收机制。Java程序员不用担心内存问题,因为垃圾回收期会自动进行内存管理。
④finalize()方法:一旦垃圾回收器准备好释放对象占用的存储空间,首先会先调用finalize()方法进行一些必要的清理工作。只有到下一次再进行垃圾回收动作的时候,才会真正释放这个对象所占用的内存空间。
2.GC特性及各种GC的选择
GC选择时应注意以下几点:
①尽可能少的暂停应用的运行。
②需要在时间、空间、回收频率这三个要素中平衡
③内存碎片的问题(一种解决内存碎片的方法,就是压缩)
2.1 连续 VS 并行
连续垃圾回收器,即使在多核的应用中,在回收时,也只有一个核被利用。
但并行GC会使用多核,GC任务会被分离成多个子任务,然后这些子任务在各个CPU上并行执行。
并行GC的好处是让GC的时间减少,但缺点是增加了复杂度,并且存在产生内存碎片的可能。
2.2 并发 VS stop-the-world
当使用stop-the-world 方式的GC在执行时,整个应用会暂停住的。
而并发是指GC可以和应用一起执行,不用stop the world。
一般的说,并发GC可以做到大部分的运行时间,是可以和应用并发的,但还是有一些小任务,不得不短暂的stop the world。
stop the world 的GC相对简单,因为heap(堆)被冻结,对象的活动也已经停止。但缺点是可能不太满足对实时性要求很高的应用。
相应的,并发GC的stop the world时间非常短,并且需要做一些额外的事情,因为并发的时候,对象的引用状态有可能发生改变的。
所以,并发GC需要花费更多的时间,并且需要较大的heap。
2.3 压缩 VS 不压缩 VS 复制
在GC确定内存中哪些是有用的对象,哪些是可回收的对象之后,它就可以压缩内存,把拥有的对象放到一起,并把剩下的内存进行清理。在压缩之后,分配对象就会快很多,并且内存指针可以很快的指向下一个要分配的内存地址。
一个不压缩的GC,就原地把不被引用的对象回收,他并没有对内存进行压缩。好处就是回收的速度变快了;缺点呢,就是产生了碎片。
一般来说,在有碎片的内存上分配一个对象的代价要远远大于在没有碎片的内存上分配。
另外的选择是使用一个复制算法的GC,他是把所有被引用的对象复制到另外一个内存区域中。
使用复制GC的好处就是,原来的内存区域,就可以被毫无顾忌的清空了。但缺点也很明显,需要更多的内存,以及额外的时间来复制。
3. GC性能指标
几个评估GC性能的指标
吞吐量 应用花在非GC上的时间百分比
GC负荷 与吞吐量相反,指应用花在GC上的时间百分比
暂停时间 应用花在GC stop-the-world 的时间
GC频率 顾名思义
Footprint 一些资源大小的测量,比如堆的大小
反应速度 从一个对象变成垃圾道这个对象被回收的时间
一个交互式的应用要求暂停时间越少越好,然而,一个非交互性的应用,当然是希望GC负荷越低越好。
一个实时系统对暂停时间和GC负荷的要求,都是越低越好。
一个嵌入式系统当然希望Footprint越小越好。
4. 分代回收
4.1 概念简介
stop-the-world:简称STW,JVM停止应用程序,而去进行垃圾回收。当stop-the-world发生时,除了进行垃圾回收的线程,其他所有线程都将停止运行。被中断的任务将在GC任务完成后恢复执行。GC调优往往意味着减少stop-the-world的时间。
4.2 分代垃圾回收
在HotSpot虚拟机中,物理的将内存分为两个——年轻代(young generation)和年老代(old generation)。
年轻代:新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数对象在年轻代中创建,然后消失。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。
年老代:没有变得不可达,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。
4.3 流程如下
当使用分代回收技术,内存会被分为几个代(generation)。也就是说,按照对象存活的年龄,把对象放到不同的代中。
使用最广泛的代,应属年轻代和年老代了。
根据各种GC算法的特征,可以相应的被应用到不同的代中。
研究发现:
大部分的对象在分配后不久,就不被引用了。也就是,他们在很早就挂了。
只有很少的对象熬过来了。
年轻代的GC相当的频繁,高效率并且快。因为年轻代通常比较小,并且很多对象都是不被引用的。
如果年轻代的对象熬过来了,那么就晋级到年老代中了。如图:
通常
年老代要比年轻代大,而且增长也比较慢。所以
GC在年老代发生的频率非常低,不过一旦发生,就会占据较长的时间。
年轻代通常使用时间占优的GC,因为年轻代的GC非常频繁。
年老代通常使用善于处理大空间的GC,因为年老代的空间大,GC频率低。
5. GC算法
任何一种垃圾回收算法一般要做2件基本的事情:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间。
5.1 一种常用方法:引用计数。
引用计数未使用根集。
所有对象都有一个引用计数器,当有引用连接时计数器加1,离开时减1。垃圾回收器会在所有对用的引用列表上遍历,当发现某个对象的计数为0时,释放占用的空间。
5.2 使用根集(root set)的部分算法
根集:正在执行的Java程序可以访问的引用变量的集合(包括局部变量、参数、类变量)。垃圾回收首先要确定从根开始哪些是可达的和哪些是不可达的,从根可达的对象都是活动对象,当然包括从根间接可达的对象。如:tracing算法等。
6.HotSpot上的GC学习 -分代、GC类型、快速分配
6.1 分代
分成三部分:年轻代、年老代、永久代。
很多的对象一开始是分配在年轻代的,这些对象在熬过了一定次数的young gc之后,就进入了年老代。同时,一些比较大的对象,一开始就可能被直接分配到年老代中(因为年轻代比较小嘛)。
6.2 年轻代
年轻代也进行划分,划分为:一个Eden和两个survivor。如下图:
大部分的对象被直接分配到年轻代的eden区(之前已经提到了是,很大的对象会被直接分配到年老代中),
survivor区里面放至少熬过一个YGC的对象,在survivor里面的对象,才有机会被考虑提升到年老代中。
同一时刻,两个survivor只被使用一个,另外一个是用来进行复制GC时使用的。
6.3 快速分配内存
多线程进行对象建立的时候,在为对象分配内存的时候,就应该保证线程安全,为此,就应该进入全局锁。但全局锁是非常消耗性能的。
为此,HotSpot引入了Thread Local Allocation Buffers (TLAB)技术,这种技术的原理就是为每个线程分配一个缓冲,用来分配线程自己的对象。
每个线程只使用自己的TLAB,这样,就保证了不用使用全局锁。当TLAB不够用的时候,才需要使用全局锁。但这时候对锁的时候,频率已经相当的低了。
为了减少TLAB对空间的消耗,分配器也想了很多方法,平均来说,TLAB占用Eden区的不到1%。