JVM虚拟机个人总结(四)

这是我看《深入Java虚拟机》这本书的最后一个总结,发现之后的10章讲的是底层操作符运算,我这里就暂不学习了,后面有需要再去深入。
今天讲两个方面的内容:1.连接模型。2.垃圾收集。

首先来看连接模型,动态连接是怎样的一个过程?
当编译一个Java程序的时候,会得到程序中每个类或者接口独立的class文件。虽然独立文件看上去毫无联系,实际上它们之间通过接口符号互相联系,或者与Java API文件相联系。当程序运行的时候,Java虚拟机装载程序的类或接口,并且动态连接的过程中把它们互相勾连起来。在程序运行中,Java虚拟机内部组织了一张互相连接的类和接口的网。

那么class文件的引用符号存在哪里?
class文件把它所有的引用符号保存在常量池。每一个class文件有一个常量池,每一个被Java虚拟机装载的类或者接口都有一份内部版本的常量池,被称作运行时常量池。运行时常量池是一个特定于实现的数据结构,数据结构映射到class文件中的常量池。因此,当一个类型被首次装载时,所有来自于类型的符号引用都被装载到了类型的运行时常量池?

所谓的常量池解析是指什么?
当程序运行的某个时刻,如果某个特定的符号引用将要被使用,它首先要被解析。解析过程就是根据符号引用查找到实体。再把符号引用替换成一个直接引用的过程。因为所有的符号引用都保存在常量池,所以这个过程就被称为常量池解析。

在连接中需要做检查么?
连接不仅仅包括把符号引用替换成直接引用,还包括检查正确性和权限,需要检查以下条件:
1.那个其他类存在
2.该类有权访问那个其他类
3.那个其他类存在名字相符的字段
4.那个字段的类型和期望的类型相符
5.本类有权访问那个字段
6.那个字段是一个静态类变量,而不是一个实例变量

初始类装载器和定义类装载器是指什么?
在Java术语中,要求某个类装载器去装载一个类型,但是却返回了其他类装载器装载的类型,这种装载器被称为那个类型的初始类装载器,而实际定义那个类型的类装载器被称为类型的定义类装载器。

下面开始重点的垃圾收集。
垃圾收集算法是什么?
任何垃圾收集算法必须做两件事情。首先,它必须检测出垃圾对象。其次,它必须回收垃圾对象所使用的堆空间并还给程序。
垃圾检测通常通过建立一个根对象的集合并且检查从这些根对象开始的可触及性来实现。Java虚拟机的根对象集合根据实现不同而不同,但是总会包含局部变量中的对象引用和栈帧的操作数栈(以及类变量中的对象引用),另外一个来源是被加载的类的常量池中的对象引用,比如字符串。被加载的类的常量池可能指向保存在堆中的字符串,比如类名字,超类的名字,超接口的名字,字段名,字段特征签名。还有一个来源是穿提到本地方法中的、没有被本地方法“释放”的对象引用。
区别活动对象和垃圾的两个基本方法是:引用计数和跟踪。引用计数垃圾收集器通过为堆中每一个对象保存一个计数来区分活动对象和垃圾对象。这个计数记录下了对那个对象的引用次数。跟踪垃圾收集器时间上追踪从根结点开始的引用图。在追踪中遇上的对象以某种方式打上标记,当追踪结束时,没有被打上标记的对象就被判定是不可触及的,可以被当做垃圾收集。

下面逐一来介绍不同的垃圾收集算法:
首先是引用计数垃圾收集算法:
引用计数是垃圾收集的早期策略。在这种方法中,堆中每一个对象都有一个引用计数。当一个对象被创建了,并且指向该对象的引用被分配给一个变量,这个对象的引用结束被置为1。当任何其他变量被赋值为对这个对象的引用时,计数加1.当一个对象的引用超过了生存期或者被设置一个新的值时,对象的引用计数减1.任何引用计数为0的对象可以被当做垃圾收集。当一个对象被垃圾收集的时候,它引用的任何对象计数值减1.在这种方法中,一个对象被垃圾收集后可能导致后续其他对象的垃圾收集行动。
这种方法的好处是,引用计数收集器可以很快地执行,交织在程序的运行中。这个特性对于程序不能被长时间打断的实时环境很有利。坏处就是,引用计数无法检测循环(即两个或者更多的对象互相引用)。还有一个坏处是每次引用计数的增加或者减少都带来额外的开销。这种方法已经不为人所接收,一般使用追踪算法。

接下来是跟踪收集器:
第二种垃圾收集器:跟踪收集器
跟踪收集器追踪从根结点开始的对象引用图。在追踪过程中遇到的对象以某种方法打上标记。总的来说,要么在对象本身设置标记,要么用一个独立的位图来设置标记。当追踪结束时。未被标记的对象就知道是无法触及的,从而可以被收集。
基本的追踪算法被称作“标记并清除”。这个名字支出垃圾收集过程中的两个阶段在标记阶段,垃圾收集器遍历引用树,标记每一个遇到的对象。在清除阶段,未被标记的对象呗释放了,使用的内存被返回到正在执行的程序,在Java虚拟机中,清除步骤必须包括对象的终结。

Java虚拟机的垃圾收集器有对付堆碎块的策略。标记并清除收集器通常使用的两种策略:压缩和拷贝。
压缩收集器把活动的对象越过空闲区活动到堆的一端,在这个过程中,堆的另一端出现一个大的连续空闲区。所有被移动的对象的引用也被更新,指向新的位置。
拷贝收集器把所有的活动对象移动到一个新的区域。在拷贝的过程中,他们被紧挨着布置,所以可以消除原本它们在旧区域的空隙。原有的区域被认为都是空闲区。这种方法的好处是对象可以从根对象开始的遍历过程中随着发现而拷贝,不再有标记和清除的区分。对象被快速拷贝到新区域,同时转向指针仍然留在原来的位置。转向指针可以让垃圾收集器发现已经被转移的对象的引用。然后垃圾收集器可以把这些引用设置为转向指针额值,所以它们现在指向对象的新位置。
一般的拷贝收集器算法被称为“停止并拷贝”。在这个方案中,堆被分为两个区域,任何时候都只使用其中一个区域。对象在同一个区域中分配,直到这个区域被耗尽。此时,程序执行被中止,堆被遍历。遍历时遇到的活动对象被拷贝到另外一个区域,当停止和拷贝过程结束时,程序恢复执行。内存将从新的堆区域中分配,直到它也被用尽,那时程序再次中止。遍历堆,活动对象又被拷贝回原来的区域。这种方法带来的代价就是对于指定大小的堆来说需要两倍大小的内存,因为任何时候都只能使用其中一半。
如下图:
这里写图片描述

然后是按代收集的收集器:
简单的停止并拷贝收集器的缺点:每一次收集时,所有的活动对象都必须被拷贝。大部分语言的大多数程序都有以下腾点:
1.大多数程序创建的大部分对象都具有很短的生命期。
2.大多数程序都创建一些具有非常长生命周期的对象。
简单的拷贝收集器浪费效率的主要原因是它们每次把这些生命周期很长的对象来回拷贝,消耗大量的时间。按代收集的收集器通过吧对象按照寿命来分组解决这个效率低下的问题,更多的收集那些短暂出现的年幼对象,而非寿命较长的对象。在这种方法里,堆被划分为两个或者更多的子堆,每一个子堆为一“代”对象服务。最年幼的那一代进行最频繁的垃圾收集。因为大多数对象都是短促出现的,只有很小部分的年幼对象可以再它们经历第一次收集后还存活。如果一个最年幼的对象经历了好几次垃圾收集后仍然存活,那么这个对象就成长为寿命更高的一代。它被转移到另外一个子堆中去了。年龄更高的每一代的收集都没有年轻的那一代来的频繁。每当对象在它所属的年龄层变得成熟(逃过了多次垃圾收集),它们就被装一道更高的年龄层中去。

最后一种是自适应收集器:
自适应收集器就是不需要只选择一种特定的垃圾收集算法,可以使用多种技术,以便在每种技术最擅长的场合使用它们。

为了解决收集时出现可感觉出的停顿问题,来介绍火车算法:
之前的垃圾收集器都是需要停止整个程序来查找和收集垃圾对象,这种可能导致用户可察觉的停顿或者使得程序无法适合实时系统的要求,这种算法称作是破坏性的。所以要达到(或者试图达到)非破坏性垃圾收集的方法是使用渐进式收集算法。渐进式垃圾收集器就是不试图一次性发现并回收所有不可触及的对象,而是每次发现并回收一部分。
火车算法详细说明了按代收集的垃圾收集器的成熟对象空间的组织,火车算法目的就是为了在成熟对象空间提供限定时间的渐进收集。

火车算法把成熟对象空间划分为固定长度的内存块。算法每次在一个块中单独执行。为了更好的解释算法,把块叫做“车厢”,把集合叫做“火车”。使用这个比喻,成熟对象空间扮演火车站的角色,例如:
这里写图片描述

对象从更年轻的年龄层的子堆中提出来进入成熟空间。不管何时从年轻年龄层中提出,他们被附加到任何已经存在的火车上(最小号码的火车除外,因为是往更成熟的火车添加),或者专为容纳它们而创建出一列或多列火车。所以新的对象有两种方法到达火车站,要么被打包成车厢,挂接到除号码最小的火车的尾部,要么作为一列新的火车开进火车站。
每一次火车算法被执行的时候,它要么收集最小数字火车中的最小数字车厢,要么收集整列最小数字火车。算法首先检查指向最小数字火车中的任何车厢的引用,如果不存在任何来自最小数字火车以外的引用指向它内部包含的对象,那么整列最小数字火车包含的都是垃圾,可以被抛弃。假如最小数字火车被认为都是垃圾,火车算法归还火车中所有车厢的对象并且返回(此时火车算法执行结束)。如果不都是垃圾,那么算法把它的注意力都放到火车的最小数字车厢上。在这个处理过程中,算法或者转移或者释放车厢中的任何对象,算法首先把所有被最小数字车厢外部的车厢引用的对象转移到其他车厢去。当进行这个移动后,任何保留在车厢内的对象都是没有引用的,可以被垃圾收集。
保证整列火车中没有循环的数据结构的关键是算法如何移动对象。如果正被收集的车厢中有一个对象存在来自成熟对象空间以外的引用,这个对象被转移到引用正在被收集的火车之外的其他车厢上去。如果对象呗成熟空间的其他火车引用,对象就被转移到引用它的那列火车上去,然后转移过后的对象被扫描,查找对原车厢的引用。发现的任何被引用的对象都被转移到引用它的火车上去。新被转移的对象也被扫描,这个过程不断重复,直到没有任何来自其他火车的引用指向正被收集的那节车厢,如果接收对象的火车没有空间了,那么算法会创建新的车厢,并附加到那列火车的尾部。
一旦没有从成熟对象空间外部来的引用,也没有从成熟空间内其他火车来的引用,那么这节正在被收集的车厢剩余的外部引用都是来自于同一列火车的其他车厢。算法把这样的对象转移到这节最小数字火车的最后一个车厢去。然后这些对象被扫描,查找对原被收集车厢的引用。任何新发现的被引用对象也被转移到同一列火车的尾部,也被扫描。这个过程不断重复,直到没有任何形式的引用指向被收集的车厢。然后算法归还整个最小数字车厢占据的空间,释放所有仍然在车厢内的对象,并且返回。因此,在每一次执行时,火车算法或者收集最小数字的火车中最小数字的车厢,或者收集整列最小数字的火车。火车算法最重要的方面之一,就是它保证大型的循环数据会完全被收集,即使不能被放置在一个车厢中。因为对象被转移到引用它们的火车,相关对象会变得集中。最后,成为垃圾的循环数据结构中的所有对象,不管有多大,会被放置到同一列火车中去。增大循环数据结构的大小只会增大最终组成同一列火车的车厢数。因为火车算法在检查最小数字车厢之前,首先检查最小数字火车是否完全就是垃圾,它可以收集任何大小的循环数据结构。
为了促进收集过程,火车算法使用了记忆集合。一个记忆集合是一个数据结构,它包含了所有对一节车厢或者一列火车的外部引用。算法为成熟对象空间内每节车厢和每列火车都维护一个记忆集合。所以一节特定车厢的记忆集合包含了指向车厢内对象的所有引用的集合。一个空的记忆集合显式车厢或者火车中的对象都不再被车厢或者火车外的任何变量引用(已经被“遗忘”),被遗忘的对象就是不可触及的,可以被垃圾收集。

在Java语言里,一个对象可以拥有终结方法:这个方法时垃圾收集器在释放对象前必须运行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值