垃圾回收机制就是 采用了自动内存管理。
1这意味着你不用 *** 心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
2Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。
3垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。
垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。
4如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。
垃圾回收器函数
5Lua 提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理:
collectgarbage("collect"): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
collectgarbage("count"): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。
collectgarbage("restart"): 重启垃圾收集器的自动运行。
collectgarbage("setpause"): 将 arg 设为收集器的 间歇率。 返回 间歇率 的前一个值。
collectgarbage("setstepmul"): 返回 步进倍率 的前一个值。
collectgarbage("step"): 单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
collectgarbage("stop"): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
本文主要内容
本文主要从概念上介绍内存回收及垃圾收集器相关内容,不涉及具体性能调优。
内存回收是程序员永恒的主题,虽然Java虚拟机自动回收内存,但仍存在内存漏泄的可能,需要理解内存回收机制,有助于程序员规避、排查内存泄漏问题。
GC机制,最重要的是三个问题:
对象已死
对象是否已经死亡,可被回收,经常能听到下面这种说法:
引用计数法 :给对象中添加一个引用计数器,每当有一个地方引用时,计数器就加1,当引用失效时,计数器值就减1,任何时候计数器为0的对象就是不可能再被使用的
不过,它存在一个致命缺陷,很难解决循环引用问题,比如对象AB,A引用B,B也引用A,但它们再没有被其它人所引用,AB理应是被回收对象,但它们的引用计数器仍然不为0,导致无法回收。
Java虚拟机不使用此算法来判定对象是否死亡,不过它依然有很多优点,简单。Android 中的智能指针即是使用这种方法,不过添加了智能指针强弱引用来解决循环引用问题
可达性分析算法 ,这个算法的基本思路就是通过一系列的名为“GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到“GC Roots"没有任何引用链相连,则此对象不可用,将被回收
在Java中,可作为“GC Roots"的对象包括以下几种:
引用
在JDK12以前,引用的概念为:
JDK12之后,引用概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种,四种引用强度依次减弱。
垃圾收集算法
本章将介绍三种垃圾收集算法。
标记清除算法 ,算法分成“标记”和“清除”两个阶段,首先标记出需要回收的对象,在标记完成后统一回收掉所有被标记的对象
它主要有两个问题,一是效率不高,标记和清除过程的效率都不高,另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多,当程序在运行中需要分配较大对象,因为碎片过多,可能无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
复制算法 ,为了解决效率问题,一种称为复制的收集算法出现了。它将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。每次都是只对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是代价高昂,将内存缩小为原来的一半。
现在的商业虚拟机都采用这种算法来回收新生代,新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当回收时,将Eden和Survivor看还存活的对象一次性拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
虚拟机默认Eden和Survivor的大小比例是8比1,所以只有10%的空间会被浪费。
如果存活的对象较多而Survivor空间不够用时,需要依赖其它内存(老年代)进行分配担保,如果另外一块Survivor空间没有足够的空间来存放上一次新生代的存活对象,这些对象将直接通过分配担保机制进入老年代
标记整理算法 ,复制收集算法在对象存活率较高时就要执行较多的复制 *** 作,效率将会变得更低,更关键的是,如果不想浪费一半的空间,就需要额外的空间进行分配担保,以应对所有对象百分百存活的极端情况。所以老年代一般不直接使用这种算法
根据老年代的特点,提出了“标记整理算法”,过程依然和“标记清除算法”一致,但后续步骤不是直接对可回收对象进行清除,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
分代收集算法
当前商业虚拟机的垃圾收集都采用分代收集算法。它根据对象存活周期的不同将内存划分为几块。
一般是把Java堆分成新生代和老年代,新生代又分成一块较大的Eden和两块Survivor。根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾回收时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率罗高,没有额外空间对它进行分配担保,就必须使用标记清理或者标记整理算法。
垃圾收集器
Serial收集器
Serial收集器是最基本、历史最悠久的收集器。顾名思义,这个收集器是一个单线程收集器。单线程的意义并不仅仅说明它只会使用一个CPU或者一条线程去完成垃圾收集工作,更重要的是它在垃圾收集时,必须暂停其他所有的工作线程(Sun将这件事情称之为“Stop the world”),直到它结束
“Stop the world”,非常影响用户体验,虚拟机在后台自动发起和完成的,在用户不可见的情况下把用户的正常工作的线程全部停掉。
虽然Serial收集器出现时间较长,但它依然是Client模式下的默认新生代收集器
ParNew收集器
ParNew收集器其实就是Serial收集器的多线程控制版本,除了使用多条线程进行垃圾收集之外,其它和Serial收集器完全一样。
ParNew收集器是Server模式下虚拟机中的首选的新生代收集器。而且在单CPU环境下,ParNew绝对不会比Serial更高效,甚至由于存在线程交互的开销,在两个CPU的环境中都不一定比Serial更好
Parallel Scavenge收集器
Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法,又是并行的多线程收集器,看上去和ParNew一样,但它非常的有特点。
Parallel Scavenge收集器关注点即是吞吐量,CMS等收集器的关注点是尽量缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge的目的则是达到一个可控制的吞吐量。
Parallel Scavenge提供两个参数用于精准控制吞吐量
Serial old收集器
Serial old收集器是Serial收集器的老年代版本,同样是一个单线程收集器,使用标记整理算法,它的主要意义也是被Client模式下的虚拟机使用
Parallel old收集器
Parallel old是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它很适合互联网站或者B/S系统的服务端上。
从Mark Sweep名字可知,CMS用的是标记清除算法,它的动作过程较之前的复杂一些,整个过程分为4个步骤:
其中初始标记、重新标记这两个步骤仍然需要 “Stop the world”。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发阶段就是进行GC Root Tracing的过程,而重新标记则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,重新标记时间略比初始标记长,但远比并发标记时间短。
整个过程最耗时的是并发标记和并发清除,但用户线程和收集器线程一起工作,所以总体上说,CMS收集器的内存回收过程是与用户线程一起并发地执行。
Net依赖CLR(公共语言运行时)实现自动内存管理,标准CLR使用分代式标记-压缩GC对托管堆上对象进行自动内存管理。1、GC过程
(1)标记阶段:从应用程序的根找到所有可达对象进行标记并创建一个对象引用图;
每个应用程序都有一组根,应用程序的根包含线程堆栈上的静态字段、局部变量和参数以及CPU寄存器。垃圾回收器可以访问由实时编译器(JIT)和运行时维护的活动根的列表。
(2)清除阶段:
a、未标记对象如果没有终结器(析构函数)则立即回收;
b、有终结器对象在创建时会将对象引用放到终结队列,当此对象变为垃圾时,垃圾收集器会将其引用从终结队列移到f-reachable队列。GC完成后一个终结器线程会遍历f-reachable队列,获取每个对象执行其Finalize方法,待该对象的Finalize方法执行完后,将该对象引用从f-reachable队列移除。这些对象会在下一次GC中回收(除非该对象复活);
如果某个终结器对象在终结器过程中复活(Resurrection),但是复活时不会将对象的引用重新放到终结队列,如果想让复活对象下次回收时执行Finalize方法,可以在对象复活时手动调用GCReRegisterForFinalize(obj)将该对象的引用添加到终结队列。
另外GCSuppressFinalize(Object obj)可以将对象的引用从终结队列移除,垃圾回收时不再执行Finalize方法。一般SuppressFinalize用于同时实现了Finalize和Dispose方法来释放资源的情况下,在Dispose方法中调用GCSuppressFinalize(this)。Finalize方法作为忘记调用Dispose释放资源的一个保障。
(3)压缩阶段:清除完后将所有存活对象移到堆的起始位置。压缩可以防止碎片化,同时避免耗时的空内存片段列表维护,直接用简单的策略将堆的尾部内存分配给新对象。
2、GC触发时机:
(1)由托管堆上已分配对象的使用内存超过特定阈值(该阈值可能随着进程的运行不断调整)时;
(2)调用SystemGCCollect手动触发;
(3)系统具有低的物理内存。通过OS的内存不足通知或主机指示的内存不足检测出来。
3、CLR中GC优化
31、分代回收
垃圾回收器将堆上内存对象分为3代:
第0代:新分配对象及从未经过垃圾回收的对象集合。通常仅有几百KB到几MB;
第1代:第0代回收中存活的对象集合;
第2代:第1代和第2代中未回收的对象集合。
因此可以单独处理长生存期和短生存期对象。每一代都维护一个阀值,当第n代内存达到该阀值时会触发对第n代的收集,当垃圾回收器检测到某个代中的幸存率很高时,会增加该代的分配阈值。回收一代时同时回收它前面的所有代。
32 、大对象堆(Large Object Heap,LOH)
加载CLR时,GC分配两个初始堆段:一个用于小型对象(小对象堆SOH),一个用于大型对象(大对象堆 LOH)。垃圾回收器将大于等于一个阈值(目前是85000字节)的对象分配到大对象堆。大对象堆上对象都按第2代处理,可以避免过量的第0代回收。由于复制大型对象代价太大,默认不压缩大对象堆,所以需要维护空闲内存块链表,并会产生碎片化问题。在Net Core和Net Framework 451以上版本中,可以使用 GCSettingsLargeObjectHeapCompactionMode 属性按需压缩大对象堆。
33、并发和后台回收
(1)并发垃圾回收
仅适用于工作站垃圾回收的Net Framework 35及更早版本;服务器垃圾回收Net Framework 4及更早版本。更高版本中,后台垃圾回收取代了并发垃圾回收。
并发垃圾回收只影响第2代垃圾回收,第0代和第1代的垃圾回收始终是非并发的。并发垃圾会输在一个专用线程上执行,运行并发垃圾回收线程的大多数时间,托管线程可以继续运行,最大程度减少因回收引起的暂停。
(2)后台垃圾回收
在Net Framework 4 及更高版本中,后台垃圾回收替换并发垃圾回收。但在Net Framework 4 中仅支持工作站垃圾回收,Net Framework 45开始,后台垃圾回收可用于工作站和服务器垃圾回收。
后台垃圾回收只适用于第2代回收,后台垃圾回收在一个或多个专用线程上执行行,后台垃圾回收进行中,后台垃圾回收线程将在常见的安全点上检查,如果发现第0代或第1代空间不足需要前台垃圾回收时,后台垃圾回收会暂停自己并让前台垃圾回收(对暂时代(第0代和第1代)的回收)执行。前台垃圾回收完成之后,后台回收线程和用户线程将继续。
34、工作站和服务器垃圾回收
CLR提供以下类型的垃圾回收,可以基于工作负载的特征设置垃圾回收类型:
(1)工作站垃圾回收:为客户端应用设计;
回收发生在触发垃圾回收的用户线程上,并保留与用户线程相同的优先级(普通优先级),所以垃圾回收线程必须与其它线程竞争CPU时间。
(2)服务器垃圾回收:用于需要高吞吐量和可伸缩性的服务器应用程序
回收发生在以 THREAD_PRIORITY_HIGHEST 优先级运行的多个专用线程上,为每个CPU提供一个用于执行垃圾回收的一个堆和专用线程,多个垃圾回收线程一起工作。
34、垃圾回收通知(为担负大量请求的服务器应用准备)
服务器版本的CLR可以在完全垃圾回收之前发送通知。可以调用GCRegisterForFullGCNotification启用通知。然后开启另一个线程持续监听GCWaitForFullGCApproach(),当返回的GCNotificationStatus表示即将进行一次回收时,将工作负载重定向到另一个服务器实例,然后监听GCWaitForFullGCComplete(),当该方法返回的状态表明回收完毕时在重新开始接受请求。
4、弱引用
CLR由SystemWeakReference类实现弱引用,将Target属性设置为该对象。当垃圾收集器遇到一个弱引用指针指向对象时,不会将该对象加入引用关系图中。如果一个对象只有弱引用指向它,该对象不会被标记,垃圾回收时就可以清除此对象。
(1)短弱引用
垃圾回收回收对象后,弱引用的Target会变为null。弱引用本身时托管对象,也需要经过垃圾回收。
(2)长弱引用
在对象的Finalize方法调用后,长弱引用获得保留。这样便可以重新创建新对象。
若要建立强引用,可以将WeakReference的Target属性(前提是Target属性不为null,即对象未被回收)强制转换为对象类型。
一、四种引用方式
11 强引用
12 软引用(SoftReference)
13 弱引用(WeakReference)
14 虚引用(PhantomReference)
二、如何判断对象是垃圾
21 引用计数法
22 根可达性分析
三、垃圾回收算法
31 标记-清除(mark-sweep)
32 标记-整理(mark-compact)
33 标记-复制(mark-copy)
四、垃圾收集器
41 分类及特点简述
411 串行
412 吞吐量优先
413 响应时间优先
42 串行垃圾回收器详述
421 Serial
422 Serial-Old
423 流程图
43 吞吐量优先垃圾回收器详述
431 JVM相关参数
432 流程图
44、响应时间优先垃圾回收器详述
441 JVM相关参数
442 流程图
433 CMS的特点
五、G1垃圾回收器
51 相关JVM参数
52 特点
53 G1新生代垃圾回收
54 G1老年代垃圾回收
只有所有 GC Roots对象都不通过强引用引用该对象,该对象才可以被回收。
某个对象只要有一处引用关系,该对象的引用次数就加1,如果一个对象的引用次数为0,则说明该对象是垃圾。
优势:实现简单,效率较高
弊端:如果有一对对象之间形成了相互引用,但是这两个对象都已经没有被其它对象所引用了,正常情况下,这一对对象应该被作为垃圾回收掉,但是因为形成了相互引用导致无法被回收。
通过GC Root对象开始向下寻找,寻找不到的对象即说明没有被引用,那么这些没有被引用的对象被认定为垃圾。
目前,如下对象可以作为GC Root对象:
很好理解,即在GC的放生时候,先对所有对象进行根可达性分析,借此标记所有的垃圾对象;所有对象标记完毕之后会进行清理 *** 作。
因此,总体来说,就是先标记再清除。
弊端;标记清除之后会产生大量不连续的内存碎片,碎片太多可能会导致程序运行过程中需要分配较大对象时,无法满足分配要求导致GC *** 作。
该回收算法 *** 作过程基本等同于 标记-清除 算法只不过,第二步有点区别,该种方式会在清除的过程中进行 整理 *** 作,这是最大的不同。
优势:最终不会出现若干空间碎片而导致的空间浪费。
弊端:在整理过程中带来的计算不可小觑。
该种方式与前两种有较大的区别:
该种方式会将存储区分成两个部分,分别为From、To,其中From区域中可能存在着对象,而To区域始终为空,用做下一次接受数据做准备。
分别有两个指针指向这两个区域:From-Pointer、To-Pointer,
优点:这种算法非常适合早生夕死的对象
缺点:始终有一块内存区域是未使用的,造成空间的浪费。
特点:
特点:
特点:
JVM开关:-XX:+UseSerialGC = Serial + SerialOld
上图是:CMS垃圾回收器在老年代GC的工作流程图:
经过上面的文字分析,新生代的Region个数为所有Region个数的5%;这个数值其实是很小的,那么当新生代Region不够用的时候,JVM会划分更多的Region个数给新生代;
当新生代的Region个数占比所有Region个数超过 60% 时,就会进行一次新生代的垃圾回收。
新生代垃圾回收会造成STW。
具体的垃圾回收算法同其它几个新生代垃圾回收器一样,新生代都使用复制算法。
老年代垃圾回收触发机制与参数-XX:InitaingHeapOccupancyPercent有关。
但是需要注意的是:这一次的老年代回收,其实是一次混合垃圾回收,会同时清理新生代、老年代、Humongous。
与新生代回收算法一致,依然使用复制算法,但是垃圾回收的过程等同于老年代响应时间优先的CMS方式
流程分为:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)