深入java虚拟机

来源:互联网 发布:淘宝生产许可证怎么办 编辑:程序博客网 时间:2024/05/17 03:10

 1.类装载器

  一个java应用程序可以使用两种类装载器:“启动”(bootstrap)类装载器和用户定义的类装载器。

 启动类装载器是系统中唯一的java虚拟机实现的一部分。通常使用某种默认方式从本地磁盘中装载类,包括java api的类。

 java程序能够在运行时安装用户定义的类装载器,能够使用自定义的方式来装载类,如从网络上下载class文件 。

 每一个类被装载的时候,jvm都将监视这个类,看到底是被启动类装载还是被用户自定义类装载器装载。当被装载的类引用了另一个类时虚拟机就会使用装载第一个类的类装载器装载被引用的类。

 2.类装载器体系结构在三个方面对java的沙箱起作用:

     1.防止了恶意代码干涉善意代码

     2.守护了被信任的类库的边界

     3.将代码归入某类(称为保护域),该类确定了代码可以进行哪些操作

3. class文件检验器

  和类装载器一样,class文件检验器保证装载的class文件内容有正确的内部结构,并且这些class文件相互协调一致。如果class文件检验器在class文件中发现了问题,他将抛出异常。好的java编译器不应该产生畸形的class文件,但jvm并不知道某个特定的class文件是如何被创建的。因为一个class文件实质时一个字节序列,所以jvm无法分辨特定的class文件是由正常的java编译器产生还是由黑客特制的,所以,所有的jvm的实现必须有一个class文件检验器,文件检验器可以调用class文件确保这些定义类型可以安全的使用。

   class文件检验器实现的目标之一是程序的健壮性,。如果某个有漏洞的编译器,或是某个聪明的黑客,产生了一个class文件,而这个class文件中包含了一个方法,这个方法的字节码中含有一条方法之外的指令,一旦这个方法执行,将导致虚拟机崩溃。

  jvm的class文件检验器在字节码执行之前,必须完成大部分的检验工作,只在执行前而不是在之心中对字节码进行一次分析,每次遇到一个跳转指令都进行检验。作为字节码确认工作的一部分,虚拟机将确认所有的跳转指令会达到一条合法的指令,而这条指令是在这个方法的字节码流中。

   class文件检验器需要进行4趟独立的扫描来完成工作。

   第一趟:class文件的结构检查。在类被装载的时候进行。在这次扫描中,class文件检验器检查这个class文件的内部结构,保证它可以安全的编译。

   第二趟:类型数据的语意检查。在连接过程中进行。在这次扫描中,检验器不需要查看字节码,也不需要查看和装载任何其他类型,而是查看每个组成部分,确认他们是否是其所属类型的实例,他们的结构是否正确。这样是为了却惹每个方法都是符合特定语法的、格式正确的字符串。

   第三趟:字节码验证。在连接过程中进行。jvm对字节流进行数据流分析,这些字节流代表的是类的方法。

                  字节码验证器并不试图检测出所有的安全问题,如果要这样做的话可能遇到“停机问题”。字节码验证器处理停机问题的方法:不去试图精确的让每个安全的程序都通过检查。

   第四趟:符号引用的验证。在动态链接过程中,解析符号引用时发生。在这次检查中,jvm将追踪那些引用--从被验证的class文件到被引用的class文件,以确保这个引用是正确的。由于必须要检查被检测的class文件以外的其他类,索引这次扫描可能需要装载新的类。

   大多数jvm的实现采用延迟装载类的策略,直到类真正被程序使用时才装载。

   动态链接是一个将符号引用解析为直接引用的过程。

4.JVM内置的安全特性

      1.类型安全的引用转换

      2.结构化的内存访问(无指针算法)

      3.自动垃圾收集(不必显示的释放被分配的内存)

      4.数组边界检查

      5.空引用检查

5.保护域

    当类装载器将类型装入jvm时,将为每个类型指派一个保护域,保护域定义了授予一段特定代码的所有权限。

6.访问控制器

   java.security.AccessController提供了一个默认的安全策略执行机制,使用栈检查来决定潜在不安全的操作是否被允许。这个访问控制器不能被实例化,他不是一个对象,而是集合在单个类中的多个静态方法。AccessController最核心的方法是静态方法:checkPermission(),这个方法决定一个特定的操作能否被允许。

7.java安全模型的不足

  不能解决恶意代码活动带来的问题:

    1.不断分配内存直到内存耗尽

    2.不断生成线程导致每件事都很慢

   这种类型的攻击称为拒绝服务(DOS)


第四章  网络移动性

 1、java体系结构对网络移动性的支持

  与java的平台无关性和安全性,以及对在网络上传送程序的时间管理有关。

  支持体现在:动态链接,动态扩展,紧凑的class文件,jar文件允许在一次网络传输过程中传送多个文件,不采取按需下载class文件,如在需要class文件之前就下载下来。


第五章 java虚拟机

   1.java虚拟机可能时如下三种:

      抽象规范、一个具体的实现、一个运行中的虚拟机实例

 2、虚拟机生命周期

    java虚拟机的职责:运行java程序。当启动java程序时,虚拟机就诞生,当该java程序关闭退出,虚拟机随之消亡

    虚拟机实例通过调用某个初始类的main()来运行一个java程序。

   jvm包含守护线程与非守护线程,守护线程通常由虚拟机自己使用,比如执行垃圾回收。

   只要有任何非守护线程运行,那么这个java程序也在运行(虚拟机依然活着)。当所有的非守护线程都终止,虚拟机实例自动退出。

3.java虚拟机体系结构



当虚拟机运行一个程序时,需要内存来存储很多东西,例如字节码,从以装载的class文件中 得到其他信息,程序创建的对象、传递给方法的参数、返回值、局部变量、以及运算的中间结果等,jvm把这些都组织到几个“运行时数据区”,以便管理。

尽管这些“运行时数据区”都会以某种形式存在于每一个java虚拟机实现中,但是规范对它们的描述还是抽象的。这些“运行时数据区”结构上的细节,大多由具体实现的设计者决定。

不同的虚拟机实现可能有不同的内存限制,有的实现可能有大量的内存可用,有的可能只有很少。有的实现可以利用虚拟内存,有的则不能。

当每一个新线程被创建,他都将他自己的pc寄存器(程序计数器)以及一个java栈,如果线程正在执行一个jaba方法(非本地方法)那么pc寄存器的值总是指示下一条将被执行的指令,而他的java栈总是存储该线程中java方法调用的状态--包括他的局部变量,被调用时传进来的参数,返回值,以及运算的中间结果等。而本地方法调用的状态,则是以某种依赖于具体实现的方式存储在本地方法栈中,也可能是在寄存器或其他某些与特定实现相关的内存中。

java栈是由许多栈帧或者帧组成那个的,一个栈帧包含一个java方法的调用,当线程调用一个java方法时,虚拟机压入一个新的栈帧到该线程的java栈中,当该方法返回时,这个栈帧被从java栈中弹出并抛弃。

java虚拟机没有寄存器,其指令集使用java栈来存储中间数据,为了保持java虚拟机的指令集尽量紧凑,同时也便于java虚拟机在那些只有很少通用寄存器的平台上实现。jvm这种基于栈的体系结构,也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。

4.数据类型

   基本类型和引用类型。java语言中的基本数据类型也都是jvm中的基本类型,但是boolean有点特别,虽jvm把boolean当作基本类型,单指令集对boolean只有很有限的支持,当编译器把java源码编译为字节码时,会用Int或者byte来表示boolean。在jvm中,false是用0表示,所有非0的整数都表示true,涉及boolean值的操作则会使用int,boolean数组是当作byte数组来访问的。

  在jvm内部使用的基本类型returnAddress,程序员不能使用这个类型,这个类型被用来实现java程序中的finally子句。

 jvm的引用类型:类类型、接口类型、数组类型。

    类类型是对类实例的引用;数组类型的值是对数组对象的引用,在jvm中,数组是个真正的对象;接口类型的值是对实现了该接口的某个实例的引用;特殊引用值null表示该引用变量没有引用任何对象。

5.字长的考量

  jvm中,最基本的数据单元时字,大小由每个虚拟机实现的设计者来决定,字长必须足够大,至少是一个字长单元就满足持有byte、short、int、char、float、returnAddress或者reference类型的值,而两个字单元就足以支持long或者double类型的值。

在运行时,java程序无法侦测到底层虚拟机的字长大小,同样虚拟机的字长大小也不会影响程序的运行--仅仅是虚拟机内部的属性。

6.类装载器子系统

  在jvm中,负责查找并装载类型的那部分被称为类装载器子系统。

  jvm有两种类装载器,启动类装载器(jvm实现的一部分)和用户自定义装载器(java程序的一部分)。

  顺序:

     1.装载---查找并装载类型的二进制数据

     2.连接---执行验证,准备,以及解析(可选)

            验证:确保被导入类型的正确性

            准备:为类变量分配内存,并将其初始化为默认值

            解析:把类型中的符号引用转换为直接引用。

     3.初始化--把类变量初始化为正确初始值。

每个类装载器都有自己的命名空间,维护者由他装载的类型。

 7.方法区

   在jvm中,关于被装载类型的信息存储在一个螺髻山被称为方法区的内存中,当虚拟机装载某个类型时,使用类装载器定位相应的class文件,然后读入这个class文件----一个线性二进制数据流,然后将他传输到虚拟机中,紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区,该类型中的类(静态)变量也是存储在方法区。

所有线程都共享方法区,他们对方法区的访问必须是线程安全的。方法区大小是固定的,虚拟机可以根据需要动态调整,方法区也是不连续的,方法区可以在一个堆中自由分配。也可允许用户或者程序员指定初始大小以及最小和最大值。

方法区可以被垃圾回收,因为虚拟机运行通过用户定义的类装载器来动态扩展java程序,所以有一些类也会成为程序的“不再引用”的类。

8.堆

  java程序在运行时创建的所有类实例或数组斗放在同一个堆中。而一个jvm实例中只存在一个堆,因此所有线程都共享这个堆,又由于一个java程序独占一个jvm实例,因而每个java程序都有它自己的对空间--不会干扰彼此,单同一个java程序的多个线程共享者同一个堆空间,这时应考虑多线程访问堆数据的同步。

 jvm中有一条在堆中分配新对象的指令,却没有释放内存的指令,这个释放交由GC。

8.程序计数器(PC)

   对于一个运行的java程序,其中的每一个线程都有自己的PC寄存器,在线程启动时创建的,PC寄存器的大小是一个字长。当线程执行某个java方法时,PC寄存器的内容总是指向下一条将被执行指令的“地址”。如果正在执行一个本地方法,PC寄存器的值时undefined。


第六章  java class文件

1.java class文件

    是对java程序二进制文件格式的精确定义,一个class文件中只能包含一个类或者接口。无论虚拟机在何种系统运行,java class文件的精确定义使得所有jvm都能够正确读取和解释所有java class文件。

2.全限定名

   在class文件中,全限定名中的点用斜线取代了


第七章  类型生命周期

1.类型装载、连接、初始化

jvm通过装载、连接、初始化一个java类型,使该类型可以被正在运行的java程序使用。

装载:jvm应该

      1.通过该类型的全限定名,产生一个代表该类型的二进制数据流

      2.解析这个二进制数据流为方法区内的内部数据结构

      3.创建一个表示该类型的java.lang.Class类的实例

验证:1.检查final类不能拥有于类

               2.检查final方法不能被覆盖

               3.确保在类型和超类型直接没有不兼容的方法声明

               4.检查所有的常量池入口相互之间一致

               5.检查常量池中的所有特殊字符串

               6.检查字节码的完整性

初始化:1.如果类存在直接超类,且直接超类还没有被初始化,就先初始化直接超类。

               2.如果类存在一个类初始化方法,就执行此方法。

2.对象的生命周期

   一旦一个类被装载、连接、初始化,它就随时可以使用了。

2.1类实例化:

   类可以被明确或者隐含的实例化,实例化类的4种途径:

     1.明确使用new操作符

     2.调用Class类或者java.lang.reflect.Constructor对象的newInstance()

     3.调用任何现有对象的clone()方法

     4.通过java.io.ObjectInputStream的getObject()反序列化。

2.2 垃圾收集和对象的终结

   程序可以明确或隐含的分配内存,但不会明确的释放内存,当一个对象不再使用了,虚拟机必须回收那部分内存。

   实现可以决定何时应垃圾收集不再引用的对象--或决定是否根本不收集他们,并没有要求jvm实现一旦要释放不再被引用的对象占据的内存。

  如果声明了finalize(),垃圾收集器会在释放这个实例所占据的内存空间之前执行这个方法一次。

 垃圾收集器自动调用finalize()方法抛出的任何异常都将被忽略,垃圾收集器可以用任意顺序调用finalize(),使用任意线程,甚至并行使用多线程。

3.卸载类型

  当程序不再引用类的时候可以可选的卸载。

 类的垃圾收集和卸载之所以重要,因为java程序可以在运行时通过用户自定义的类装载类型来动态扩展程序,所有被装载的类都在方法区占据内存空间。如果java程序持续通过用户定义的类型装载器装载类型,方法区占用就会迅速增长。

如果某些动态装载的类型只是临时需要,当他们不再被引用之后,占据的内存空间可以通过卸载类型而释放。

jvm判断一个动态装载的类型是否被程序需要:

   如果程序不再引用某类型,那么这个类型就无法再对未来的计算过程产生影响,变成类型不可触及的,而且可以被垃圾回收。

   使用启动类装载器装载的类型永远是可触及的,永远不会被卸载。只有使用用户定义的类装载器装载的类型才会变成不可触及的,从而被虚拟机回收,如果某个类型的Class实例被发现无法通过正常的垃圾收集堆触及,那么这个类型就是不可触及的。

  判断动态装载的类型的Class实例在正常的垃圾收集过程中是否可以触及的两种方式:

  1.如果程序保持堆Class实例的明确引用,它就是可触及的

  2.如果在堆中还存在一个可触及的对象,在方法区中它的类型数据指向一个Class实例,那么这个Class实例就是可触及的。


第八章  连接模型

 1.动态连接和解析

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

1.1 动态扩展java程序

   通过传递类型的名字到java.lang.Class的forName()或者用户自定义的类装载器的loadClass(),即可实现动态扩展。

用户自定义的类装载器可以从java.lang.ClassLoader的任何子类创建。


第九章  垃圾收集

   垃圾收集是自动释放不再被程序使用的对象的过程。

   其实更精确的说法是“内存回收”。

   当一个对象不再被程序引用时,它所使用的堆空间可以被回收,以便后续的新对象使用。垃圾收集器必须能断定哪些对象是不再被引用的,并且能够把他们所占据的堆空间释放出来。在释放不再被引用的对象的过程中,垃圾收集器运行将要被释放的对象的终结方法(finalizer)。

  除了释放不再被引用的对象,还要处理堆碎块。堆碎块是在正常程序运行过程中产生的。新的对象分配了空间,不再被引用的对象被释放,所以堆内存的空闲位置介于活动的对象之间。请求分配新对象时可能不得不增大堆空间的大小,虽然可以使用的总空闲空间是足够的。在一个虚拟内存系统中,增长的堆所需要的额外分页(或交换)空间会影响运行程序的性能。在内存较小的嵌入式系统中,碎块导致虚拟机产生不必要的“内存不足”错误。

  jvm垃圾收集:

      1.提高生产效率,在一个不具备垃圾收集机制的语言下,需要花很多时间解决内存问题。

      2.帮助程序保持完整性。垃圾收集是java安全策略的一个重要部分,程序员不可能因失误或故意错误的释放内存而导致jvm崩溃。

     jvm垃圾收集缺陷:加大了程序负担,可能影响程序的性能。jvm必须追踪哪些对象被正在执行的程序所引用,并且动态的终结并释放不再被使用的对象,和明确释放不再被使用的内存比较起来,这个活动需要更多的CPU时间。并且在垃圾收集环境下,程序员对安排CPU时间来释放无用对象缺乏控制。


2.垃圾收集算法

  垃圾收集算法需要做的两件事:

    1.检测出垃圾对象

    2.回收垃圾对象所使用的堆空间并还给程序

垃圾检测通常通过建立一个根对象的集合并且检查从这些根对象开始的可触及性来实现。

如果正在执行的程序可以访问到的根对象和某个对象之间存在引用路径,这个对象就是可以触及的。对于程序来说,根对象总是可以访问的,从这些根对象开始,任何可以被触及的对象都被认为时活动的对象,无法被触及的对象被认为是垃圾,因为他们不再影响程序未来的执行。

   任何被根对象引用的对象都是可触及的,从而是活动的,另外,任何被活动的对象引用的对象都是可触及的,程序可以访问任何可触及的对象,所以这些对象必须保留在堆内。任何不可触及的对象都可以被收集,因为程序没有办法访问。

3.引用计数收集器

   是垃圾收集的早期策略。在这种方法中,堆中每一个对象都有一个引用计数,当一个对象被创建了,并且指向该对象的引用被分配给一个变量,这个对象的引用计数被置为1.

   当其他任何变量被赋值为对这个对象的引用时,计数加1.但一个对象的引用超过了生存期或者被设置新的值时,对象的引用计数减1.任何引用计数为0 的对象可以被当作垃圾收集,当一个对象被垃圾收集的时候,他引用的任何对象计数值减1,在这种方法中,一个对象被垃圾收集后可能导致后续其他对象的垃圾收集行动。

  好处:引用计数收集器可以很快执行,交织在程序的运行之中,这个特性对于程序不能被长时间打断的实时环境很有利。

  坏处:引用计数无法检测出循环(即两个或者更多对象的相互引用)。

4.追踪收集器

  追踪从根节点开始的对象引用图。在追踪过程中遇到的对象以某种方式打上标记。总的来说,要么在对象本身设置标记,要么用一个独立的位图设置标记。当追踪结束时,未被标记的对象就知道时无法触及的,从而可以被收集。

 基本的追踪算法被称作“标记并清除”。这个名字指出了垃圾回收的两个阶段,在标记阶段,垃圾收集器遍历引用树,标记每一个遇到的对象。在清除阶段,未被标记的对象被释放了,使用的内存被返回到正在执行的程序。

  在jvm中,清除步骤必须包括对象的终结。

5. 压缩收集器

   把活动的对象越过空闲区滑动到堆的一端,在这个过程中,堆的另一端出现一个大的连续空闲区,所有被移动的对象的引用也被更新,指向新的位置。

  更新被移动的对象的引用有时候通过一个间接对象引用层可变的更简单。不直接引用堆中的对象,对象的引用实际上指向一个对象句柄表。对象句柄才指向堆中对象的实际位置。当对象被移动了,只有这个句柄需要更新为新位置。所有程序中对这个对象的引用仍然指向这个具备新值的句柄,而句柄本身没有移动。这种方法简化了消除堆碎块的工作,但是每一次对象访问都带来了性能损失。

6. 拷贝收集器

   把所有的活动对象移动到一个新的区域。在拷贝的过程中,他们被紧挨着布置,所以可以消除原本他们在旧的区域的空隙。现有的区域被认为是空闲区,这种方法的好处是对象可以从根对象开始的遍历中随着发现被拷贝,不再有标记和清除的区分。对象被快速拷贝到新区域,同时转向指针仍然留在原来的位置。转向指针可以让垃圾收集器发现已经被转移的对象的引用。然后垃圾收集器可以把这些引用设置为转向指针的值,所以他们现在指向对象的新位置。

 一般的拷贝收集器算法被称为“停在并拷贝”。在这个方案中,堆被分为两个区域,任何时候都只使用其中的一个区域。对象在同一个区域中分配,直到这个区域被耗尽。此时程序执行被中止,堆被遍历 ,遍历时遇到活动对象被拷贝到另一个区域。当停止和拷贝结束时,程序恢复执行。内存从新的堆区域中分配,直到它也被用尽。那时程序再次中止,遍历堆,活动对象又被拷贝回原来的区域,这种方法的代价就是:对于指定大小的堆来说需要两倍大的内存,因为任何时候都只能使用其中的一半。

7.按代收集的收集器

   简单的停止拷贝收集器缺点:每一次收集时,所有的活动对象都必须拷贝。

   但大多数程序的特点:

      1.大多数程序创建的大部分对象都具有很短的生命周期

      2.大多数程序都创建一些具有非常长生命周期的对象。

简单拷贝收集器浪费效率:每次都把生命周期很长的对象来回拷贝,消耗大量时间。

按代收集器:

    通过把对象按照寿命来分组解决这个效率低下的问题,更多的收集那些短暂出现的年幼对象,而非寿命较长的对象。

  在这种方法里,堆被划分为两个或者更多的子堆,每个子堆为一“代”对象服务。最年幼的那一代进行最频繁的垃圾收集。因为大多数对象都是短促出现的,只有很少部分年幼对象可以在他们经历第一次收集后还存活。如果一个最年幼的对象经历了好几次垃圾回收后仍然存活,,那么这个对象就成为寿命更高的一代:它被转移到另外一个子堆中去。年龄更高的每一代都没有年轻那一代来的频繁。每当对象在它所属的年龄层(代)中变得成熟(逃过了很多次垃圾回收)之后,它就被转移到更高的年龄层(代)中去。

  按代收集除了可以用于拷贝算法,还可以用于标记清除算法,不管在哪种情况下,把堆按照年龄分层都可以提高最基本的垃圾收集算法的性能。

8.自适应收集器

   在某种情况下,某些垃圾收集算法工作的更好,而另外一些收集算法在另外的情况下工作的更好。自适应算法监视堆中的情形,并且对应的调整为合适的垃圾收集技术。在程序调整的时候可能会调整某种简单的垃圾收集算法的参数,也可能快速转换到另一种不同的算法。或者把堆分为子堆,同时在不同的子堆中使用不同的算法。


9. 火车算法

  垃圾收集算法与明确释放对象比起来存在一个潜在的缺点:

      垃圾收集算法中程序员对安排CPU时间进行内存回收缺乏控制。要精确的测出何时(甚至是否)进行垃圾收集、手机需要多长时间,基本上都是不可能的。因为垃圾收集一般都会停止整个程序的运行来查找和收集垃圾对象,他们可能在程序执行的忍一时刻暂停,并且暂停的时间也不确定。垃圾收集也使得程序队事件响应迟钝,无法满足实时系统的需求。

 如果一种垃圾收集算法可能导致用户可察觉到的停顿或者使得程序无法适合实时系统要求,这种算法称为破坏性的。为了减少垃圾收集和明确释放对象之间潜在的差距,设计垃圾收集算法的一个基本目标是使本质上的破坏性尽可能的减少,如果可能的话尽可能取消这种破坏性。

 达到非破坏性垃圾收集算法:使用渐进式收集算法。即不试图一次性发现并回收所有不可触及对象,而是每次发现并回收一部分,因为每次只有堆的一部分执行垃圾收集。

通常渐进式收集器都是按代收集的收集器,大部分调用中,都是收集堆的一部分。

。。。。这个火车算法就不看了,感觉复杂。。。。

10.终结

   在java语言里,一个对象可以拥有终结方法,这个方法是垃圾收集器在释放对象前必须运行。这个可能存在的终结方法使得任何jvm的垃圾收集器要完成的工作更加复杂。

 垃圾收集器必须检查它所发现的不再被引用的对象是否存在finalize()方法。

存在终结方法finalize()时,jvm的垃圾收集器必须每次在收集时执行一些额外的步骤。

第一遍扫描

首先垃圾收集器必须使用某种方法检测出不再被引用的对象(称作第一遍扫描),然后它必须检查它检测出的不再被引用的对象是否声明了finalize(),如果时间允许的话,可能在这个时候垃圾收集过程就着手处理这些存在的终结方法。

第二遍扫描

当执行了所有的终结方法finalize()后,垃圾收集器必须从根节点开始检测不再被引用的对象(第二遍扫描)。这个步骤时必要的,因为终结方法可能复活了某些不再被引用的对象,使他们再次被引用了。

最后,垃圾收集器才能释放那些在第一次和第二次扫描中发现的都没有被引用的对象。

是垃圾收集器运行对象的终结方法finalize()

11.对象可触及性的生命周期

 在垃圾收集器看来,堆中每个对象都存在以下状态之一:

  可触及,软可触及,弱可触及,影子可触及,可复活的,不可触及的

11.1 引用对象

  所有引用对象都是java.lang.Reference类的子类的实例,包含三个直接子类:

   SoftReference:封装了对引用目标的“软引用”;

   WeakReference:封装了对引用目标的“弱引用”;

   PhantomReference:封装了对引用目标的“影子引用”。

  强引用与软引用、弱引用、影子引用之间最基本的差别:强引用禁止引用目标被垃圾收集,而软引用、弱引用、影子引用不禁止。

   一旦一个引用对象创建后,它将一直维持到它的引用目标的软引用、弱引用、影子引用,直到被程序或者垃圾收集器清除。要清除一个引用对象,程序或者垃圾收集器只需要调用引用对象的clear(),这个方法是Reference定义的。通过清除引用对象,就切断了引用对象的软引用、弱引用、影子引用。

11.2 可触及性状态的变化

   强可触及:对象可以从根节点不通过任何引用对象搜索到,对象生命周期从强可触及状态开始,并且只要有根节点或者另外一个强可触及对象引用他,就保持强可触及状态,垃圾收集器不会试图收回强可触及对象占据的空间。

  软可触及:对象不是强可触及的,但是可以从根节点开始通过一个或多个(未被清除的)软引用对象触及。垃圾收集器可以回收软可触及的对象所占据的内存。如果这发生了,他会清除所有到此软可触及对象的软引用。当垃圾收集器清除一个和引用队列有关联的软引用队列时,它把该软引用对象加入队列。

  弱可触及:对象既不是强可触及也不是软可触及的,但是从根节点开始可以通过一个或多个(未被清除的)弱引用对象触及。垃圾收集器必须归还弱可触及对象所占据的内存。这发生的时候,他会清除所有到此弱可触及对象的弱引用。当垃圾收集器清除一个和引用队列有关联的弱引用对象时,它把该弱引用对象加入队列。

  可复活的:对象既不是强可触及的、软可触及,也不是弱可触及,但是仍然可以通过执行某些终结方法复活到这几种状态之一。

  影子可触及:对象不是强可触及、软可触及,也不是弱可触及,并且已经断定不会被任何终结方法复活(如果它自己定义了终结方法,并且已经运行过了),并且它可以从根节点开始通过一个或多个(未被清除的)影子引用对象触及。一旦某个被影子引用的对象变成影子可触及状态,垃圾收集器立即把该引用对象加入队列。垃圾收集器从不清除一个影子引用,所有的影子引用都必须由程序明确的清除。

  不可触及:一个对象既不是强可触及、弱可触及、软可触及,也不是影子可触及,并且它不可复活。不可触及的对象已经准备好被回收了。

原创粉丝点击