大家好,又见面了,我是你们的朋友全栈君。
对于垃圾JVM的垃圾回收机制这里我们称为GC,众所周知,java语言不需要像c++那样需要自己申请内存,自己释放内存,这些都是JVM帮我们做好了的,但是对于一名java程序员,想要更近自己的水平更上一层楼,就要去了解GC的工作原理,根据原理才能写出更好的更优的程序,这里我们先初步讲解一下GC的工作原理
首先我们在讲解之前我们需要了解一下JVM内存运行时数据区的三个重要的地方
- 堆(heap) : 他是最大的一块区域,用于存放对象实例和数组,是全局共享的.
- 栈(stack) : 全称为虚拟机栈,主要存储基本数据类型,以及对象的引用,私有线程
- 方法区(Method Area) : 在class被加载后的一些信息 如常量,静态常量这些被放在这里,在Hotspot里面我们将它称之为永生代
关于具体结构可以用下面这张图来对内存运行时数据区有一个大致的概括
堆(heap):,前面已经说了他是最大的,也是最重要的一块区域,这里也称为逻辑堆,主要用来存放对象实例与数组,对于所有的线程来说他是共享的,对于Heap堆区是动态分配内存的,所以空间大小和生命周期都不是明确的,而GC的主要作用就是自动释放逻辑堆里实例对象所占的内存,而在逻辑堆中还分为新生代与老年代,用来区分对象的存活时间,在新生代中还被细致的分为 Eden SurvivorFrom以及SurvivorTo这三部分.
方法区(Method Area):方法区主要存储(类加载器)ClassLoader加载的类信息,在这里我们可以理解为已经编译好的代码储存区,所以存储包括类的元数据,常量池,字段,静态变量与方法内的局部变量以及编译好的字节码,等等
栈(stack):在每一个对象被创建的时候,在堆栈区都有一个对他的引用,在这里我们可以这样理解。
Object obj = new Object();
上面的代码左边的Object obj 等于在堆栈区申请了一个内存,这里也就是对类的引用了,而 new Object()则是生成了一个实例,=则是 将对象的内容则可通过obj进行访问,在Java里都是通过引用来操纵对象的。
pc寄存器(PC Regesiter): 在多线程中,系统需要给每一个线程 分配一个进程编号,这个时候才会需要到寄存器。
好了 基本的都介绍完了,现在来进入正题 :
我们知道对象的实例是存在于逻辑堆中,而GC在逻辑堆是怎样运行的呢,下面我们看下逻辑堆的具体结构
就像上面的图可以看出 逻辑堆分为 年轻代与年老代,对于非堆内存这里先不讲解,而年轻代则被分为 eden survivor1 survivor2 ,对于一个新被实例化的对象都是存在于年轻代中的eden区,至于为什么,下面会讲到,eden中文名为伊甸园,按照GC的运行机制,会回收掉已经死掉的对象,而对象一般都是在年轻代就会死去,所以年轻代比老年代需要更频繁的GC清理,下面针对年轻代与老年代的回收机制有不同的讲解
年轻代 :
在年轻代中jvm使用的是Mark-copy算法,就像算法名字说的那样有两个步骤,第一是标记(Mark) 第二是copy(复制),Mark主要用于标记出还活着的实例,然后清除掉没有被标记的实例,释放内存,然后Copy部分则是将还活着的实例根据年龄拷贝到不同的年龄代,而jvm又是根据什么来区分年龄代,和实例存活与不存活的呢? 下面将对这个过程进行讲解
对于标记 与区分年龄代的技术 我们一般都是用到的都是引用计数器,在每一个对象中都含有引用计数器,都有引用指向对象的时候 引用计数器就会加1,不在被引用 计数器 减 1,对与垃圾回收的策略则是标记所有活着的实例,将没有被标记的实例全部回收 释放内存,
对于静态,我们都知道静态方法与静态变量是不会产生实例的,直接通过类的引用,使用 ClassLoader进行加载的类数据如前面所说是不存在逻辑堆里面的,直接存在于永生代里面也就是 方法区里面,这个类一旦被清除掉里面所有的静态变量都会被清除
当我们在 Object obj 的时候 向逻辑堆中的 Eden区域 申请内存,当Eden区域的内存不足的时候,这个时候会触发GC这个时候称gc为小型垃圾回收,每个实例都有一个独有的年龄,每个引用被经历过一次GC后就会年龄加一,同时就会将没有被清理掉的对象全都copy到上图的survivor1区域,如图1所示:
当第二次GC执行的时候就会使用Mark算法找到存活的对象,然后将他们的年龄加1,并且将他们拷贝到survivor2区域,然后执行GC,这样就可以实现survivor1 与 survivor2 两个一样大的区域进行交替使用,当对象的年龄足够大的时候,对象就会被移动到老年代,这里移动到老年代的标准由JVM的参数所决定
年老代 :
当GC被触发的时候 eden的对象会转到 survivor1 然后再次就会转到 survivor2 ,当survivor1的对象太大了 survivor2的区域无法容纳得部分就会转到Tenured的区域,当Tenured的区域也容不下的时候就会自动移动到年老代,在移动年老代的时候会先触发年老代上面的GC然后在将Tenured容纳不下的对象放入年老代,对于年老代的GC算法与年轻代的Mrak-copy算法有很大不同
年老代的GC算法在jdk 1.7 中分为五种,
Serial GC
Parallel GC
Parallel Old GC(Parallel Compacting GC)
Concurrent Mark & Sweep GC (or “CMS”)
Garbage First (G1) GC
在这里我只讲解两种Parallel Scavenge 与 Concurrent Mark sweeps 对与这两种接下来会进行简单的讲解
Parallel Scavenge :
在这里我们简称他为PS算法,PS算法执行的是 Mark-compact算法的过程 ,并且是用多线程进行执行这样提高了执行效率,这里的Mrak还是与之前的年轻代的Mark原理是一样的,但是Compat算法则是将年老代的对象进行碎片化的整理,并且年老代是没有像年轻代的那样有survivor1 与 survivor2来将残留的对象全部copy过去,考虑到年老代的对象比较多,所以就需要进行碎片化整理如下图:
Concurrent Mark sweeps:
我们简称他为 CMS 算法,对与cms算法,我们先需要了解一个概念 Stop the world,对于Stop the world,不管选择哪种GC算法,stop-the-world都是不可避免的。Stop-the-world意味着从应用中停下来并进入到GC执行过程中去。一旦Stop-the-world发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。GC调优通常就是为了改善stop-the-world的时间。在CMS GC开始时的初始标记(initial mark)比较简单,只有靠近类加载器的存活对象会被标记,因此停顿时间(stop-the-world)比较短暂。在并发标记(concurrent mark)阶段,由刚被确认和标记过的存活对象所关联的对象将被会跟踪和检测存活状态。此步骤的不同之处在于有多个线程并行处理此过程。在重标记(remark)阶段,由并发标记所关联的新增或中止的对象瘵被会检测。在最后的并发清理(concurrent sweep)阶段,垃圾回收过程被真正执行。在垃圾回收执行过程中,其他线程依然在执行。得益于CMS GC的执行方式,在GC期间系统中断时间非常短暂。CMS GC也被称为低延迟GC,
关于GC的原理就讲到这里了,这篇博客只讲解了最基本的GC 接下来要学习的还有更多,加油吧
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/132468.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...