java garbage collection
什么是garbage collection
在c/c++中,程序员需要自己手动分配内存和释放内存,这样会让内存分配很困难。稍不注意没有回收内存,就会造成内存溢出。
而java贴心的为程序员准备了自动回收垃圾的垃圾桶,当有对象不被需要了,gc就会识别不需要的对象,将其回收。gc回收器是一个在后台一直运行的守护线程,当jvm启动,gc跟着运行,jvm停止,gc跟着停止。
gc回收机制
mark(标记对象)
在堆中找到所有存活的对象,这个进程从’roots’开始,包括线程栈,静态变量,JNI代码的特殊引用以及其他的区域的存活对象将被找到。
如果引用来自gc root,则对对象的引用只能阻止当前对象被垃圾回收。
垃圾回收器将会标记可达的对象为存活对象(live),标记不可达的对象为死亡对象。如果这些死亡对象(dead)仍然可达,它会导致内存泄漏。
标记的工作与存活对象和引用的数量呈线性相关,与对象大小无关。将1000个10k的对象与1000个10m的对象标记所需时间是相同的。
在并发标记中,所有可达的对象将标记为存活,并且这个对象图也会被改变,这会导致经典的并行标记竞赛。应用程序可能会将一个没有被发现的引用移动进已经被访问过的被标记的对象,使这个引用指向这个对象,这样会导致堆损坏。这个对象将会被垃圾回收,但是这个引用仍然会存在。
以上并发标记问题可以通过’write barrier’来解决。'write barrier’会捕获可能会被标记错过的对象引用的改变,通过这个信息,标记会重新访问所有被改变的引用和跟踪新的改变。但是需要注意的是,垃圾收集器对改变很敏感,所做的工作会随着改变率的增加而增加。
sweep(清理)
垃圾收集器会扫描整个堆来验证死亡(dead)对象的位置和跟踪它们的位置。
不像标记,sweep是与堆的大小呈线性相关,而不是存活集合的大小。如果应用程序使用一个存活对象很少的大的堆,sweep也会扫描整个堆。
sweep会移除这些死亡对象,释放出内存。但是sweep只有清除,会让内存中出现很多碎片。分配内存每次是使用连续空间分配的内存,如果内存碎片很多,新的内存分配会变慢。
compact(压缩)
compact会将存活的对象重新放入一个连续的空间中,随着对象被移动,垃圾收集器必须修理所有的在线程中的存活对象的引EW用。这个过程被称为’remapping’,这和存活对象集合的大小呈线性相关。
增量压缩被用在Oracle G1 和 the Balanced Collector from IBM。这个技术假设某个内存区域比其他的更活跃,这个算法跟踪跨区域记忆集合(即哪个区域指向哪个区域).这允许垃圾收集器同一时刻只压缩单个区域,并且重新remapping潜在引用时只扫描它们指向的区域。大堆有很少的不活跃的区域,指向单个区域的区域数与堆大小呈线性相关,这种类型的压缩犯法能随着堆大小的平方而增长。
compact能将在清理死亡对象之前,将存活对象集中,这样清理死亡对象之后释放的内存是连续的,便于新的内存分配。
copy
将内存划分为两块,每次只用一块。当前这一块内存用完了,就将存活对象移动到另一块,然后对当前这一块执行清理操作,这样内存释放也不会产生碎片。但是这样浪费了一半的内存空间,并且这种算法需要频繁gc。
gc回收算法
mark-sweep (标记-清理)
先使用将死亡对象标记(mark),然后清理对象(sweep)。
mark-compact (标记-压缩)
先使用将死亡对象标记(mark),然后将存活对象压缩到一个连续的空间(compact),最后清理标记的对象。
mark-copying (复制)
先将内存划分为两个区域,然后所有对象都在一个区域。标记死亡对象,将存活对象移动到另一个区域,最后清理这个区域中标记的对象。
gc的分代收集
分代收集一般使用mark-compact回收算法。
Young Generation(新生代)
这是所有新对象分配内存和老化的地方。
新生代内存空间又被分为eden和survivor两个内存空间(eden:survivor = 8:2)。
eden(伊甸园),是新对象被创建的地方(分配内存)。
survivor(幸存地),其中分为大小一样的S0和S1,它们被用来替换使用。
Old Generation(老年代)
老年代用于存放新生代多次回收依然存活的对象,如缓存对象。当老年代满了的时候就需要对老年代代进行回收,老年代代的垃圾回收称作Major GC。
Permanent Generation(永久代)
类和方法的元数据就是存储在Permanent Generation区中的。如果类不常用,就有可能被垃圾回收。大多数JVM没有这一代。
gc分代回收过程
首先,在eden区分配对象,survivor区的两个分区分别被标记为from和to,可以看出,已经有一次垃圾回收了。
当生成的对象占满新生代的空间时,此时Yong gc被称为Minor GC。这时就可以进行垃圾回收了。
第一次垃圾收集时,s0和s1都为空,将s0(s0为To Survivous,s1为From Survivous)作为To Survivous(存储eden中幸存的对象)。
对死亡对象进行标记,将存活对象移动到s0中。
在一次垃圾收集后,eden中幸存的对象会被移动到s0中,s0和s1互换标签。
然后,当eden区域又一次满了,就开始第二次垃圾收集。
这次垃圾收集波及到eden和survivor区,这两个区中的死亡对象会被标记。
将第二次垃圾收集eden中幸存的对象和s0中幸存的对象(s0中幸存的对象再一次被垃圾回收)迁移到s1中,所有幸存对象年龄增加1岁(第一次幸存的对象年龄为1),如果还有剩余的幸存对象s1放不下,将s1中剩余的对象放入Old Generation区中。
然后将eden和s0清空,s0和s1再互换标签。
再进行一次垃圾回收,s0又成为新的To Survivous。
又将eden和s1中幸存的对象移动到s0中,幸存对象年龄再增加1岁。
再经过一次垃圾回收后,s1中的有些幸存对象年龄已经很老了,需要移动到老年代中去。
随着minor GC持续进行,幸存对象年龄慢慢变老,更多的幸存对象被移动到老年代中去。
最终,对老年代的对象进行压缩。等老年代满了之后,触发Major GC
hotspot四种垃圾回收器
Serial Garbage Collector(串行垃圾收集器)
所有垃圾收集事件都在一个线程中串行进行。当这个gc运行时,会让应用程序线程阻塞,等垃圾回收完后才运行,对于多线程应用程序来说最好不要用它。
对于那些对时间要求不高,又在客户端计算机上运行的应用程序来说确是首选垃圾回收器。它也可以用来理解垃圾收集器的工作原理。
使用方法:
1 java -XX:+UseSerialGC -jar test.java
Parallel Garbage Collector(并行垃圾收集器)
Minor GC时使用多线程进行,Major GC和老年代压缩时使用单线程。Parallel的比较老的版本在Major GC和老年代压缩时使用多线程。
这是JVM默认的GC,但是当执行GC也会阻塞其他应用程序线程。我们可以自定义垃圾收集器的最大线程数,暂停时间,吞吐量和占用空间(堆大小)。
使用方法:
1 2 3 4 5 6 -XX:UseParallelGC : 使用的垃圾收集器 -XX:ParallelGCThreads= : 垃圾收集器的最大线程数量 -XX:MaxGCPauseMills= : 垃圾收集器最大暂停时间 -XX:GCTimeRatio= : 垃圾收集器吞吐量 -Xms : 堆大小 java -XX:+UseParallelGC -XX:ParallelGCThreads= -XX:MaxGCPauseMills= -XX:GCTimeRatio= -Xms -jar test.java
CMS Garbage Collector(Concurrent Mark Sweep)
并行标记清理,与Parallel一样,在Minor GC时使用多线程,Major GC也是使用多线程。但是CMS是与应用程序并发运行,尽量减少垃圾收集时应用程序被停止的状况。它不使用mark-compact回收算法,而是使用mark-sweep回收算法。
上面两个垃圾收集器开始GC时都会暂停应用程序线程,但是CMS就会让垃圾收集器与应用程序并行和并发。
但是也会出现一些问题,执行垃圾收集器时,垃圾收集器的响应速度会比较慢,但是应用程序的响应也不会停。
注意事项:当这个并发过程正在运行时,如果显示的调用GC(System.gc()),将会返回Concurrent Mode Failure / Interruption
如果CMS垃圾回收器运行的时间花费了总时间的98%以上,而堆只有2%以下被回收,这将会抛出OutOfMemoryError异常。增加-XX:-UseGCOverheadLimit选项这个错误将被禁止。
使用方法:
1 2 3 -XX:UseParNewGC : 使用CMS垃圾收集器 -XX:UserGCOverheadLimit : 取消垃圾回收器回收时间限制 java -XX:+UseParNewGC -XX:UserGCOverheadLimit -jar test.java
G1 Garbage Collector(Garbage First)
这是CMS的扩展,它被设计用于大内存空间的运行在多处理器机器上的应用程序。在JDK7.4以后加入的。
当需要性能高效时,它将被用来替换CMS垃圾收集器。
G1 collector将堆分为大小相等的堆区的集合,每一个堆区拥有连续的虚拟内存范围。
当执行垃圾回收时,G1显示了一个并发的全局标记阶段,以确定整个堆中对象的活动性。在标记完成之后,G1会知道哪些堆区大部分是空的,它会首先收集这些区域,通常会产生大量空闲空间。
使用方法:
1 java -XX:+UseG1GC test.java
文献引用
java7 gc
understand gc
garbage-collection-java
What is Java Garbage Collection
JVM Garbage Collectors
深入探究 JVM | 初探 GC 算法
深入理解JVM的内存结构及GC机制
How Garbage Collector works in Java
Types of Java Garbage Collectors
JVM performance optimization, Part 3: Garbage collection
Tuning Garbage Collection with the 5.0 JVM
JVM GC原理
JVM内存结构–新生代及新生代里的两个Survivor区