A preliminary understanding of JVM Title III (GC heap and collection algorithm)

In "Getting to Know JVM first chapter," and "Getting to Know The second JVM" in, introduced:

  • Class loader: * .class file responsible for loading, loading the contents of the byte code into memory. Which type of class loader are as follows: Execution Engine: responsible for interpreting commands, submitted to the operating system.
    • Boot class loader (on Bootstrap)
    • Extension class loader (the Extension)
    • Application class loader (the AppClassLoader)
    • Custom loader (User-Defined) 
  • Execution Engine: responsible for interpreting commands, submitted to the operating system.
  • Local Interface: the purpose of the integration of different programming languages ​​used to Java, but the company has rarely uses.
  • Native method stacks: the native method interface registered in the native method stacks, load the native method libraries at the time of execution of the execution engine
  • PC register: a thread private, perform sequential recording method, to complete the branching, looping, branching, exception handling, and other basic functions thread resumes.
  • Methods District: schema information storage classes, ClassLoader class loads the contents of files stored in the method area.
  • Stack: thread private, to manage running Java programs.

After a brief review, the next to introduce the Java heap.

Heap (Heap)

We can not tell the stack and heap will, in fact, simply remember one sentence: stack pipe run, stack pipe storage. The heap is shared by the threads, and thread stack is private. So what is heap it?

In a JVM instance, there is a heap memory only. After the memory size can be adjusted, the class loader needs to read the class file class, method, constant variables into the heap memory, all the real information stored reference type, in order to perform performs.

First, throw a big concept to you first, to introduce the heap memory of three parts (here we are talking about version JDK8 to prevail, that will change forever changed behalf dimensional space):

  • The young generation: Our new address is stored out of the object, but the young generation is divided into three parts:
    • Eden (Eden area)
    • Survivor 0 Space (Survivors Zone 0)
    • Survivor 1 Space (Survivors Zone 1)
  • Pension District: Object nascent recovery area after GC (garbage collection) 15 of the surviving objects put here, if the area is full pension will be GC recovery, but the frequency of occurrence is less than the young generation
  • Yuan space: yuan on a space we've talked about, is mainly used to store information structural class, similar to a template.

 

To heap memory is the junior section: Eden District, pension area, Yuan space. The figure is a logical structure, but only physically and neonatal region pension area, and we need to distinguish the new generation is alternative and pension JVM's memory, but the element is a space for system memory. If you see a little ignorant, it does not matter, let us come one by one, first of all the first part of the young generation.

Young generation

The birth of the young generation is the kind of growth, the demise of the region. Here produce a class, and then apply the last to be garbage collected, the process of ending the life of the release of memory. So we simply talk about, a process after a class is new from the beginning to the demise of:

  • 1) Suppose you have a program that has been constantly in the new object, the new object out first is stored in the Eden district newborn zone (note: new objects are placed in the general area of the newborn Eden area, large objects It will be special treatment).
  • 2) memory Eden area is limited, the program has been in constant new objects, finally! ! ! At a certain moment, the Eden room for fast no place to store the new object. That is, the threshold is reached Eden area to store objects. At this time, pay attention! ! ! Eden District began garbage collection, that is, we often say that the light GC, most of the objects will no longer use the Kill off! ! Leaving objects still in use. Because the vast majority of heap memory inside objects are temporary objects, garbage collection will Kill off more than 90% of the object, the number can survive very small.
  • 3) surviving object is moved to the area from 0 survivors Eden area , pay attention to the survivors zone 0 also has an alias to do From.
  • 4)虽然垃圾回收会Kill掉大部分的对象,但是我们还是不能排除有个别现象存在伊甸区和幸存者0区再一次满了的情况,因为程序new的速度肯定是比Kill的速度快的,终于又在某一时刻!!!伊甸区又达到了一定的阈值,再次进行垃圾回收,这时候就会将伊甸区和幸存者0区(注意:迁移的对象包括幸存者0区)存活下来的对象迁移到幸存者1区(幸存者1区的另外一个别名为To)。
  • 5)一直如此反复,等到幸存者1区也满了,就将存活的对象移到养老区进行养老,能到养老区的一般都一些长期使用的对象。那养老区怎么确定哪些才是长期使用的对象呢?在新生区中,一个对象经过每次垃圾回收之后幸存下来的,都会进行计数,经过了15次垃圾回收之后依然存在的,就会进入到养老区。

(注意:讲到这里,是大部分对象消亡了,但是还是有经过15次垃圾回收之后存活下来的对象进入了养老区)

养老区

在新生区中,我们已经描述了一个类从开始到消亡或者进入养老区的过程,要么就是被kill了,要么就是进入了养老区。进入养老区之后就可以舒舒服服的摸鱼了吗?你想得太简单了,接下来看看,养老区又有怎么样的一番搏斗呢:

  • 1)从新生区幸存下来的幸运儿来到了养老区养老,养老区就相当一个养老院,但是一个养老院也会满员。这时候,没办法了,只能清出一部分老人,让新的一批从新生区来的老人入住,这时候就发生了垃圾回收,也就是我们说的重GC。
  • 2)虽然在养老区也会发生垃圾回收机制,但是还是会有一天,这个养老院实在是腾不出空位了,即使是进行重GC也腾不出几个空间,这时候没办法了!!!代表已经没有内存了,玩不转了,所以系统就会报错,也就是我们常看到的OOM(“OutOfMemoryError”):对内存溢出。
  • 3)于是乎,程序就异常停止了,所有对象都消亡了,这个就是程序中一个对象从开始到消亡的整个过程。

堆的内存大小分配:

 注:

  • From就是上面说的幸存者0区的别名
  • To就是上面说的幸存者1区的别名

这个比例我们一定要记住,非常重要,这是在GC时选取何种算法的一个依据之一,新生代跟老年代是1:2,而新生代中的三个分区中分别是8:1:1。

看完了堆内存的结构,接下来我们就要讲讲GC垃圾回收算法了。在上面我们描述了一个对象从开始到结束的过程,中间会发生GC回收,其中:

  • 新生代:发生的GC叫做轻GC也叫MinorGC,所用的算法叫做复制算法。
  • 老年代:发生的GC叫做重GC也叫Full GC,所用的算法叫做标记清除算法和标记压缩算法

  这里过个眼熟,下面我们在GC垃圾回收算法的时候会讲到。

垃圾回收算法

在进行垃圾回收的时候,JVM需要根据不同的堆内存和结构去选取适合的算法来提高垃圾回收的效率,而垃圾回收算法主要有:

  • 引用计数法
  • 复制算法
  • 标记清除算法
  • 标记压缩算法

1)引用计数算法

原理:给对象中每一个对象分配一个引用计数器,每当有地方引用该对象时,引用计数器的值加一,当引用失效时,引用计数器的值减一,不管什么时候,只要引用计数器的值等于0了,说明该对象不可能再被使用了。

优点:

  • 实现原理简单,而且判定效率很高。大部分情况下都是一个不错的算法。

缺点:

  • 每次对对象复制时均要维护引用计数器,且计数器本身也有一定的消耗。
  • 较难处理循环引用。

在JVM中一般不采用这种方式实现,所以就不展开来讲了。

2)复制算法(Copying)——新生代使用

在新生代中的GC,用的主要算法就是复制算法,而且发生GC的过程中From区和To区会发生一次交换(请记住这句话)。在堆的内存分配图中JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(别名叫From和To)。默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),当Eden区进行了GC还存留下来的就会被移到Survivor区。对象在Survivor区每经过一轮GC存留下来年龄就会加1。直到它存活到了一定岁数的是时候就会被移到养老区。由于新生区中的绝大部分对象都是临时对象,不会存活太久,所以经过每一轮的GC之后存活下来的对像都不多,所以新生区所用的GC算法就是复制算法。

复制算法原理:

首先先给大家介绍一个名词叫做根集合(GC Root):

  • 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
  • 处于激活状态的线程
  • 栈中的对象
  • JNI栈中的对象
  • JNI中的全局对象
  • 正在被用于同步的各种锁对象
  • JVM自身持有的对象,比如系统类加载器等

有了上面的了解我们就可以来学学复制算法:

  • 复制算法从根集合(GC Root)开始,从From区中找到经过GC存活下来的对象(注意:虽然说是From区,但是这里的From区是包括了伊甸区和幸存者1区(别名From),所以大家不要认为From区就是单单包括From区而已)。拷贝到To中;
  • 上面我们说过From和To会发生一次交换就是发生在这里,From将幸存下来的对象拷贝到To之后,这时From区就没有对象,空出来了,而To现在不是空的,存放了From的幸存的对象(默认状态是From有对象,To是空的)。这时候From和To就会发生身份的互换,下次内存分配从To开始。也就是说发生一次GC之后From就会变成To,To就会变成From(当谁是空的,谁就是To)
  • 一直这样反复GC,一直再一次发生GC的时候,From存活的对象拷贝到To时,To会被填满,这时候就会把这些对象(满足年龄为15的对象,这个值可以通过-XX:MaxTenuringThreshold来设置,默认是15)移动到养老区。

  下面我们用一张图来描述一下复制算法发生的过程:

我们一直都在反复强调,Eden区的对象存活率是比较低的,所以一般就是拿两块10%的内存作为空闲区(To)和活动区(From),拿80%的内存来存储新建的对象。一但GC过后,就会将这10%的活动区和80%的Eden区存留下来的对象移到空闲区(To)中。然后之前的内存就得到了释放,依次类推。

复制算法的缺点:

  • 复制的时候需要耗费一般的内存,内存消耗大(但是效率的快的,而且新生区的存活效率低,并不需要复制太多的对象,所以新生区用这种算法效率是比我们下面要讲的算法效率高的)。
  • 如果对象的存活率很高,需要复制的对象太多,这时候效率就大大降低了。

复制算法的优点:

  • 没有标记和清除的过程,效率高。
  • 因为是直接对对象进行复制的,所以不会产生内存碎片。

3)标记清除算法(Mark-Sweep)

老年代主要由标记清除算法和标记压缩算法混合使用。

标记算法的步骤从名字其实就可以看出来是怎么回事了:

  • 标记需要清除的对象
  • 清除标记的对象

在复制算法中我们就说了它的缺点是浪费空间,所以为了解决这个问题,就不将对象进行复制了,因为复制一份需要同等大小的内存。标记清除算法采用标记的方式,将要清除的对象进行标记然后直接清除掉,这样就就大大节省了空间了。同上,继续来通过一张图来理解:

上图就是标记清除算法的过程,从过程中可以看出一些问题:

由于回收的对象是进行标记后直接删除的,所以就像上图回收后所展示的一样,内存空间是不连续的,也就是会有内存碎片的产生。第二个问题是复制算法是直接复制的,但是标记清除算法是需要扫描两次,耗时严重。

标记清除算法的优点:

  • 对需要回收的对象进行标记清除,不需要额外的空间。

标记清除算法的缺点:

  • 效率低,在进行GC时,需要停止整个程序。
  • 清理出来的内存空间是不连续的,存在内存碎片。由于空间不连续,查找的效率也会降低

但是由于养老区存活下来的对象会比新生区的对象多,所以用标记清除是比复制算法好的。

4)标记压缩算法(Mark-Compact)

理解了标记清除算法后,其实这一个算法就比较简单理解了。就是多了一步整理的阶段,清除内存碎片使空间变得连续。过程如下图:

标记压缩算法的优点:

  • 可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。 
  • 标记/整理算法不仅可以弥补标记清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。

标记压缩算法的缺点:

  • 虽然这个算法解决了上两个算法的一些缺点,但是这个算法却是耗时最长的。从效率来看是低于标记清除算法和复制算法的。

以上就是GC的四大算法,当然出了这四大算法还有标记清除压缩算法(Mark-Sweep-Compact),这个也很好理解就是在整理阶段不再是GC一次就整理一次,而是每隔一段时间整理一次,减少移动对象的成本。

分代收集算法:

当有人问你哪个算法是最好的时候,你的回答应该是:无,没有最好的算法,只有最合适的算法。使用哪个算法应该看GC发生在什么地方:

  • 新生代:复制算法
    • 原因:存活率低,需要复制的对象很少,所需要用到的空间不是很多。另外一方面,新生代发生的频率是非常高的,而复制算法的效率在新生代是最高的,所以新生代用复制算法是最合适的。
  • 老年代:标记清除和标记压缩算法混合使用
    • 原因:存在大量存活率高的对像,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
    • Mark阶段的开销与存活对像的数量成正比,这点上说来,对于老年代,标记清除或者标记整理有一些不符,但可以通过多线程利用,对并发、并行的形式提高标记效率。
    • Sweep阶段的开销与所管理区域的大小成正相关,但是清除“就地处决”的特点,回收的过程没有移动对象。使其相对其它有移动对像步骤的回收算法,仍然是效率最好的。但是需要解决内存碎片问题。
    • Compact阶段的开销与存活对像的数据成开比,如上一条所描述,对于大量对像的移动是很大开销的,做为老年代的第一选择并不合适。
    • 基于上面的考虑,老年代一般是由标记清除或者是标记清除与标记整理的混合实现。以hotspot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对像的回收效率很高,而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器做为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理。

终于写完了,以上便是本人对JVM的理解,如有不足欢迎提出,谢谢!!!

Guess you like

Origin www.cnblogs.com/linzepeng/p/12069353.html