理解JVM(4)垃圾回收
来源:互联网 发布:匹配滤波器 矩阵算法 编辑:程序博客网 时间:2024/05/16 12:54
常用的垃圾回收算法
引用计数法:
- 优点:简单
- 缺点:循环引用问题,引用产生消除时进行加减
标记清除法:
在标记阶段,通过根节点标记所有可达的对象;在清除阶段,清除所有未被标记的对象。
- 缺点:回收后空间不连续,产生空间碎片,对于大对象会降低工作效率
复制算法:
将内存空间分为2块,每次使用一块,在垃圾回收时,将正在使用内存中的存活对象复制到未使用的一块中,清除这块的所有对象
- 缺点:内存被折半了
在Java的新生代串行垃圾回收器中,使用Eden,from,to。其中from,to进行角色互换实现复制算法。因为新生代对象死得块,用很小的一块的内存就能放得下存活的对象。但出现放不下的情况,会将对象直接放进老年代。此外,大对象,老年对象,一块survivor区域放不下的对象,都是直接进入老年代。
标记压缩算法:
适用于存活对象多,垃圾对象少的地方,即老年代。首先也是标记,然后将存活对象压缩整理到内存的一端,之后清除边界外的所有空间。
分代算法
分代算法基于对象的存活率不同,分为新生代和老年代,根据上述算法中,复制算法适合新生代,标记压缩算法适合老年代。
卡表的数据结构:老年代以512字节(或4K)为块,分为若干张卡(Card)。卡表为单字节数组,每个数组元素对应堆中的一张卡,每次老年代对象引用新生代对象时,该卡对应的卡表中的元素会设置为脏,垃圾收集器只会扫描脏卡。
分区算法
分区算法将整个堆空间划分为连续的不同小区间。每个小区间都独立使用,独立回收。这样的好处是可以控制一次回收内存的大小,不至于回收整个堆内存而产生大量的停顿时间。
判断可触性
强引用
- 强引用直接访问对象
- 强引用指向的对象不会被回收
- 强引用会造成内存泄露
软引用:内存不足会自动回收
class User(var name: String, var id: Int) { var data = ByteArray(6 * 1000 * 1000) override fun toString(): String { return "User(name=$name)" }}fun main(args: Array<String>){ var u: User? = User("Owen", 1) val soft = SoftReference(u) u = null println(soft.get()) System.gc() println("After GC:" + soft.get()) val b = ByteArray(6 * 1000 * 1000) println("After big data:" + soft.get())}/*User(name=Owen)After GC:User(name=Owen)After big data:null*/
引用队列
PS:在创建一个引用对象,传入一个引用队列,这个对象死后会进入引用队列
import java.lang.ref.ReferenceQueueimport java.lang.ref.SoftReference//要弱引用的类class User(var name: String, var id: Int) { var data = ByteArray(6 * 1000 * 1000) override fun toString(): String { return "User(name=$name)" }}//强化的弱引用,记录一个UIDclass UserSoftReference(p0: User?, p1: ReferenceQueue<in User>?, val uid: Int = p0?.id!!) : SoftReference<User>(p0, p1) {}//引用队列var softQueue: ReferenceQueue<User>? = null//一个守护线程class CheckRedQueue : Thread() { override fun run() { //不断查看删除的引用 while (true){ softQueue?.let { val obj: UserSoftReference? = softQueue?.remove() as? UserSoftReference println("User Id ${obj?.uid} is deleted") } } }}fun main(args: Array<String>){ val t = CheckRedQueue(); t.isDaemon = true t.start() var u:User? = User("Owen",1) softQueue = ReferenceQueue<User>() val userSoftRef = UserSoftReference(u, softQueue) u = null println(userSoftRef.get()) System.gc() println("After GC :${userSoftRef.get()}") val daa = ByteArray(6*1000*1000) println("After big data:${userSoftRef.get()}") Thread.sleep(1000)}/*User(name=Owen)After GC :User(name=Owen)User Id 1 is deletedAfter big data:null*/
弱引用:发现即回收
class User(var name: String, var id: Int) { var data = ByteArray(6 * 1000 * 1000) override fun toString(): String { return "User(name=$name)" }}fun main(args: Array<String>){ var u: User? = User("Owen",12) val weak = WeakReference<User>(u) u = null System.gc() println("After GC:${weak.get()}")}
虚引用
随机可能被垃圾收集器回收,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况
Stop-The-World
在垃圾回收时,应用程序会无响应,来保持系统状态在某一瞬间的一致性。
class MyThread : Thread(){ val map: HashMap<Long,ByteArray> = HashMap() override fun run() { while (true){ if (map.size*512/1024/1024 >= 900){ map.clear() println("clean map") } var b1: ByteArray? for (i in 0..100){ b1 = kotlin.ByteArray(512) map.put(System.nanoTime(),b1) } Thread.sleep(1) } }}class PrintThread : Thread(){ companion object { val startTime = System.currentTimeMillis() } override fun run() { while (true){ val t = System.currentTimeMillis() - startTime println("${t/1000}.${t%1000}") Thread.sleep(100) } }}fun main(args: Array<String>){ val t = MyThread() val p = PrintThread() t.start() p.start()}
垃圾收集器种类
串行回收器
是Client下默认的垃圾回收器,仅仅使用单线程回收,会产生Stop-The-World.
新生代日志[GC ….[DefNew: xxxxK -> xxxK…]]
老年代日志[Full GC [Tenured: xxxxK -> xxxK…]]
- -XX:+UseSerialGC:新生代,老年代都使用串行回收器
- -XX:+UseParNewGC: 新生代使用ParNew回收器,老年代串行收集器
- -XX:+UseParallelGC: 新生代使用ParallelGC回收器,老年代串行收集器
并行回收器
新生代ParNew回收器:
工作在新生代,只是将串行程序并行化,策略,算法不变。在并行能力强的CPU上,产生的停顿时间要短于串行回收器,但单CPU上可能表现更差。
日志[GC ….[ParNew: xxxxK -> xxxK…]]
- -XX:+UseParNewGC: 新生代使用ParNew回收器,老年代串行收集器
- -XX:+UseConcMarkSweepGC: 新生代使用ParNew回收器,老年代使用CMS
- -XX:ParallelGCThreads指定多少线程参与并发收集,小于8CPU,则等于CPU数量,大于8CPU,则3+((5*CPU)/8)
新生代ParallelGC回收器:
使用复制算法的收集区,非常关注系统的吞吐量
日志[GC [PSYoungGen …]]
- -XX:+UseParallelGC: 新生代使用ParallelGC回收器,老年代串行收集器
- -XX:+UseParallelOldGC: 新生代使用ParallelGC回收器,老年代ParallelOldGC收集器
- -XX:MaxGCPauseMillis: 设置最大垃圾收集器停顿时间
- -XX:GCTimeRatio: 设置吞吐量大小。它的值为1-100,系统会使用1/(1+n)的时间来用于垃圾收集。如默认为19,则会使用1/(1+19) = 5%的时间来垃圾收集
- -XX:UseAdaptiveSizePolicy: 打开自适应GC策略,会自动调整新生代大小,Eden区和Survivor区比例,进入老年代的年纪等参数
老年代ParallelOldGC回收器:
使用标记压缩算法,也是非常关注吞吐量。也是Stop-The-World后,使用多线程并发收集垃圾。
日志[Full GC [PSYoungGen …]]
CMS回收器:
Concurrent Mark Sweep,并发标记清除,使用标记清除算法
工作流程:初始标记 -> 并发标记 -> 预清理 -> 重新标记 -> 并发清除 -> 并发重置
- 初始标记:STW: 标记根对象
- 并发标记:和应用程序并发:标记所有对象
- 预清理:和应用程序并发:清理前准备,并控制停顿时间
- 重新标记:STW: 修正并发标记的数据
- 并发清理:和应用程序并发:清理垃圾
- 并发重置:和应用程序并发:恢复第1步
预清理会刻意等待一次新生代GC, 然后预测下一次新生代GC,在当前时间和预测时间的中间时间,进行重新标记。使用-XX:-CMSPrecleaningEnabled,不进行预清理。
- -XX:-CMSPrecleaningEnabled,不进行预清理。
- -XX:+UseConcMarkSweepGC,启用CMS回收器
- -XX:+ConcGCThreads或-XX:ParallelCMSThreads,设置并发线程数量,当CPU紧张时,收到CMS影响,垃圾收集会很糟糕
- -XX:CMSInitiatingOccupancyFraction,回收阀值,默认68,即当老年代内存使用率达到68%后,会进行CMS回收。如果回收时出现内存不足,会CMS失败,然后启动老年代串行收集器进行垃圾回收,这样的话,会STW
- -XX:+UseCMSCompactAtFullCollection,由于是标记清除算法,可以整理空间,启用内存碎片的整理,STW
- -XX:CMSFullGCsBeforeCompaction,进行多少次CMS, 才进行一次内存整理
- -XX:+CMSClassUnloadingEnabled,会使用CMS的机制回收Perm区的Class数据
G1回收器:
- 并行性:利用多核进行回收
- 并发性:部分工作会和应用程序并发
- 分代GC:兼顾新生代和老年代的回收
- 空间整理:每次回收都会复制对象,减少空间碎片
- 可预见性:由于分区,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,对STW的时长进行很好的控制。
阶段1:新生代
新生代GC主要收集eden和survivor区。回收后,所有eden区全部清空,survivor区会收集一部分数据,但至少存在一个survivor区。并且老年代的区域会增加。
阶段2:并发标记周期
初始标记=>根区域扫描=>并发标记=>重新标记=>独占清理=>并发清理
标记为G的区域,只是内部垃圾比例比较高
阶段3:混合回收
针对含垃圾比例高的区域进行回收,这是Garbage First Garbage Collector名字的由来,回收时优先选择垃圾比例最高的区域
该阶段,既会执行正常的新生代GC,也会选取一些老年代区域进行回收,同时处理新生代和老年代。因为新生代GC会清空Eden区域,此外两块标记为G的老年代也被清理,存活对象被移动到其他区域,减少空间碎片。
阶段4:可能的Full GC
在回收时,出现内存不足,会转入Full GC。
命令
- -XX:UseG1GC,打开G1收集器
- -XX:MaxGCPauseMillis,指定最大停顿时间
- -XX:ParallelGCThreads,设置并行回收的工作线程数量
- -XX:InitiatingHeapOccupancyPercent,指定堆使用率达到多少后,触发并发标记周期,默认为45。这个值不会修改,如果该值偏大,会导致回收时内存不足,Full GC可能性增大,如果该值偏小,会频繁GC,应用程序性能降低
日志
其他参数
- -XX:+DisableExplicitGC,禁言线性System.gc()
- -XX:+ExplicitGCInvokesConcurrent,显性调用System.gc()使用并发收集
- -XX:MaxTenuringThreshold,进入老年代的年纪
- -XX:+PrintHeapAtGC,打印堆日志
- -XX:TargetSurvivorRatio,设置Survivor区的目标使用率,默认为50。当超过该比率时,会减少进入老年代的年纪。
- -XX:PretenureSizeThreshold,设置对象多大,会直接进入老年代,单位为字节。默认为0,即看情况
- -XX:-UseTLAB,禁用TLAB,即线程本地分配缓存。这个区域为了加速对象分配,对象默认分配在堆上,多线程时分配空间必须同步,就降低性能。使用TLAB,能避免多线程冲突,TLAB本身占用eden空间,虚拟机会为每一个线程分配一块TLAB空间。
阅读全文
0 0
- 理解JVM(4)垃圾回收
- 深入理解JVM垃圾回收
- 理解jvm垃圾回收机制
- 【深入理解JVM】垃圾回收
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- 深入理解JVM--JVM垃圾回收机制
- JAVA多态——类型判断
- 解释器构造实践-ANTLR(一)
- Django框架
- WebView android sdk 25加载“file:///..."失败解决
- 统计学学习的博客转载
- 理解JVM(4)垃圾回收
- pip和easyinstall区别
- TMemoryStream、String与OleVariant互转
- 解释器构造实践-ANTLR(二)
- 输入一个表达式,表达式中包括三种括号“()”、“[]”和“{}”,判断该表达式的括号是否匹配。
- Java后台框架篇--spring Batch实现数据库大数据量读写
- ffmpeg+SDL的使用之获取视频帧将其存储为PPM格式图片
- 数据结构算法(一)--有序线性表的合并和交集
- Linux-进程间通讯-管道