JVM之垃圾收集

       Java与C++之间有一堵内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。

概述:

       对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权利的“皇帝”又是从事最基础工作的“劳动人民”——既拥有每一个对象的“所有权”,又担负着每一个对象声明开始到终结的维护责任。

       对于 Java 程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个 new 操作去写配对的 delete/free 代码,不容易出现内存泄漏和内存溢出,看起一切都很美好。不过,也正因为 Java 程序员把控制内存的权力交给了 Java 虚拟机,一旦出现内存泄漏和溢出,如果不了解虚拟机是怎样使用内存的,那排查错误将会成为一项异常艰难的工作。

       说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史远远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考:

  GC需要完成的三件事情:

     哪些内存需要回收?

    什么时候回收?

    如何回收?

       经过半个世纪的发展,内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”时代,那为什么我们还要去了解GC和内存分配呢?答案很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

       把时间从半个世纪以前拨回到现在,回到我们熟悉的Java语言。我们上篇介绍了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内不需要过多考虑回收的问题,因为方法结束或线程结束时,内存自然就跟随着回收了。

       而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存,后续讨论中的“内存”分配与回收也仅指这一部分内存。

内存溢出

       在JVM申请内存的过程中,会遇到无法申请到足够内存,从而导致内存溢出的情况。一般有以下几种情况:
• 虚拟机栈和本地方法栈溢出
       • StackOverflowError: 线程请求的栈深度大于虚拟机所允许的最大深度(循环递归)
       • OutOfMemoryError: 虚拟机在扩展栈是无法申请到足够的内存空间,一般可以通过不停地创建线程引起此种情况
• Java堆溢出: 当创建大量对象并且对象生命周期都很长的情况下,会引发OutOfMemoryError
• 运行时常量区溢出:OutOfMemoryError:PermGen space,这里一个典型的例子就是String的intern方法,当大量字符串使用intern时,会触发此内存溢出
• 方法区溢出:方法区存放Class等元数据信息,如果产生大量的类(使用cglib),那么就会引发此内存溢出,OutOfMemoryError:PermGen space,在使用Hibernate等框架时会容易引起此种情况。

垃圾收集

理论基础

       在通常情况下,我们掌握java的内存管理就是为了应对网站/服务访问慢,慢的原因一般有以下几点:
       • 内存:垃圾收集占用cpu;放入了太多数据,造成内存泄露(java也是有这种问题的^_^)
       • 线程死锁
       • I/O速度太慢
       • 依赖的其他服务响应太慢
       • 复杂的业务逻辑或者算法造成响应的缓慢
       其中,垃圾收集对性能的影响一般有以下几个:
       • 内存泄露
       • 程序暂停
       • 程序吞吐量显著下降
       • 响应时间变慢

       

对于GC(垃圾回收)的流程的基本描述如下:


• 找出堆中活着的对象
• 释放死对象占用的资源
• 定期调整活对象的位置
       

GC算法一般有以下几种:


• Mark-Sweep 标记-清除
• Mark-Sweep-Compact 标记-整理
• Copying Collector 复制算法
• Mark-标记
       从”GC roots”开始扫描(这里的roots包括线程栈、静态常量等),给能够沿着roots到达的对象标记为”live”,最终所有能够到达的对象都被标记为”live”,而无法到达的对象则为”dead”。效率和存活对象的数量是线性相关的。
• Sweep-清除
扫描堆,定位到所有”dead”对象,并清理掉。效率和堆的大小是线性相关的。
• Compact-压缩
对于对象的清除,会产生一些内存碎片,这时候就需要对这些内存进行压缩、整理。包括:relocate(将存货的对象移动到一起,从而释放出连续的可用内存)、remap(收集所有的对象引用指向新的对象地址)。效率和存活对象的数量是线性相关的。
• Copy-复制
       将内存分为”from”和”to”两个区域,垃圾回收时,将from区域的存活对象整体复制到to区域中。效率和存活对象的数量是线性相关的。
       其中,Copy对比Mark-sweep
1. 内存消耗:copy需要两倍的最大live set内存;mark-sweep则只需要一倍。
2. 效率上:copy与live set成线性相关,效率高;mark-sweep则与堆大小线性相关,效率较低。

分代收集是目前比较先进的垃圾回收方案

对于分代收集,有以下几个相关理论
• 分代假设:大部分对象的寿命很短,“朝生夕死”,重点放在对年青代对象的收集,而且年青代通常只占整个空间的一小部分。
• 把年青代里活的很长的对象移动到老年代。
• 只有当老年代满了才去收集。
• 收集效率明显比不分代高。
HotSpot虚拟机的分代收集,分为一个Eden区、两个Survivor去以及Old Generation/Tenured区,其中Eden以及Survivor共同组成New Generatiton/Young space。
       这里写图片描述
• Eden区是分配对象的区域。
• Survivor是minor/younger gc后存储存活对象的区域。
• Tenured区域存储长时间存活的对象。
分代收集中典型的垃圾收集算法组合描述如下:
• 年青代通常使用Copy算法收集,会stop the world
• 老年代收集一般采用Mark-sweep-compact, 有可能会stop the world,也可以是concurrent或者部分concurrent。

HotSpot垃圾收集器

       这里写图片描述
上图即为HotSpot虚拟机的垃圾收集器组成。
Serial收集器
• -XX:+UserSerialGC参数打开此收集器
• Client模式下新生代默认的收集器。
• 较长的stop the world时间
• 简单而高效
此收集器的一个工作流程如下如所示:
收集前:
       这里写图片描述
收集后:
        这里写图片描述
ParNew收集器
• -XX:+UserParNewGC
• +UseConcuMarkSweepGC时默认开启
• Serial收集器的多线程版本
• 默认线程数与CPU数目相同
• -XX:ParrallelGCThreads指定线程数目
对比Serial收集器如下图所示:
        这里写图片描述
Parallel Scavenge收集器
• 新生代并行收集器
• 采用Copy算法
• 主要关注的是达到可控制的吞吐量,“吞吐量优先”
• -XX:MaxGCPauseMillis -XX:GCTimeRAtion两个参数精确控制吞吐量
• -XX:UseAdaptiveSizePolicy GC自适应调节策略
• Server模式的默认新生代收集器
Serial Old收集器
• Serial的老年代版本
• Client模式的默认老年代收集器
• CMS收集器的后备预案,Concurrent Mode Failure时使用
• -XX:+UseSerialGC开启此收集器
Parallel Old收集器
• -XX:+UseParallelGC -XX:+UseParallelOldGC启用此收集器
• Server模式的默认老年代收集器
• Parallel Scavenge的老年代版本,使用多线程和”mark-sweep”算法
• 关注点在吞吐量以及CPU资源敏感的场合使用
• 一般使用Parallel Scavenge + Parallel Old可以达到最大吞吐量保证

CMS收集器

并发低停顿收集器
• -XX:UseConcMarkSweepGC 开启CMS收集器,(默认使用ParNew作为年轻代收集器,SerialOld作为收集失败的垃圾收集器)
• 以获取最短回收停顿时间为目标的收集器,重视响应速度,希望系统停顿时间最短,会和互联网应用。
四个步骤:
• 初始标记 Stop the world: 只标记GC roots能直接关联到的对象,速度很快。
• 并发标记:进行GC roots tracing,与用户线程并发进行
• 重新标记 Stop the world:修正并发标记期间因程序继续运行导致变动的标记记录
• 并发清除
对比serial old收集器如下图所示:
        这里写图片描述
CMS有以下的缺点:
• CMS是唯一不进行compact的垃圾收集器,当cms释放了垃圾对象占用的内存后,它不会把活动对象移动到老年代的一端
• 对CPU资源非常敏感。不会导致线程停顿,但会导致程序变慢,总吞吐量降低。CPU核越多越不明显
• 无法处理浮动垃圾。可能出现“concurrent Mode Failure”失败, 导致另一次full GC ,可以通过调整-XX:CMSInitiatingOccupancyFraction来控制内存占用达到多少时触发gc
• 大量空间碎片。这个可以通过设置-XX:UseCMSCompacAtFullCollection(是否在full gc时开启compact)以及-XX:CMSFullGCsBeforeCompaction(在进行compact前full gc的次数)

G1收集器

G1算法在Java6中还是试验性质的,在Java7中正式引入,但还未被广泛运用到生产环境中。它的特点如下:
• 使用标记-清理算法
• 不会产生碎片
• 可预测的停顿时间
• 化整为零:将整个Java堆划分为多个大小相等的独立区域
• -XX:+UseG1GC可以打开此垃圾回收器
• -XX:MaxGCPauseMillis=200可以设置最大GC停顿时间,当然JVM并不保证一定能够达到,只是尽力。
        这里写图片描述

GC日志简介

       这里写图片描述
• 第一个箭头:35592K->1814K(36288K),箭头指向的是新生段的内存占用情况;- 第二个箭头:38508K->7792K(520256K),箭头指向的是回收后的内存占用情况。
• 垃圾收集停顿时间:0.0336
老年代使用建议
• Parallel GC(-XX:+UseParallel[Old]GC)
• Parallel GC的minor GC时间是最快的, CMS的young gc要比parallel慢, 因为内存碎片
• 可以保证最大的吞吐量
• 确实有必要才改成CMS或G1(for old gen collections)
开发建议
• 小对象allocate的代价很小,通常10个CPU指令;收集掉新对象也非常廉价;不用担心活的很短的小对象
• 大对象分配的代价以及初始化的代价很大;不同大小的大对象可能导致java堆碎片,尤其是CMS, ParallelGC 或 G1还好;尽量避免分配大对象
• 避免改变数据结构大小,如避免改变数组或array backed collections / containers的大小;对象构建(初始化)时最好显式批量定数组大小;改变大小导致不必要的对象分配,可能导致java堆碎片
• 对象池可能潜在的问题
• 增加了活对象的数量,可能增加GC时间
• 访问(多线程)对象池需要锁,可能带来可扩展性的问题
• 小心过于频繁的对象池访问

Java7、8带来的一些变化

       Java7带来的内存方面的一个很大的改变就是String常量池从Perm区移动到了Heap中。调用String的intern方法时,如果存在堆中的对象,则会直接保存对象的引用,而不会重新创建对象。
       Java7正式引入G1垃圾收集器用于替换CMS。
       Java8中,取消掉了方法区(永久代),使用“元空间”替代,元空间只与系统内存相关。
       Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重 复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。可以使用 -XX:+UseStringDeduplication这个JVM参数来试一下这个特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值