垃圾收集算法、垃圾回收算法、java垃圾收集器
目录
1. 垃圾收集算法
1)引用计数法
2)根搜索法
2. 垃圾回收算法
1)复制算法
2)标记-清除算法
3)标记-整理算法
4)分代收集算法
3. java垃圾收集器
新生代GC器:
1)Serial垃圾收集器
2)ParNew垃圾收集器
3)Parallel Scavenge垃圾收集器
老年代GC器
1)Serial和Parallel Scavenge都有对应的老年代版本
2)CMS垃圾收集器
G1回收器
4.java对象的内存分配位置
5.java TLAB
6.java 何时触发GC
1) new gen对象什么时候会晋升到old gen中?
2) 什么时候会出现old gen空间不足(old gen空间不足时,会触发full gc)?
1. 垃圾收集算法
1)引用计数法
给对象添加引用计数器,当引用对象时计数器+1,引用失效时,计数器-1,当计数器等于0时,对象失效,内存可以被回收。
优点:实现简单高效。
缺点:对象之间的互相循环引用问题不好解决。
2)根搜索法
通过GC roots可达的对象路径称为引用链(reference chain),当一个对象没有引用链时(即从GC roots不可达)则视为不可用对象,内存可以被回收。java使用该算法进行垃圾收集。
哪些对象可以视为GC roots ?
a. 虚拟机栈中(即栈帧中的本地变量)的引用对象;
b. 本地方法栈中的引用对象;
c. 方法区(永久代)中的静态变量引用的对象和常量池中引用的对象
2. 垃圾回收算法
1)复制算法
将内存分为(大小相等)两部分,每次只使用其中一块进行内存分配,当内存使用完后,就出发GC,将存活的对象直接复制到另一块空闲的内存中,然后对当前使用的内存块一次性清除所有,然后转到另一块内存进行使用。
优点:简单,高效。
缺点:浪费内存,因为每次都有另一块内存空闲着。
2)标记-清除算法
分两步进行,第一步标记出可以回收的对象,第二步统一清理可以回收的对象内存。
缺点:首先标记和清除步骤效率都不高,其次会产生内存碎片。
3)标记-整理算法
类似于标记-清除算法,但是第二步进行内存回收时,将存活的对象向内存一端移动,达到消除内存碎片问题。
4)分代收集算法
java sun hotspot虚拟机将内存分为新生代(堆)、老年代(堆)、永久代(方法区、常量池、即时编译代码)几个区域,新生代主要使用基于复制算法的垃圾回收,老年代和永久代主要使用标记-整理算法进行垃圾回收。具体每个区域使用哪种垃圾回收算法还要视收集器的实现制约。
3. java垃圾收集器
新生代GC器:
1)Serial垃圾收集器:
单线程串行垃圾收集器,使用复制算法进行垃圾回收,GC时需要暂停所有用户线程,直到GC完成。
注:此处的串行垃圾收集器中的串行意义是指GC过程和用户线程执行过程是串行的,即GC过程中用户线程暂停,用户线程执行是,GC是不执行的。
2)ParNew垃圾收集器:
多线程串行垃圾收集器,Serial的多线程版本,其它特性同Serial。
3)Parallel Scavenge垃圾收集器:
类似于ParNew,但是该收集器关注的是cpu的吞吐量(throughput=user-cpu-time/total-cpu),通过参数-XX:GCTimeRation和-XX:MaxGCPauseMillis来控制吞吐量,是吞吐量优先的收集器,同样使用复制算法进行垃圾回收,GC过程需要暂停所有用户线程。
老年代GC器:
1)Serial和Parallel Scavenge都有对应的老年代版本:
Serial Old和Parallel Old垃圾收集器,它们与新生代的区别在于老年代区域的版本,采用标记-整理算法进行垃圾回收。
2)CMS垃圾收集器:
Concurrent Mark Sweep收集器,是真正意义上的多线程并行垃圾收集器,CMS在GC过程中的某些阶段用户线程是可以运行的,因此说它是真正意义上的并行垃圾收集器,前面介绍的垃圾收集器在GC过程中都要暂停用户线程,因此,视它们为与用户线程串行执行的垃圾收集器。CMS GC过程分四步完成:
a.初始标记:只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
b.并发标记:进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
c.重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
d.并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。
**G1回收器:
参考:http://www.importnew.com/15311.html
4. java对象的内存分配位置
1) 编译器通过逃逸分析,确定对象是在栈上分配还是在堆上分配。如果是在堆上分配,则进入选项2.
2) 如果tlab_top + size <= tlab_end,则在在TLAB上直接分配对象并增加tlab_top 的值,如果现有的TLAB不足以存放当前对象则3.
3) 重新申请一个TLAB,并再次尝试存放当前对象。如果放不下,则4.
4) 在Eden区加锁(这个区是多线程共享的),如果eden_top + size <= eden_end则将对象存放在Eden区,增加eden_top 的值,如果Eden区不足以存放,则5.
执行一次Young GC(minor collection)。
5) 经过Young GC之后,如果Eden区任然不足以存放当前对象,则直接分配到老年代。
5. java TLAB
JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。
也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。
关于对象分配的JDK源码可以参见JVM 之 Java对象创建[初始化]中对OpenJDK源码的分析。
6. java 何时触发GC
eden或old gen空间不足时会发出minor gc或full gc。
1) new gen对象什么时候会晋升到old gen中?
1. 多次minor gc后依然存活的对象,在存活年龄超过MaxTenuringThreshold阈值时会晋升到old gen中。
2. 在创建大对象/数组时,如果设置了PretenureSizeThreshold,超过大小的对象会直接在old gen中分配内存。
2) 什么时候会出现old gen空间不足(old gen空间不足时,会触发full gc)?
1. 创建大对象,大数组时,如果设置了PretenureSizeThreshold,超过阈值会直接在old gen中分配内存,此时可能会出现old gen内存不足。
2. minor gc完成后,可能会有对象满足了MaxTenuringThreshold设置的晋升年龄,需要晋升到old gen,此时可能会出现old gen内存不足。
3. 如果设置了新生代收集担保机制即HandlePromotionFailure关闭,则会在old gen中预留edu+survivor大小的空间作为担保,如果minor gc时,
old gen无法提供足够内存进行担保,会出现old gen内存不足。
4. 调用System.gc()时,会建议jvm进行full gc,但是并不保证一定会进行,大多数情况会执行full gc。因此,要慎用System.gc(),因为full gc
比较耗时,增加full gc次数会严重影响程序执行效率。-XX:+DisableExplicitGC可以关闭System.gc()调用。
5. perm gen内存不足时,也会出发full gc。
参考:
垃圾收集算法和垃圾回收算法:
http://www.2cto.com/kf/201401/272681.html
java垃圾收集器:
http://blog.csdn.net/chjttony/article/details/7883748 (推荐)
http://www.cnblogs.com/wrencai/articles/4232264.html
内存模型:
http://www.cnblogs.com/wrencai/articles/4232264.html
java对象都是分配在堆上吗:
http://blog.hesey.net/2011/07/object-allocation-on-non-heap.html
java逃逸分析和对象内存分配,TLAB:
http://blog.csdn.net/yangzl2008/article/details/43202969
java虚拟机参数: