美文网首页
Jvm内存结构与垃圾回收

Jvm内存结构与垃圾回收

作者: flying_bear | 来源:发表于2020-11-02 09:43 被阅读0次


    1. jvm运行时内存结构

    jvm运行时内存结构图

            程序计数器:当前线程通过程序计数器来辅助执行代码,《深入理解JVM》书作者称为字节码行号指示器。字节码执行器通过改变程序计数器的值来选取下一条执行指令。分支、跳转、循环、异常处理、线程恢复等基本功能依赖于程序计数器实现。注意:每条线程拥有一个独立的程序计数器,多个线程之间互不影响。也因此,多线程可以互相切换执行。程序计数器只记录java字节码的行号,不会记录native方法的行号。程序计数器不会OOM。

            虚拟机栈:虚拟机栈同样是线程私有。每个方法执行时会创建一个方法栈,用于存放方法执行过程中的各种局部变量。虚拟机栈最大容量可以用-Xss256k参数配置为256k,当方法栈帧过深,所占用的内存产国256k时,就会抛出StackOverFlow异常。

            本地方法栈:本地方法栈与虚拟机栈功能类似,最大区别是本地方法栈为本地方法(native)服务,虚拟机栈为java方法服务。

            java堆:线程共享区域,永远存放java对象。java堆是垃圾收集器主要管理区域。一般可分为新生代,老年代。其中新生代又可以分为Eden区,From survivor区和To survivor区。java堆中可能划分出多个线程私有的分配缓冲区(TLAB),用于存放对象。java堆可以用参数-Xms和-Xmx配置最大值和最小值。另注:java堆的最大容量不超过16G。Java堆内存不足时抛出OOM。

            方法区:线程共享区域,用于存放已被虚拟机加载的类信息,静态变量,常量,即时编译器编译后的代码等。方法区通常不进行内存回收,因此有人称为永久代。注意:最新的HotSpot虚拟机虚拟机里,已加载的类可以被卸载,不再被引用的常量也会被回收。关于是否要对类型进行回收,HotSpot虚拟机提供了一Xnoclassgc 参数进行控制,还可以使用-verbose:class以及-XX: +TraceClass一Loading、-XX:+TraceClassUnLoading查看类加载和卸载信息。在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及oSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

            运行时常量池:方法区的一部分,在类加载后会保存类的一些符号引用。运行时常量池可以在运行时往里面放入常量,例如String的intern方法。注意:0-127的int数字会被保存在常量池,因此比较0-127的数字地址用于相等。

            直接内存:直接内存属于堆外内存,通常使用native方法分配,也不受垃圾回收器影响。注意:运维在给服务配置内存参数时(例如容器),经常只考虑堆内存,忽略堆外内存,导致运行时出现OOM。

    2. java对象的内存布局

            在Hotspot虚拟机中,对象在内存中的布局可以分为3部分:对象头(Header)、示例数据(Instance Data)和对齐填充(Padding)。

    java对象头存储信息Mark World

            Hotspot虚拟机对象头包括两部分,第一部分用于保存运行时数据,如哈希吗(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID,偏向时间戳等。这部分数据在32位和64位计算机中分别占用大小为32bit和64bit。另一部分数据是类型指针,指向当前对象的类元数据。如果对象是java数组,对象头中还有一块用于保存数组长度

    3. 垃圾收集器和内存回收策略

         判断对象可被回收

            引用计数法:给对象增加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时,计数器减1. 优点:实现简单,判定效率高缺点:无法解决环形引用的问题。例如当只剩下对象A引用B,对象B引用A时,对象AB都应当被回收,然而在引用计数法下却判定不能回收。

            可达性分析:从“GC ROOT”对象作为起始点向下搜索,搜索走过的路径成为引用链(Reference Chain),当一个对象到“GC ROOT”没有任何引用链时,即判定为可回收。在java语言中,可被作为“GC ROOT”的对象有以下几种:

                 虚拟机栈中引用的对象

                方法区中类静态属性引用的对象

                方法区中常量引用的对象

                本地方法栈中JNI(native方法)引用的对象

            引用的四种类型

                强引用:正常引用

                软引用:在OOM之前会被回收的引用

                弱引用:下一次GC就会被回收的引用

                虚引用:完全无影响的引用。唯一的作用实在对象被回收后收到一个系统通知。

            finalize方法的作用场景:java对象被判断可被回收时,会对再次进行判定,如果对象的finalize方法没有被覆盖,或者已经被执行时,判定为无需执行finalize方法。对于被判定为需要执行finalize方法的对象,会被放进F-QUEUE队列,由一个低优先级的线程执行每个对象的finalize方法。在finalize方法里,对象有最后一次逃逸死亡的机会。但是如果finalize方法执行缓慢或被阻塞,F-QUEUE的其他对象会处于等待状态无法被回收,导致垃圾回收系统崩溃。

        垃圾回收算法

            标记-清除法:先标记,再清除。缺点:执行效率低,产生大量不连续的内存碎片。

            复制算法:将内存分为两块,一块用完后,把存活对象复制到另一块上,原来的一块进行回收。常用于新生代。由于新生代中98%的对象都是朝生夕死的,因此新生到代被分为一块较大的Eden区和两块较小的survivor区。每次使用Eden区和其中一块survivor区,新生代gc时,把存活的对象复制到另一块survivor区,最后再清理原来的Eden区和survivor区。Hotspot虚拟机中Eden区和survivor区默认比例为8:1:1。当每次新生代垃圾回收存活的对象超过一块survivor容量是,多出来的对象将进入老年代。

            标记-整理算法:类似于标记-清楚,但不同的是,在标记万可被回收对象后,存活对象会向一侧平移靠拢,然后回收边界以外的空间。标记-整理算法适用于对象存活率较高的场景,因此常用于老年代

        垃圾回收器

           串行收集器: Serial收集器,单线程执行,执行时需要“stop the world”暂停所有其他线程。

            并行收集器:Parallel收集器,CMS(Concurrent Mark Sweep)收集器,G1(Garbage First)收集器。并行收集器缩短了用户线程停顿的时间,但依然无法完全消除。

            ParNew收集器:Serial收集器的多线程版本,只有它和Serial收集器可以和CMS配合使用。

            Parallel Scavenge收集器:新生代并行多线程收集器,使用复制算法。他和CMS最大的额区别在于:CMS着眼于尽可能缩小用户停顿时间,适合于用户服务器。而Parallel Scavenge收集器着眼于尽可能提高吞吐量(吞吐量=用户代码运行时间/(用户代码运行时间+垃圾回收时间)),高吞吐量可以高效利用CPU,迅速完成任务,因此适用于后台运算,不需要太多交互的任务。Parallel Scavenge收集器提供了-XX:MaxGCPauseMilis参数配置最大垃圾回收时间(大于0的毫秒数)和-XX:GCTimeRatio参数配置吞吐量大小(大于0小于100的整数)。如果-XX:GCTimeRatio=19,那么最大垃圾回收时间比例为1/(1+19)=5%,默认值是99,因此最大垃圾回收时间为1/(1+99)=1%。  Parallel Scavenge收集器还提供了另外一个参数-XX:UseAdaptiveSizePolicy,这个开关开启后,虚拟机会根据当前系统状况自动调整新生代,老年代的一些参数以提供最合适的垃圾回收时间和吞吐量。称为自适应策略。

        Serial Old收集器:Serial收集器的老年代版本,单线程执行,使用标记整理算法。

        Parallel  Old收集器:Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。通常配合新生代选择 Parallel Scavenge收集器一起使用达到最优效果。

        CMS收集器:CMS收集器是一款着眼于获取最短回收停顿为目标的收集器。CMS垃圾回收有四个步骤:初始标记,并发标记,重新标记,并发回收。其中初始标记和重新标记会出发“stop the world”。CMS缺点:一、对CPU资源敏感。CMS默认回收线程数=(CPU数量+3)/4,在CPU核心数小于等于4个时,由于回收线程占用大量CPU资源,因此对用户影响很大。二、CMS无法处理浮动垃圾,可能导致“Concurrent Mode Failure”。原因是CMS需要在并发收集时,预留一部分空间给用户程序,在jdk5时,CMS收集器在老年代占用空间达到68%时就会被激活。这一阈值在jdk6被调整到92%。如果预留空间不足以给用户程序运行,就会导致“Concurrent Mode Failure”。这是会启动预案:启动Serial Old收集器重新收集老年代垃圾,导致停顿时间更长。所以-XX:CMSInitiatingOccupacyFraction设置太高,导致“Concurrent Mode Failure”反而降低性能。  三、标记清楚算法在回收后会导致大量内存碎片。当无法找到足够大的连续空间时,不得不提前触发一次FULL GC。为解决此问题,CMS提供了一个参数-XX:+UseCMSComapctAtFullCollection开关(默认开启)用于控制CMS在顶不住要要进行FULL GC时进行内存碎片整理。内存整理时无法并发,导致停顿时间变长。CMS还提供了一个参数-XX:CMSGCsBeforeCompaction用于设置多少不压缩的FULL GC后开启一次带压缩的。默认为0,即每次FULL GC都会内存碎片整理。

         G1收集器:不成熟的垃圾收集器。同样追求最短回收停顿,目标是性能优于CMS,还远未成熟。

    GC相关参数:

    内存分配策略:

        一、对象优先在Eden区分配

        二、大对象直接进入老年代。参数-XX:PretenureThreshold令大于这个参数的对象直接进入老年代。

        三、长期存活的对象进入老年代。对象在survivor区每熬过一次minor gc就年龄加1,当年龄大于-XX:MaxTenuringThreshold参数时,就会进入老年代。默认15岁。

        四、动态对象年龄判断。当survivor区所有相同年龄对象总和大于survivor区空间一半,所有大于该对象年龄的对象直接进入老年代。

        五、空间分配担保。正常情况新生代minor gc前,会检查老年代空间是否大于新生代所有对象空间。如果条件满足,minor gc很安全。如果不是,会检查HandlePromotionFailure值是否允许担保失败。如果允许,再检查老年代剩余空间是否大于历次进入老年代对象的平均大小,如果大于,将进行一次minor gc,尽管存在风险。如果小于,或者HandlePromotionFailure不允许,会进行Full GC。注意:在担保minor gc失败后,依然要改为Full GC。jdk6之后,HandlePromotionFailure参数无效,默认就会进行空间分配担保

    相关文章

      网友评论

          本文标题:Jvm内存结构与垃圾回收

          本文链接:https://www.haomeiwen.com/subject/cehfvktx.html