本篇文章,我们将主要介绍JVM GC原理相关的知识:
什么是JVM什么是GCJVM GC收集器介绍JVM体系结构中GC作用区域JVM GC分代管理和流转过程什么是JVM
JVM概念
JVM是Java Virtual Machine(Java虚拟机)的缩写。
1.1、什么是Java虚拟机呢?Java虚拟机的好处是什么呢?
答:虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
简单来说JVM是用来解析和运行Java程序的。
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够**“一次编译,到处运行”**的原因。
图解JVM
从Java平台的逻辑结构上来看,我们可以从下图来了解JVM:
在这里插入图片描述从上图能清晰看到Java平台包含的各个逻辑模块,也能了解到JDK与JRE的区别,对于JVM自身的物理结构,请看下图:
什么是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应用将停止。JVM GC收集器介绍
有哪些垃圾收集器?
串行垃圾回收器(Serial)
JVM第一个垃圾收集器,JDK 1.3.1之前都是有这个收集器。可以作用新生代和老年代。
算法:新生代-使用复制算法,老年代-使用标记-整理算法。
特点:串行单线程、不支持并发、会导致"stop the world"。
//配置如下使用 -XX:+UseSerialGC并发垃圾回收器(parNew)
为了解决 serial拉圾收集器引起的停机问题,在serial基础上开发了多线程版本,但是parNew是针对client的版本。
特点:多线程、唯一能够与cms搭配使用的收集器;
算法:年轻代采用复制算法、年老代采用标记-整理
//强制指定使用ParNew; -XX:+UseParNewGC //指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同; -XX:ParallerGCThreads并行垃圾收集器(Parallel Scavenge)
Parallel Scavenge是后个多线程新生代收集器,使用的算法是复制算法。实现方式是当垃圾达到一个可控制的吞吐量(Throughput)则启动垃圾回收。
特点:作用于新生代、老年代由Serial搭配使用,可以有效的减少停顿时间提升用户体验。
算法:新生代复制算法,老年代标记整理
//注意:启动后不需要手工指定新生代的大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了 -XX:+UseAdaptiveSizePolicy //使用Parallel收集器+老年代串行 -XX:+UseParallelGC //启用并行压缩 -XX:+UseParallelOldGC。并发垃圾回收器 CMS(Concurrent Mark Sweep)
cms主要是解决停顿时间的问题而开发的一种,低停顿、并发收集器,基于标记清除,可以作用于新生代和老年代。
CMS收集器的运行过程分为下列4步:
初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
并发清除:对标记的对象进行清除回收。
特点:作用于新生代、老年代、代停顿、并发收集;
算法:标记-清除;
缺点:无法处理浮动垃圾、对CPU资源非常敏感、会造成空间碎片化;
//新生代使用并行收集器,老年代使用CMS+串行收集器 -XX:+UseConcMarkSweepGC //设定CMS的线程数量 -XX:ParallelCMSThreadsG1垃圾回收器(G1)
特点:作用于新生代、老年代、空间连续减少垃圾碎片、缩小回收范围减少全局停顿、并发收集;
算法:分区算法;
缺点:无法处理浮动垃圾、对CPU资源非常敏感、会造成空间碎片化;
G1收集器的阶段分以下几个步骤:
1、初始标记(它标记了从GC Root开始直接可达的对象);
2、并发标记(从GC Roots开始对堆中对象进行可达性分析,找出存活对象);
3、最终标记(标记那些在并发标记阶段发生变化的对象,将被回收);
4、筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)。
//启用G1垃圾回收器 -XX:+UseG1GC
查看jdk所用内存垃圾回收器
通过命令:
java -XX:+PrintCommandLineFlags -version
堆大小的计算?
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分代管理和流转过程
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触发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。
希望本文对你有所帮助~~如果对软件测试、接口测试、自动化测试、面试经验交流感兴趣可以私聊我。免费领取最新软件测试大厂面试资料和Python自动化、接口、框架搭建学习资料!技术大牛解惑答疑,同行一起交流。