
一、背景

接着上一篇文章:性能测试能力提升-长连接、短连接、连接池,本篇文章,我们将主要介绍JVM GC原理相关的知识:
什么是JVM
什么是GC
JVM体系结构中GC作用区域
JVM GC分代管理和流转过程
JVM GC收集器介绍

二、JVM GC原理

2.1 什么是JVM
什么是JVM
名词解释:JVM是Java Virtual Machine(Java虚拟机)的缩写;
JVM的作用:
Java语言的一个非常重要的特点就是与平台的无关性,可以跨平台执行。而使用Java虚拟机是实现这一特点的关键。
一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
2.2 什么是GC
名词解释:
Garbage Collections 字面意思是垃圾回收器,释放垃圾占用的空间。
GC的作用:
让创建的对象不需要像c、c++那样delete、free掉 。对于c、c++的开发人员来说内存是开发人员分配的,也就是说还要对内存进行维护和释放。
对于Java程序员来说,一个对象的内存分配是在虚拟机的自动内存分配机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,而且不容易出现内存泄露和内存溢出问题,但是,如果出现了内存泄露和内存溢出问题,而开发者又不了解虚拟机是怎么分配内存的话,那么定位错误和排除错误将是一件很困难的事情。
GC工作原理:
当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”(内存溢出)的错误,Java应用将停止。
2.3 JVM体系结构中GC作用区域

堆(heap):
它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
1.堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
2.Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
3.TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
方法区(Method Area)
1.在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。
2.方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域。同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

三、JVM GC分代管理和流转过程

3.1 GC分代管理策略

GC分为永久代(Perm区域,对应方法区)、新生代(Eden,S0,S1区域)、老年代(Old区域)。
永久代:存放的为一些class的信息、常量、静态变量等数据。当系统中要加载的类、反射的类和调用的方法较多时可能占满,触发GC。
新生代:存放生命周期较短的对象,GC频率高。
老年代:存放新生代中经历多次GC依然存活的对象,新建的对象也有可能直接在老生代分配(比如大对象),取决于具体GC的实现。GC频率相对较低。
GC类型主要分为:
MinorGC/YoungGC:只是新生代(Eden,S0,S1)的垃圾收集
MajorGC/OldGC:只是老年代的垃圾收集。
FullGC:收集整个java堆(新生代、老年代)和方法区(永久代)的垃圾收集。
注意:只有CMS收集器会有单独收集老年代的行为,其他收集器均无此行为,老年代空间不足会直接触发Full GC。而针对新生代的MinorGC,各个收集器均支持。
总之,单独发生收集行为的只有新生代,除了CMS收集器,都不支持单独回收老年代。这也是为什么我们经常提到的只有Young GC和Full GC,而很少提到Old GC的原因。
流转过程:
EdenSpace(Eden区一次Young GC)=》FromSpace(S0区一次Young GC)=》ToSpace(S1区一次Young GC)=》对象移至老生代=》老年代空间不足=》触发Full GC

3.2 触发Full GC的常见情况
1. System.gc()方法的调用:此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系统性能。建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
2. 老年代空间不足:如果某个(些)对象(原来在内存中存活的对象或者新创建的对象)由于以上原因需要被移动到老年代中,而老年代中没有足够空间容纳这个(些)对象,那么会触发一次Full GC。
3. 永久代空间不足:Permanet Generation(方法区)中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。
Full GC的执行时间比Minor GC要长很多。因此,如果Full GC花费了太多的时间(比如超过1秒),一些连接的部分可能会发生超时错误。要尽量减少触发Full GC。
3.3 JVM GC收集器介绍

各类收集器解释:
单线程GC收集器包括Serial和SerialOld这两款收集器,分别用于年轻代和老年代的垃圾收集工作;
ParNew收集器其实就是Serial收集器的多线程版本;
Parallel Scavenge收集器类似ParNew收集器,Parallel Scavenge收集器更关注系统的吞吐量;
Parallel Old是Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器;
G1垃圾收集器:Jdk7的新特性之一,未来计划替代CMS。目前CMS依然是主流;

常用组合搭配:
Serial + SerialOld,只能适用于单核CPU,单机程序;
ParNew + CMS ,在意服务器的响应时间,希望系统停顿时间最短,适用于互联网应用;
Parallel Scavenge + Parallel Old , 在意服务器吞吐量,适用于后台计算任务服务器;
G1垃圾收集器:Jdk7的新特性之一,未来计划替代CMS。目前CMS依然是主流;
END
以上就是本次的全部内容,如果对你有帮助,麻烦点个赞+收藏+分享,你的支持就是作者更新最大的动力~
下一篇文章,我们将主要介绍以下方面的知识:
GC监控相关知识
GC优化相关知识
最后,欢迎加入杨叔的测试交流群,沟通交流日常测试工作相关内容,三个臭皮匠赛过诸葛亮,2022一起学习进步!





