JVM内存模型及相关的垃圾回收算法

JVM模型

Java虚拟机内存模型:程序计数器、虚拟机栈、本地方法栈、Java堆以及方法区等。
程序计数器(指令计数器):程序计数器是一块很小的内存区域,它是运行速度最快的内存区域,主要用于存放下一条取指指令的地址。
虚拟机栈:栈是一种快速有效的分配存储方法,访问速度仅次于寄存器,堆栈指针向下移动,则分配新的内存,若向下移动则释放内存。
虚拟机栈主要被用于存放一些基本类型的变量,例如int、short、long、byte、float、double、boolean、char以及对象引用。并且存放在栈中的数据是可以共享的。
虚拟机栈在运行时使用栈帧来保存上下文数据。方法调用时栈帧入栈,方法返回时栈帧出栈。栈帧由三部分组成:即局部变量区、操作数栈和帧数据区(常量池引用)。局部变量区为一个从0开始的数字数组。局部变量区是通过数组下标进行访问;操作数栈同样为一个数字数组,它是通过栈的push、pop方法来操作数据;帧数据的主要作用有:解析常量池中的数据;方法执行完后处理方法返回,恢复调用方现场;方法执行过程中抛出异常时的异常处理。
本地方法栈:本地方法栈和Java虚拟机栈的功能很相似,Java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用。本地方法并不是用Java实现的,而是使用C实现的。
Java堆:堆是一种通用性的内存池,用于存放所有的Java对象。Java中的对象和数组均存放在堆中。Java堆区是GC的重点回收区域。正是由于此区域为GC的重点回收区域,所以GC极有可能会在大内存的使用和频繁进行垃圾回收的过程中成为系统性能的瓶颈。针对此问题,主要有两种解决思路:第一,将一些生命周期比较长的对象移动到heap区之外,以此达到降低GC的回收频率和提升GC的回收效率的目的;第二,利用逃逸分析与栈上分配等优化技术。逃逸分析通俗来讲,如果一个对象的指针被多个方法或线程引用时,那我们就称这个指针发生了逃逸。利用逃逸分析原理对JVM进行优化,即针对栈的重新分配方式。首先分析并且找到未逃逸的变量,将变量类的实例化内存直接在栈内进行分配,分配完成后,继续在栈内进行调用,最后线程结束,栈空间被回收,局部变量也被回收。该方法减少了临时对象在堆内的分配数量。
方法区:方法区主要用于保存类的元数据。方法区中最为重要的是类的类型信息、常量池、域信息、方法信息。类型信息包括类的完整名称、父类的完整名称、类型修饰符和类的直接接口类表。常量池包括类方法、域等信息所引用的常量信息。域信息包括域名称、域类型和域修饰符。方法信息包括方法名称、返回类型、方法参数、方法修饰符等。总之,方法区中保存的信息大部分来自Class文件。JDK1.8之前,Class在被加载的时候被放入(永久区)PermGen Space区,但是如果加载的类过多的话就会出现PerGen Space错误。在JDK1.8之后,原来永久代中的数据被分到了堆和元空间中,元空间存储类的元信息,静态变量和常量池等放入堆中,需要注意的是这里指的常量池是字符串常量池并不是指运行时常量池。运行时常量池仍然是方法区的一部分。

JVM中四种类型的引用

Java中四种类型的引用:强引用、软引用、弱引用、虚引用
强引用:在一个线程中,无需引用直接可以使用的对象,除非引用不存在了,否则强引用不会被GC清理。平时声明的变量即为强引用如String s1 = “Hello World”
软引用:JVM抛出OOM之前,GC清理所有的软引用对象
弱引用:弱引用对象与软引用对象最大的不同在于,当GC进行回收时,需要通过算法检测是否回收软引用对象,而对于弱引用对象,GC总是进行回收。弱引用对象常常用于Map结构中。
虚引用(幽灵引用):用于在一个对象所占内存被实际回收之前得到通知,从而进行一些相关的清理工作。通过幽灵引用是无法获取被引用的对象的。

JVM相关的垃圾回收算法

在讲解垃圾回收算法之前,首先需要判断什么类型的对象属于是应该回收的对象
区分死亡对象以及存活对象的两种比较常见的算法是引用计数算法以及根搜索算法。
1)引用计数算法:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器即加1,当引用失效时,引用计数器就减1。若对象A的引用计数器的值为0,则GC将该对象所占用的内存进行回收。但引用计数法无法处理循环引用的情况。
2)根搜索算法:根搜索算法以根对象集合为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达,如果目标对象不可达,就意味着该对象已经死亡,便可以被instanceOopDesc的Mark World中将其标记为垃圾对象。对于标记为垃圾的对象,此时并不会被回收,而是处于“缓刑阶段”,此时该对象将被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,若对象没有覆盖finalize方法,或者finalize方法已经被虚拟机调用过,在这两种情况下皆被视为没有必要执行。若该对象被判定为有必要执行finalize方法,则此时将此对象放置在一个F-Queue的队列中,之后GC将对F-Queue中的对象进行第二次小规模的标记,若对象此时要在finalize中拯救自己,只需要重新与引用链上的任意一个对象建立关联即可。
下面开始讲解常见的一些垃圾回收算法:
标记清除算法:首先标记出存活的对象,那些没有被标记的对象就可以被回收了。第一步从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。
在这里插入图片描述
复制算法:此算法将内存空间划分为两个相等的区域,每次只使用其中一个区域。在进行垃圾回收时,将正在使用的内存中存活的对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾的回收。

在这里插入图片描述

标记压缩算法:第一阶段从根节点开始标记所有被引用的对象;第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按照顺序进行存放。
在这里插入图片描述
分代收集算法:分代的唯一原因是为了优化GC性能
将Java堆分为Young、Tenured、Perm三个部分;而年轻代又分为1个Eden区和2个Survivor区(分别叫From和To);当GC只发生在年轻代时,回收年轻代对象的行为称为Minor GC。当GC发生在老年代时被称为Major GC或者Full GC。如果对象在Eden出生并经过第一次Minor后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象的年龄设为1,对象没熬过一次Minor GC,年龄便增加一岁,一般默认当年龄增加到15岁时,就升为老年代。
不同年龄段的对象的分配原则为:
1)对象优先分配在Eden区,如果该区域没有足够的空间,则虚拟机执行一次Minor GC操作;
2)大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)
3)长期存活的对象进入老年代
4)如果Survivor区中相同年龄的所有对象的大小总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代
5)每次进行Minor GC时,JVM会机损Survivor区移动到老年区对象的平均大小,如果这个数值大于老年区剩余值的大小则进行一次Full GC

常见的一些垃圾回收器(GC)

GC:GC的主要工作可以分为两大类,分别为内存的动态分配和垃圾回收
垃圾收集器:
1)按照工作模式可分为并发式垃圾收集器和独占式垃圾收集器;
2)按照碎片处理方式可以分为压缩式垃圾收集器和非压缩式垃圾收集器,压缩式收集器会在垃圾回收完成后,对存活对象进行压缩整理,消除回收后的碎片;
按工作内存区间可以分为年轻代垃圾收集器和老年代垃圾收集器
Serial和Serial Old收集器
Serial收集器作用于年轻代,它采用复制算法、串行回收和Stop-the-World机制执行内存回收。
Serial Old收集器与Serial收集器唯一的区别为内存回收算法使用的是标记-压缩算法;首先标记在老年代中存活的对象,从头开始检查内存空间,对没有引用的对象进行清除,仅仅留下幸存的对象,将存活的对象按照顺序进行存放,并将存储空间分为两部分,一部分存放经过清除之后幸存下来的对象,一部分为空。
ParNew收集器
ParNew收集器为多线程的收集器(作用于年轻代),并行回收、复制算法和Stop-The-World机制进行垃圾回收,在多CPU的环境下应用较为理想
Parallel收集器
Parallel收集器与ParNew收集器的不同之处在于它可以控制程序的吞吐量,即它为吞吐量优先的垃圾收集器。它同样采用了复制算法、并行回收和Stop-the-World机制
1)年轻代并行收集器
多线程、独占式、复制算法,主要关注其吞吐量
2)老年代并行收集器
多线程、并发、采用标记-压缩算法
CMS收集器
作用于老年代,并行回收、基于标记-清除算法;CMS的执行过程主要分为四个阶段:
1)初始标记阶段
Stop-the-World机制令所 qo有的工作线程暂停,标记出内存中那些被根对象集合所连接的目标对象是否可达,标记完成回到之前暂停的应用进程位置继续执行;
2)并发标记阶段
将不可达的对象标记为垃圾对象
3)再次标记阶段
为了防止并发阶段对之前所标记对象的状态篡改,需要再次进行标记阶段对所有对象做进一步的标记,从而保证所有的对象是被正确标记的
4)并发清除阶段
执行内存回收,释放无用对象所占用的内存空间
Garbage First(G1)GC
与之前提及的所有GC相比,G1&GC具有一些全新的特点:
并行性:G1在回收期间,可以有多个GC线程同时工作
并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行
分代GC:G1仍然是一个分代收集器,但是它同时兼顾了年代代和老年代
空间清理:与CMS GC的优势在于每次回收垃圾时都会对对象进行复制,从而减少空间碎片,进而提升内部循环速度
可预见性:采用Region的设计思路,而不是固定某一内存区域的具体用途,可将Region分配给Eden、Survivor、老年代以及大对象等。这样G1可以选取部分区域进行内存回收,从而缩小了回收的范围
在这里插入图片描述
最后,讲解一下JVM中的类加载机制:
类机载机制:类是在运行期间第一次使用时动态加载的,而不是一次性加载的。因为如果一次性将所有的类都进行加载,内存的占用量是相当高的。
类的生命周期主要包括以下7个阶段:加载、验证、准备、解析、初始化、使用、卸载
在这里插入图片描述
其中类的加载过程包含了:加载、验证、准备、解析、初始化5个阶段
1)加载
在加载阶段主要完成3件事:通过类的完全限定名称获取定义该类的二进制字节流;将该字节流表示的静态存储结构转换为方法区的运行时存储结构;在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。
2)验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
3)准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值