查看当前jvm使用的垃圾收集器的方法:

[root@centos7 ~]# java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=30110656 -XX:MaxHeapSize=481770496 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
java version "1.8.0_172"
Java(TM) SE Runtime Environment (build 1.8.0_172-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.172-b11, mixed mode)


我们看看JVM的内存划分,上图是我从jvisualvm的截图,从中可以直观的看到jvm堆的内存划分为Metaspace,old,eden,s0和s1的内存区域。

Metaspace属于永久代,它是用来存储元数据,例如类的数据结构,字符串常量等,垃圾收集一般不发生在此区域。永久代的垃圾回收主要包括类型的卸载和废弃常量池的回收。当没有对象引用一个常量的时候,该常量即可以被回收。而类型的卸载更加复杂。必须满足一下三点,该类型的所有实例都被回收了,该类型的ClassLoader被回收了,该类型对应的java.lang.Class没有在任何地方被引用,在任何地方都无法通过反射来实例化一个对象

Old属于老年代,存放的是生命周期比较长的对象,此区域发生的垃圾回收称为Major GC,发生的时间相对较长,会暂停应用,因此应避免此区发生频繁的GC。Full GC同时发生于老年代和年轻代。

Eden属于年轻代,同时年轻代还包括S0和S1的两个Servivor区。java中新创建的对象都存于此区域,也是垃圾收集器重点关注的区域,此区域的对象生命周期大部分较短,垃圾收集比较的频繁,此处的GC称为Minor GC。从图中还观察到一个特点,s0与s1区总有一个区域是空的,这是因为年轻代使用的标记复制算法,eden区中经过回收后存活的对象会先复制到s0,当下次回收时,会将eden区存活的对象和s0中存活的对象一起复制到s1区,然后清空s0区。当再次发生回收时,再将eden区和s1区存活的对象复制到s0区,再清空s1区,这样循环复制,直到其中一个区满后,然后将存活的对象送到老年代中。

下图是jvm的内存参数设置

几种垃圾收集器的比较

分代垃圾收集的一大优点是,每个分代都可以依据其特性使用最适当的垃圾收集算法。新生代通常使用速度快的垃圾收集器,因为Minor GC频繁。这种垃圾收集器会浪费一点空间,但新生代只是java堆中的一小部分,所以不是什么大问题。老年代通常使用空间效率较高的收集器,因为老年代占用了大部分的堆空间,这种垃圾收集器不会很快,但Full GC不会很频繁,所以对性能也不会有很大的影响。

Stop-The-World

在新生代进行的GC叫做minor GC,在老年代进行的GC都叫major GC,Full GC同时作用于新生代和老年代。在垃圾回收过程中经常涉及到对对象的挪动(比如上文提到的对象在Survivor 0和Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”,导致系统全局停顿。Stop-The-World对系统性能存在影响,因此垃圾回收的一个原则是尽量减少“Stop-The-World”的时间。

不同垃圾收集器的Stop-The-World情况,Serial、Parallel和CMS收集器均存在不同程度的Stop-The-Word情况;而即便是最新的G1收集器也不例外。当gc线程在处理垃圾的时候,其它java线程要停止才能彻底清除干净,否则会影响gc线程的处理效率增加gc线程负担,特别是在垃圾标记的时候。

发表评论