Java虚拟机垃圾回收相关知识点全梳理(上)

1、前言

笔者最近在复习JVM的知识,本着记录分享的精神,整理下学习Java虚拟机垃圾回收相关知识点,因为整个垃圾回收内容比较多,我将整理成上下两篇文章去分享,上篇我会主要分享Java虚拟机的运行时数据区域划分,垃圾回收算法。下篇文章主要分享Java虚拟机的垃圾回收器以及一些虚拟机调优建议。html

2、运行时数据区

Java虚拟机定义了程序在运行期间的多种数据区域,其中有些区域是在Java虚拟机建立的时候就建立了,只有在虚拟机退出后才会被销毁。根据Java虚拟机定义,咱们能够数据区域作以下区分,分为:堆、Java虚拟机栈、程序计数器、方法区(元数据区、运行时常量池、本地方法栈。下面咱们来详细介绍下每一个区域的做用。java

2.1 程序计数器

程序计数器是一块线程私有的区域,是一个较小的内存块,用来存放当前线程执行的字节码的指令地址,若是执行的是本地方法(Native),这个计数器就会为空(Undefined)。算法

2.2 Java虚拟机栈

Java虚拟机栈是线程私有的区域,生命周期与线程相同,它存储的是栈帧(Stack Frame),栈帧会来存储局部变量表、操做数栈、动态连接、方法出口和返回地址等信息。每个方法从调用到执行完成的过程,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。若是线程请求的栈深度大于虚拟机所容许的最大深度,就会抛出StackOverflowError异常;若是申请栈内存不够,也会致使抛出OutOfMemoryError异常。数组

Jvm参数 
-Xss:栈空间大小;栈的空间大小决定了栈能建立的深度
复制代码

栈结构以下:数据结构

2.3本地方法栈

本地方法栈和java方法栈很是相似,他们以前的区别主要是Java方法栈是提供给字节码服务的,本地方法栈是给本地方法(C语言实现)调用服务的。Java虚拟机并无对本地方法栈中使用的语言、数据结构等进行强制规定,因此虚拟机能够自行实现它。Sun HotSpot虚拟机把虚拟机栈和Java方法栈进行了合二为一。本地方法栈也会和虚拟机栈同样抛出StackOverFlowError和OutOfMemoryError异常。oracle

2.4 堆

Java堆是一个全部线程共享的区域,堆用来存储几乎全部对象的实例和数组,堆按照分代的思想进行划分,能够划分了新生代(YoungGeneration)和老年代(Old/Tenured Generation),新生代又可进一步细分为 eden、survivor space0(s0 或者 from space)和 survivor space1(s1或者to space)。咱们用图来表示下堆的划分:jvm

eden区:新建对象通常都放在该区域,除非是新建了大对象,该区域放不下就直接存放在老年代(Tenured)。 S0和S1区:该区域放置的对象至少经历了一次垃圾回收(Minor GC),若是经历了屡次回收,到达指定次数还存活,那么就会被转移到老年代。ide

Java虚拟机规范规定堆能够是物理上不连续的空间,只须要逻辑上连续便可,咱们能够经过命令(-Xmx和-Xms )来调整堆空间,若是申请的堆内存超过了堆的最大内存,将会抛出OutOfMemoryError异常。学习

Jvm参数
 -Xmx:最大堆空间大小
 -Xms:最小堆空间大小
 -Xmn:新生代空间大小
复制代码

2.5 方法区(元数据区)

方法区是线程共享的区域,它用于存放已经被虚拟机加载的类信息、常量池、静态变量、即时编译器编译后的代码等数据。类信息包括类的完整名称、父类的完整名称、类型修饰符(public/protected/private)和类型的直接接口类表;常量池指运行时常量池(后面有介绍);方法区又被称为非堆(Non-Heap)。spa

在Host Spot虚拟机的实现中,方法区也被称为永久区,是一块独立于 Java 堆的内存空间。虽然叫永久区,可是永久区中的对象一样能够被 GC 回收的(注:方法区是 JVM 的一种规范,永久区是一种具体实现,在 Java8 中,永久区已经被 Metaspace 元空间取而代之。相应的,JVM参数 PermSize 和 MaxPermSize 被 MetaSpaceSize 和 MaxMetaSpaceSize 取代)。对永久区 GC 的回收,一般主要从两个方面分析:一是 GC 对永久区常量池的回收;二是永久区对类元数据的回收。

当方法区没法知足内存分配需求时,将抛出OutOfMemoryError异常。

2.5.1运行时常量池

运行时常量池(Run-Time Constant Pool)是方法区的一部分,它主要用来存放编译期生成的各类字面量和符号引用,既然是运行时常量池,理所应当的能够存放运行时产生的常量,好比调用String.intern()方法产生的字符串常量就会被放入运行池常量中。

3、垃圾断定算法

3.1 引用计数算法

引用计数法的思想比较简单,每一个对象都有一个引用计数器,只要对象被引用,计数器就+1,当对象再也不被引用时候,计数器就减一。这种算法很高效,可是有一个致命缺点,就是有循环引用的问题。对于两个无用对象的互相引用,就会致使两个对象的计数器不为0,从而没法被断定为无用对象,没法回收内存。

3.2 可达性分析算法

因为引用计数法有互相引用的缺陷,因此Java虚拟机采用了可达性分析算法来断定垃圾对象。这个算法的思想是,以一系列称为“GC Roots”的对象做为起始点,从这些起点往下搜索,搜索所走过的路径称为引用链(Referenc Chain),当一个对象到GC Roots没有任何引用链(从GC Roots到这个对象不可达)时,就说明这个对象不可用,能够被回收。

能够做为GC Roots的对象包括:

  • 虚拟机栈中引用的对象(局部变量)。
  • 方法区中类静态属性引用的对象(静态变量)。
  • 方法区中常量引用的对象(常量)。
  • 本地方法栈中JNI(即本地方法)引用的对象。

那为何上面四种对象就能够做为GC Roots呢?

1.虚拟机栈中当前引用的对象,由于虚拟机栈中的对象是随着线程的生命周期存活的,那么在垃圾判断的时候,当前线程还存活,也就意味着栈中持有的对象确定是存活的,因此能够做为GC Roots,本地方法栈也是同样的道理。

2.对于方法区中的静态变量引用和常量,个人理解是使用方法区中的对象做为GC Roots并非必定就会以里面全部的对象做为GC Roots,虽然Java虚拟机并无规定方法区要进行回收,可是该区域在目前的JVM实现中都有回收,因为方法区也会对“废弃常量”和“无用类”进行回收,因此选择GC Roots只会选择方法区内的有效对象。"废弃常量"判断比较简单,对于“无用类”的判断,Java虚拟机只会判断动态加载的类,对于原始加载的类,虚拟机永远不会自动卸载。因此判断动态加载的类为无用类能够有如下原则:

  • 该类全部的实例都已经被回收,堆中不存在该类的任何实例。
  • 该类对于的类加载器已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方引用,没法经过反射访问该类的方法。

4、垃圾回收算法

4.1 标记-清除算法

标记-清除算法分为“标记”和“清除”两个阶段,首先须要标记出须要回收的对象,标记完成后再进行统一的垃圾回收。该算法有两个缺点:1.效率不高;2.清除后会产生大量不连续的内存碎片,内存碎片会致使分配大对象时候,没法找到足够的内存,从而提早触发一次GC.

4.2 复制算法

上面的标记清除算法效率不高,为了解决这个问题,就有了复制算法,复制算法就是把内存容量划分为大小相等的两块,每次只用其中一块,当一块内存用完后就将存活的对象复制到另一块内存上,而后再对原内存块进行清理。这种算法的优势就是内存分配不用考虑碎片的问题,只须要移动堆顶的内存指针,按顺序分配内存便可。可是这算法的缺点就是空间利用率不高,将内存缩小为原来的一半,有一半的内存没有被真正利用起来。

虽然内存利用率不高,可是目前的虚拟机中堆中的新生代就是采用这种算法进行垃圾回收的。上面咱们提到新生代分为 eden 空间、form 空间和 to空间3个部分。其中 from 和 to 空间能够视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于存放未被回收的对象。

在垃圾回收时,eden空间中存活的对象会被复制到未使用的survivor空间中(假设是 to),正在使用的survivor空间(假设是 from)中的年轻对象也会被复制到to空间中(大对象或者老年对象会直接进入老年代,若是to空间已满,则对象也会进入老年代)。此时eden和from空间中剩余对象就是垃圾对象,直接清空,to空间则存放这次回收后存活下来的对象。

4.3 标记-压缩算法

复制算法只适用于存活率较低的新生代中,若是存活率较高就须要进行过多的复制操做,效率将会下降。老年代的存活率比较高,因此复制算法不适用于老年代的场景,以前提到的“标记-清除”算法,若是不会产生内存碎片的话,仍是能够知足老年代的,那么有没有不产生碎片的相似算法呢?答案是有,“标记-整理”算法就派上用处了。它的核心思想是:先对可回收对象进行标记,而后把全部存活的对象移动到一端,接着直接清理掉边界意外的内存区域。由于清理事后,存活对象都紧密的在一端,因此不会产生内存碎片。

8、总结

本篇文章我整理了Java虚拟机的运行区划分,每一个区域的做用,同时分享了垃圾判断算法和垃圾回收算法。运行时数据区划分为:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区、运行时常量池。有的文章中提到Jdk1.7及之后的版本把运行时常量从方法区移除,这里我想说明下,Java虚拟机规范仍是要求在方法区分配,这只是个别虚拟机的本身实现,好比说Hot Spot虚拟机。

垃圾断定算法如今虚拟机主要使用可达性分析算法,垃圾回收算法有“标记-清除”算法、“复制”算法、“标记-整理”算法。“复制”算法比较适合存活对象较少的新生代,“标记-整理”算法比较适合老年代,整理的做用就是为了有连续的内存空间,防止内存碎片太多没法存放大对象。

9、参考

相关文章
相关标签/搜索