JVM02----垃圾收集上(堆)

Java中最大的特点在于具备良好的垃圾收集器。GC是JAVA中最重要的安全保证。

整个JVM中的GC的处理机制:对不需要的对象进行标记,而后进行清除。

一. 堆内存划分

                          

note:(1.8之前元空间的位置是永久代,这是最大的变化)。JDK1.8开始,之前的永久代空间取消了。取消永久代的目的是为了将HotSpot与JRocket两个虚拟机标准合成一个,因为以前只有HotSpot才有永久代。

在整个的JVM堆内存之中实际上将内存分为了三块:

  • 年轻代:新对象和没达到一定年龄的对象都在年轻代;(比如18岁前)

           新生对象在Eden,而年龄达到一定的在SO或者S1,还有伸缩区域可以扩展;

  • 年老代:对象被长时间使用的对象。(比如18岁到80岁),老年代的内存空间应该比年轻代的内存空间更大。
  • 元空间:像一些方法中的操作临时对象等,直接使用物理内存;

           最初的永久代是需要在JVM堆内存里面进行划分;而元空间直接使用物理内存;

为何分区:是为了更好的进行每一块的管理(好比磁盘分区管理),可以确定哪一块内存可以被清空,哪一块不能。

二. GC流程

所有的数据都会保存在JVM的堆内存之中,但是在实际的开发中经常会创建许多的临时对象,也会有一些常驻对象在(比如单例),所以为了保证GC的性能问题,对于GC的处理流程如下图所示:

对于GC流程里面,那么最需要处理的就是年轻代与老年代内存清理操作,而元空间(永久代)都不在GC范围内。其实元空间和永久代差不多,只是一个是内部内存,一个是外部内存。

                          

      图的说明如下:

  • 当现在有一个新的对象产生(Eden),那么对象一定需要内存空间,于是现在就需要为该对象进行内存空间的申请。
  • 首先会判断Eden园区是否有内存空间,如果此时有内存空间,则直接将新对象保存在Eden区域;如果此时Eden内存空间不足,那么他会出发一个Minor GC操作,将Eden的无用内存空间进行清理。(分区的好处不会影响到别的区域);清理之后会继续判断Eden的内存空间是否充足。充足的话则将新的对象直接在Eden进行空间分配;如果执行了Minor GC后,Eden内存任然不足,那么这个时候会进行存活区的内存判断,则会将Eden的部分对象存在存活区,(Eden就会空出一部分空间出来了),那么这个时候又继续判断Eden的空间是否够,够就存。  如果存活区也没有内存空间了,那么就继续判断老年区。如果此时老年区的空间充足,则将存活区中的活跃对象保存到老年代,而后存活区就会出现空余空间,随后Eden将活跃对象保存到存活区之中,从而为Eden开辟内存空间。--------------说明所有的新对象都会在Eden区。
  • 如果这个时候老年代也满了,那么这个时候将产生Major GC(Full GC),那么这个时候就会进行老年代的垃圾清理。
  • 如果老年代执行了Full GC之后发现仍然无法进行对象的保存,就会产生OOM异常,OutOfMemoryError.

 三. JVM内存调整参数(调优关键)

通过之前的分析发现,实际上每一块子内存区域中都会存在有一部分的可变伸缩区域,其基本流程为:如果空间不足,则在可变的空间范围内,扩展内存空间;当一段时间内,发现内存空间不那么紧张了,因此开始收缩内存空间。(因为判断伸缩是花时间的,因此它是调优的关键,我们应该尽量让这个伸缩区域不存在)。

               

前两个最常用。

在整个堆内存的调整策略之中,有经验的人基本上都只会调整两个参数(前两个):一个叫做最大内存,一个叫做初始化内存如果让这两个相等,那么就没有可变的范围,性能就可以提升。如果要想取得这些内存的整体信息直接利用Runtime类即可。

上面的单位是字节,最好用M单位来表示,就除以两个1024

 

根据结果会发现:默认情况下分配的内存是总内存的“1/4”(上面的总内存是32G).而初始化的内存为1/64.那么伸缩区就是491.0M到7276.5M之间,那么现在就有可能造成程序的性能下降。那么最好的做法就是让这两个参数一样大小。那么这个时候就避免了伸缩去的可调策略,从而提升了整个程序的性能。

比如这样:

 

note:在eclipse里面可以配置的,比如打开Run configuratios----VM arguments

 四. 年轻代

所有的新对象都会在年轻代产生。如果年轻代的空间的不足,无法生成对象,则会引发Minor GC和Major GC(Full GC)。

 

所有使用关键字new新实例化的对象一定会在Eden保存,而存活区保存的一定是已经在Eden中存在好久并且经过了好几次的Minor GC还保存下来的存活对象。那么这个对象将晋升到存活区之中。存活区分为两个存活区,而且这两个存活区一定是相等的大小。目的:一块存活区为了晋升,另外一块为了对象回收。Fromto不固定,是会互相换的。这两块内存空间一定有一块是空的。

 Minor GC的算法如下图:

在年轻代中使用的是Minor GC,这种GC算法采用的是复制算法。

解释:当Eden空间不足的时候,会把存活对象上升到存活区中,即把绿色放到存活区中的。当存活区中的GC完了以后,将统一把存活对象放到一个存活区中,另外一个存活区就会被清空。

通过以上的分析可以发现,在整个的处理过程中,Eden中大多说都是临时的新对象,可能会发生频繁的Minor GC,所以在HotpSpot虚拟机之中为了加快此空间的内存分配,所以采用了两种技术优化实现:Bump-The-Pointer和Thread-LocalAllocation Buffers.

五. 老年代

老年代主要是接收由年轻代发来的对象。一般是经过了好几次Minor GC后的。如果你要保存的对象超过了Eden大小,那么这个对象可以直接保存到老年代。当老年代内存不足时,将引发Full GC.(Major GC)

老年代中的算法有2个:

在回收清除的过程之中,发现所有在老年代被回收的对象并没有进行空间的整理,所以老年代里面最头太疼的问题就是碎片问题。于是还有第二种算法:

所以:以后在进行老年代存储的时候,尽可能保存长期会被使用的对象,并且不会被轻易回收的大对象的存在。

 六. 永久代(1.8之后消失了)-------取而代之的是元空间

虽然SEJDK1.8,但是JavaEE还是JDK1.7呢.

永久代时在堆内存之中保存的,但是永久代不会被回收,比如inter()方法产生的对象,是不会被回收的。如果操作不当,导致永久代中的数据过大,这个时候程序会抛出OOM异常。

七. 元空间

唯一的区别是永久代使用的堆内存空间,而元空间时使用的物理内存,直接受到本地的物理内存限制。

参考文献:

https://blog.csdn.net/SEU_Calvin/article/details/51404589

《深入理解JAVA虚拟机》

李兴华老师的《Java内存模型》

猜你喜欢

转载自www.cnblogs.com/Hermioner/p/10088266.html