-
GC中的垃圾特指于内存中不会再使用的对象,垃圾回收有很多算法:
- 引用计数法
- 标记压缩法
- 复制算法
- 分代,分区的思想
-
引用计数法
- 古老而经典,但是有一些严重的问题
- 核心是当对象被引用的时计数器加1,引用失效时减1
- 问题:
- 无法处理循环引用的情况
- 每次进行加减操作比较浪费系统性能
-
标记清除法
- 分为标记和清除俩个阶段进行处理内存中的对象
- 弊端:
- 导致空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作小徐要低于连续的空间
-
复制算法*
- 核心思想是将内存空间分为俩块,每次只使用其中一块,垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中的所有对象,反复去交换俩个内存的角色,完成垃圾收集。
- java新生代中的from和to空间就是使用这个算法
-
标记压缩法*
- 标记压缩在标记清除法之上做了优化,把存活的对象压缩到内存一端(避免空间碎片问题),而后进行垃圾清理
- java老年代使用的是标记压缩算法
-
为啥新生代和老年代使用不同的算法?
- 年轻代对象因为生命周期短,每次有约90%以上对象的占用空间被回收,采用“复制-清除”算法清理,具体过程:
将新生代分为一个Eden空间和两个Survivor空间,默认Eden空间和Survivor空间的比例为8:1,对象分配到Eden和其中一个Survivor空间,回收时将存活的对象复制到另一个Survivor空间,然后将Eden空间和先前使用的Survivor空间清理。 - 老年代因对象生命周期较长,每次回收只有少部分对象没清理,如果使用“复制-清理”算法的话需要额外预留更多的空闲空间用于复制生存对象,( 例如,100M的老年代占用空间 每次能回收50% ,那么他需要预留50M的空间 内存使用上不经济 )所以回收时使用“标记-整理”算法。
- 年轻代对象因为生命周期短,每次有约90%以上对象的占用空间被回收,采用“复制-清除”算法清理,具体过程:
-
分代算法
- 呵呵,看上一个问题:为啥新生代和老年代使用不同的算法?
-
分区算法
- 被oracle收购后,提出的新算法,应用还不广泛,还在摸索,G1使用的就是这个算法
- 将整个内存分为N多个小的独立空间,每个小空间可以独立使用,这样细粒度的控制一次回收多少个小空间和那些小空间,而不是对整个空间进行GC,进而提升性能,减少GC的停顿时间
-
GC的停顿时间
- 为了高效的让垃圾回收器可以高效的执行,大部分情况下,会要求系统进入一个停顿的状态,停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态咋某一瞬间的一致性,也有益于更好的标记垃圾对象,因此在垃圾回收时,都会产生应用程序的停顿
-
对象如何进入老年代
- 对象首次创建时会被放在新生代的eden区,如果没有gc介入,则对象不会离开eden区,一般来讲,只要对象的年龄达到一定的大小,就会自动离开年轻代,进入老年代,对象的年龄是由对象经历gc的次数决定的,在新生代如果对象没有被回收则年龄加一,虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升到老年代,这个参数就是
-XX:MaxTenuringThreshold
默认情况下为15 - 另外,大对象,即新生代eden区无法装入时,也会直接进入老年代,jvm有个参数可以设置对象的大小超过指定的大小后,直接晋升老年代
-XX:PretenureSizeThreshold
,但是要注意TLAB区域有限分配空间,虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此会失去在老年代分配的机会
- 对象首次创建时会被放在新生代的eden区,如果没有gc介入,则对象不会离开eden区,一般来讲,只要对象的年龄达到一定的大小,就会自动离开年轻代,进入老年代,对象的年龄是由对象经历gc的次数决定的,在新生代如果对象没有被回收则年龄加一,虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升到老年代,这个参数就是
-
关于TLAB区
import java.util.HashMap; import java.util.Map; public class Test{ public static void main(String[] args) { //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 //这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会 //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB Map<Integer, byte[]> m = new HashMap<Integer, byte[]>(); for(int i=0; i< 5*1024; i++){ byte[] b = new byte[1024]; m.put(i, b); } } }
以上程序启动(注意加上jvm参数)之后你会看到老年代的使用率几乎为0,这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会,java的每个线程都是默认使用TLAB区的,如果要禁用,则加上jvm参数
-XX:-UseTLAB
,会看到老年代使用的内存大致为5M- TLAB区的全称是Thread Local Allocation Buffer,线程本地分配缓存,线程专用的内存分配区域,为了加速对象分配而生的,每个线程都有一个TLAB,来避免多线程冲突的问题,提高对象分配的效率,TLAB空间一般不会太大,当大对象无法再TLAB分配时,则会直接分配到堆上
-
-XX:+UseTLAB
使用TLAB -
-XX:+TLABSize
设置TLAB大小 -
-XX:TLABRefillWasteFraction
设置维护进入TLAB空间的单个对象大小,他是一个比例值,默认是64,即如果对象大于整个空间的1/64,则在堆创建对象 -
-XX:+PrintTLAB
查看TLAB信息,要与-XX:DoEscape Analysis
配合使用,禁用逃逸分析参数 -
-XX:ResizeTLAB
自动调整TLABRefillWasteFraction阈值 - jdk1.7之后,TLAB区是自动调整的,不建议修改
-
对象的创建流程
- 尝试栈上分配,能分配则在栈上分配
- 不能分配,尝试TLAB分配
- 对象大小未超过TLAB阈值,分配到TLAB
- 超过TLAB阈值,尝试在堆上分配,
- 超过PretenureSizeThreshold(jvm参数,可以设置对象的大小超过指定的大小后,直接晋升老年代),分配到老年代
- 否则在eden区分配
-
垃圾收集器
- 串行垃圾回收器
使用单线程镜像垃圾回收的回收器,垃圾收集时只有一个工作线程,设置方式:-XX:+UseSerialGC
,此时新生代和老年代都是使用串行垃圾回收器,对于并行能力较弱的计算机来说,串行的垃圾回收器会有更好的性能表现 - 并行垃圾回收器
- 使用多线程同时进行垃圾回收,对于并行能力较强的计算机而言,可以缩短垃圾回收所需的实际时间
- ParNew回收器是一个工作在新生代的垃圾收集器,他只是简单的将串行回收器多线程化,他的回收策略和算法与串行回收器一样
- 使用
-XX:+UseParNewGC
,新生代使用ParNew回收器,老年代还是使用串行回收器 -
-XX:ParallelGCThreads
可以指定工作线程数,一般和计算机的cpu个数相当,避免过多的线程影响性能
- 使用
- *新生代ParallelGC回收器,使用了复制算法的收集器,也是多线程独占形式的收集器,但ParallelGC回收器有个非常重要的特点--非常关注系统的吞吐量,提供了俩个非常关键的参数控制系统的吞吐量
-
-XX:MaxGCPauseMillis
设置最大垃圾收集停顿时间,虚拟机会把GC的停顿时间控制在这个值以内,但是该值设置过小,会导致GC频繁,从而增加了GC的总时间. -
-XX:GCTimeRatio
设置吞吐量大小,他是个0到100之间的数,默认99,那么系统将花费不超过1/(1+n)的时间用于垃圾回收,也就是1/(1+99),即1%的时间 - 另外还可指定
-XX:+UseAdaptiveSizePolicy
,打开自适应模式,这种模式下,新生代的大小,eden,from/to区的比例,以及晋升老年代的对象年龄参数会被自动调整,以达到在堆大小,吞吐量和停顿时间之间的平衡点
-
- *老年代ParallelOldGC,多线程回收器,也是一种关注吞吐量的回收器,使用了标记压缩法进行实现
-
-XX:+UseParallelOldGC
进行设置使用 -
-XX:+ParallelGCThreads
设置垃圾收集时线程数量
-
- CMS回收器(主流)
- 全称为Concurent Mark Sweep ,并发标记清除,使用标记清除法,主要关注系统停顿时间
- 使用
-XX:+UseConcMarkSweepGC
进行设置使用 - 使用
-XX:ConcGCThreads
设置并发线程数量 - CMS并不是独占的回收器,在CMS回收的过程中,应用程序仍然在不停的工作,同时会有新的垃圾产生,所以在使用CMS的时候应确保应用程序的内存足够可用,CMS不会等到应用程序饱和的时候才去回收垃圾,而是在某一阈值的时候开始回收,可以使用
-XX:CMSInitiatingOccupancyFraction
来指定,默认是68,即当老年代的空间使用率达到68%的时候,会执行CMS回收,如果内存使用率增长的很快,在CMS执行的过程中,已经出现了内存不足的情况,此时CMS回收就会失败,虚拟机将启动老年代串行回收器进行垃圾回收,这会导致应用程序中断,知道垃圾回收完成后才会正常工作,这个过程GC的停顿时间可能较长,所以-XX:CMSInitiatingOccupancyFraction
参数的设置要根据实际的情况 - 标记清除法有个缺点是产生空间碎片,CMS有个参数:
-XX:+UseCMSCompactAtFullCollection
可以使CMS回收完成之后进行一次碎片整理,-XX:CMSFullGCsBeforeCompaction
可以设置进行多少次CMS回收后,对内存进行一次压缩
- G1回收器(发展中)
- Garbage-First是jdk1.7中提出的垃圾回收器,从长期目标来说是为了取代CMS回收器,有独特的垃圾回收策略,G1属于分代垃圾回收器,区分新生代和老年代,依然有eden和from/to区,它并不要求整个eden区或者新生代,老年代的空间都连续,它使用了分区算法
- 并行性:G1回收期间可以多线程同时工作
- 并发性:G1拥有与应用程序交替执行能力,部分工作可以与应用程序同时执行,在整个gc期间不会完全阻塞应用程序
- 兼顾新生代和老年代一起工作,之前的垃圾收集器他们或者在新生代或者老年代工作,这是一个很大的不同,
- 空间整理:G1在垃圾回收过程中,不会像CMS那样若干次GC之后需要进行碎片整理,G1采用了有效复制对象的方式,减少空间碎片
- 可预见性:由于分区的原因,G1可以只选取部分区域进行回收,缩小了回收的范围,提升了性能
- 使用
-XX:+UseG1GC
配置使用 - 使用
-XX:MaxGCPauseMillis
指定最大停顿时间 - 使用
-XX:ParallelGCThreads
设置并行回收的线程数量
- 串行垃圾回收器
java 虚拟机(jvm)垃圾回收(GC)算法
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 通过这篇文章你能知道的问题: 1.如何判断对象是活着还是死去? 2.在Java语言中,可作为GCRoots的对象有...