JVM调优-内存模型与垃圾回收

7 篇文章 1 订阅

目录

二、JVM介绍

三、堆内存(HEAP)

四、1.8版本JDK

五、引用类型

六、垃圾回收算法

1、按基本回收策略分

2、按分区对待的方式分

3、按系统线程分

七、分代收集

1、Minor GC

2、Major GC

3、Full GC

 4、对象存活示意


     在提升硬件性能无法等比例地提升程序的性能和并发能力时(即增加硬件对程序的性能没有任何改善作用),这里有Java虚拟机的原因,也有程序本身的原因(此处不考虑选择的框架本身原因)。

     参考:

            https://blog.csdn.net/qzqanzc/article/details/81008598

            https://www.cnblogs.com/andy-zhou/p/5327288.html

一、JVM介绍

      这里提到的是jvm优化,则我们需要了解JVM的基本情况,即JVM的内存模型:

       

  1.  方法区:线程共享。用于存储已经被虚拟机加载的类信息,常量,静态变量等。这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。
  2.  java虚拟机栈:线程私有。每个方法在执行的时候也会创建一个栈帧,存储了局部变量,操作数,动态链接,方法返回地址等,遵从后进先出原则。如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
  3. 本地方法栈:线程私有。和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。也会抛出StackOverflowError 和OutOfMemoryError。
  4. Java堆:线程共享。被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
  5. 程序计数器:线程私有。每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Natice方法,则为空。程序控制流水的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

     PS:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。面向对象就是堆和栈的完美结合

二、堆内存(HEAP)

     这里写图片描述

      堆是JVM内存占用最大,管理最复杂的一个区域。唯一的用途就是存放对象实例,所有对象的实例以及数组都在堆上进行分配。jdk1.7以后,字符串常量从永久代中剥离出来,存放在堆中。

     虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation,1.8版本后是MetaData)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

     年轻代:所有新生成的对象首先都是放在年轻代的。年轻代分三个区,一个Eden区,两个Survivor区(一般而言)。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。Survivor的两个区是对称的,且Survivor区总有一个是空的,根据程序需要,Survivor区是可以配置为多个的(多于两个)。

     年老代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

     持久代:用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

三、1.8版本JDK

       这里写图片描述

       元数据区域取代了1.7版本及以前的永久代。元数据和永久代本质上都时方法区的实现,用于存储已经被虚拟机加载的类信息,静态变量等。

四、引用类型

       堆中存储的是引用类型实例,基本数据类型的大小是固定的(基本类型的包装类型的大小,但是引用类型的大小的则不是固定的。

       Java4种引用的级别由高到低依次为:强引用  >  软引用  >  弱引用  >  虚引用。

       强引用:一般是声明对象时虚拟机生成的引用,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。

       软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间,如果剩余内存相对富裕,则不会进行回收。虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

       弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

       虚引用:顾名思义就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。

       我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。       

五、垃圾回收算法

1、按基本回收策略分

        引用计数算法(Reference Counting):比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题

        标记-清除算法(Mark-Sweep):此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片

       

         复制算法(Copying):此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间

         

         标记-整理算法(Mark-Compact):此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

         

2、按分区对待的方式分

        增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。

        分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

3、按系统线程分

        串行收集(Serial):串行收集使用单线程处理所有垃圾回收工作, 因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。适用于单处理器或数据量较少(100M 左右)的应用,只能用于小型应用

        并行收集(Parallel)并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。适用于多处理情况下减少垃圾回收的时间,应用响应时间可能较长

        并发收集(Concurrent)相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。并发收集可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC打开。适用于对响应时间有高要求,多CPU、对应用响应时间有较高要求的中、大型应用。算法复杂性会大大增加,系统的处理能力也会相应降低,同时,“碎片”问题将会比较难解决

六、分代收集

      分代的垃圾回收策略是基于不同的对象的生命周期是不一样的,然后针对不同生命周期的对象可以采取不同的收集方式,以便提高回收效率分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

      Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

1、Minor GC

       新生代(新生代分为一个 Eden区和两个Survivor区)的垃圾收集叫做 Minor GC。当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Minor GC 非常频繁,一般回收速度也比较快。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程,如果Eden区大部分新生对象不符合GC条件,Minor GC执行时暂停的时间将会长很多。

2、Major GC

      老年代GC,只清理老年代空间的GC事件。

3、Full GC

        对整个堆进行整理,包括年轻代、年老代和持久代。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能避免发生Full GC。

 4、对象存活示意

        

        Eden区没有空间存放新的对象时则将垃圾回收后幸存的对象移到Survivor幸存区的From区。

         

         Eden区没有空间存放新的对象时则将垃圾回收后幸存的对象移到Survivor幸存区的From区。

          

           当Survivor幸存区的From区没有空间存下Eden区新存活的对象,则Eden区新存活的对象和From区的对象都将复制到To区,保证Survivor存在一个区为空。

           

            当From区不能存下Eden存活的对象时,Eden区将存活的对象存入To区,且From区根据算法将部分对象存入老年代或To区。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值