`

《深入java虚拟机》读书笔记 转

 
阅读更多

一、java体系结构

 1、java程序设计语言
 2、java class 文件格式
 3、java 应用编程接口(API)
 4、java 虚拟机

 

 a、java虚拟机和API 一起组成一个平台,java虚拟机的任务是装载class文件并且执行其中的字节码。

  执行引擎分类
  (1) 一次性解释字节码。
  (2) 即使编译器(just-in-time compiler) 被执行的字节码会被编译成本地机器代码,编译出的本地机器代码会被
  缓存。当方法以后被调用的时候可以重用。

  Java程序通过两种方式来访问底层操作系统资源,Java API 和JNI(java native interface) 前者和平台无关,后者和平台相关。


 b、类装载器

  
           图 java虚拟机基本结构图
  当一个类被装载时,Java虚拟机就监视这个类,看它是被启动类装载器装载还是被用户装载器装载,
  当被装载的类引用另外一个类时jvm就会使用第一个类装载器转载被引用的类。
  java体系允许在一个java应用程序中建立多个命名空间。运行时的java程序中的每一个类加载器都有自己的命名空间。

  一个java应用程序可以使用两种类装载器:启动(bootstrap)类装载器和用户自定义的类装载器。启动类装载器是Java虚拟机实现的

  一部分,启动类装载器采用默认方式从磁盘上装载类,包括Java API类。

  用户自定义装载器能够使用自定义方式来装载类,比如从网络上下载class文件。

                       图 主机操作系统上又软件实现的java虚拟机

 c、java class文件
  java class 文件主要是在平台无关性和网络移动性方面使java更适应于网络。
  c或者c++平台相关性,是因为C++程序被编译后的二进制文件只能在指定的硬件平台和操作系统上运行,
  因为这个二进制文件包含了目标处理器
  的机器语言。而java编译器把java源文件的指令翻译成字节码。这种字节码就是java虚拟机的"机器语言"。

 

 d、java API
  java API通过支持平台无关性和安全性,使的java适应于网络应用。Java API是运行库的集合,
  它提供一套访问主机资源的访问标准。
  Java API 调用本地方法来访问底层资源,通过Java API class 文件为底层提供了具有平台无关性,标准接口的java程序。
  思考:我们自己写的java程序调用本地方法就具有平台相关性,为什么Java API 调用本地方法就不具有平台相关性呢?
  java安装在各个平台上的JDK各自实现了各个相关本地方法。
  windows下有windows下的本地方法
  Linux下有Linux下的本地方法
  MAC下有MAC下的本地方法。
  这些都已经在JDK下都实现好了。
  java API的安全性,当java API方法进行任何有潜在危险的操作(比如进行本地磁盘读写),
  都会通过查询安全管理器来检验是否得到授权。

 

 e、Java语言
  java语言和c++比较主要缺点是执行速度慢,但是java在在提高开发者效率方面和c++是有差别的,这种差别主要就是直接内存操作的约束。
  差别一:在Java中没有使用强制转换类型或者通过指针运算来直接访问内存方法,比如存在一个A类对象的引用,它只能作为A使用
  而不能强制转换为B类型,更不会向C++指针运算那样,简单地给引用加上偏移量也是不行的。
  差别二:自动垃圾回收机制,c++需要显示的 删除不需要的对象,而java垃圾回收器会自动回收这个对象占据的内存。
  差别三:数组边界检查,c++中数组操作实际上是指针运算,这会带来潜在的内存冲突,在c++中,数组有10个成员,然后
           再向第11个成员写入(尽管这是错误的写法,但c++不会限制这样做)。单在java中,会抛出一个异常。
  差别四:对象引用的检查,每次使用引用的时候,java 都会确保这些引用不能为空值,在c++中通常会导致程序崩溃。

 

二、平台无关
 java语言和平台无关性。它的基本数据类型的值域和行为都是由语言自己定义的。在c/c++中,基本数据类型int的值域是由它的占
 位宽度决定的,而它的占位宽度是由目标平台决定。一般c/c++中int的占位宽度是由编译器根据目标平台的字长来决定。然而对于
 java的int 都是由32位二进制补码表示的有符号位整数。而float是遵循IEEE 754浮点标准的32位浮点数。
 java class 文件。class文件定义了一个特定java虚拟机的二进制格式。它可以在任何平台上创建,也可以被任何平台的java虚拟机
 装入并运行。
 本地方法。当编写一个独立的java程序时,必须遵循一条重要的原则,不要直接或者间接调用不属于java API的本地方法。调用java
 API 以外的任何方法都会使程序平台相关。

 

三、安全性
 Java安全模型侧重于保护用户免受从网络下载的、来自不可靠来源的、恶意程序的侵犯。为了达到这个目的,Java提供了一个用户可以
 配置的"沙箱",在沙箱中可以放置不可靠的Java程序。沙箱对不可靠程序的活动进行了限制。程序可以在沙箱安全边界以内做任何事情
 但不能进行任何跨越这些边界的举动。比如对不可靠程序禁止对本地磁盘的读写、创建新的线程、装载新的动态连接库等。
 组成沙箱的基本组件:
 (1)、类装载器结构。
  类装载器可以防止恶意代码去干涉善意代码是通过不同的装载器装入的类提供不同的命名空间来实现的。命名空间是由一系列
  唯一的名称组成。每一个被装载的类都有一个名称,命名空间是由java虚拟机为每个类装载器维护的。


                          图 类装载器和命名空间

                           图 类装载器的委派链

 

在委派链中,如果装载器试图通过网络下载来装载一个类,首先当前装载器必须先询问他的双亲即"类路径装载器",来查找并装载这个类。如果双亲能找到这个类,
  则双亲直接装载,如果找不到继续委托给"扩展类装载器",然后是"启动装载器"。整个过程都没找到,那么就网络装载器开始
  下载class 文件并且装载,如果找到则直接装载,而不需要下载。所以,如果网络装载器要装载一个java.lang.Integer类,是不会
  成功的。因为这个类在java API中已经有了,启动加载器抢先加载,使用这种方式可以防止不可靠的代码用在及的版本来替代可信任的类。
  
 (2)、class 文件检验器。
  保证装载的class 文件内容有准确的内部结构,并且这些class 文件互相协调一致。java虚拟机在检验字节码执行之前,必须完成
  大量的检验工作。class 文件检验分四步来完成


  a、class文件的结构检查,确认加载的class文件是否符合java class文件的基本结构。比如class 文件的开头必须以oxCAFEBABE.
   检验器必须检验class文件中声明的主版本号和次版本号和class文件的正确长度。


  b、类型数据的语义检查,final类没有被子类化,final 方法没有被覆盖,常量池中的条目是否合法。它在检查了一些java 语言
   应该在编译时遵守的强制规则。因为检验器并不能确定class 文件是否是由一个善意的,没有漏洞的编译器产生。


  c、字节码检验,Java虚拟机对字节流进行分析,这些字节流代表的是类的方法。它是由操作码的单字节指令组成的序列。每一个操作
  码后面都跟一个或多个操作数。操作数用于在java虚拟机执行操作码指令提供所需的额外数据。

  字节码检验器确保采用任何路径的字节流中得到一个正确的操作码,确保操作数栈(内存一个片段,用于存储方法中间结果)正确的数据
  和正确的类型。


  d、符号引用验证,在动态链接的过程中,如果包含在一个class文件中的符号引用被解析,class文件检验器将进行第四趟检查。
   从被验证的class文件到被引用的class 文件,以确保这个引用正确。在装载新类的时候大多数虚拟机都采用延迟加载的策略,
   直到类真正被程序使用时才算装载。当这个引用类是非法引用时,比如这个类不能装载,或者不包含被引用的字段和方法
   class文件检验器将抛出一个错误。

 

 (3)、内置虚拟机安全特性。
  除了上述四趟安全检查,java虚拟机在执行字节码时还进行了内置安全机制操作。包括
  类型安全引用转换。
  结构化的内存访问。
  自动垃圾回收。
  数组边界检查。
  空引用检查。


 (4)、安全管理器及Java api。
  安全管理器主要是定义了沙箱的外部边界,主要作用是保护虚拟机的外部资源不被虚拟机内部运行的恶意或有漏洞的代码侵犯。
  当java api 进行任何可能不安全的操作的时候,它都会向安全管理器请求许可,从而强制执行自定义的安全策略。要向安全管理器请求
  许可,Java API将调用安全管理器对象的"check"方法(checkRead,checkWrite)。
  当java应用程序启动的时候,它还没有安全管理器,但是应用程序通过将一个指向java.lang.SecurityManager或者子类的实例传递
  给setSecurityManager(),以此来安装管理器。
  一般来说,当一个java API即将进行一个潜在的不安全的动作时,它将遵循以下两个步骤:首先API代码检查有没有安装安全管理器,
  如果没有安装,则跳过第二步直接执行这个潜在的不安全动作,否则,在第二步中,它将调用安全管理器中合适的"check"方法。如果
  这个方法被禁止,那么这个"check"方法会抛出安全异常。
 
四、网络移动性
 平台无关性和安全模式都是处理网络计算环境下软件开发人员所面临的两大挑战。网络移动性的两个典型事例java applet和jini

 

五、java 虚拟机
 (1) java 虚拟机的生命周期,运行一个Java程序,一个虚拟机的事例就诞生。当该程序关闭退出时,这个虚拟机事例也就消失了。
  java 虚拟机事例通过调用某个初始类的main()方法来运行一个java程序。java程序初始类中的main()方法,将作为该程序初始
  线程的起点,任何其它线程都是由这个初始线程启动的。
  java虚拟机后台有两种线程:守护线程和非守护线程。守护线程通常是虚拟机自己使用的,比如执行垃圾回收的线程,但是
  java程序可以把他创建的任何线程都标识为守护线程。
  但是main()方法执行的线程是非守护线程,只要还有任何非守护线程在运行,那么这个java程序将进行,当所有的非守护线程都
  结束了,那么虚拟机将自动退出。也可以通过System.exit()来退出。


 (2) java 虚拟机的体系结构
  虚拟机实例的行为分别按照子系统,内存区,数据类型和指令几个术语来描述。每个虚拟机都有一个类装载器,每个虚拟机都有
  一个执行引擎,它负责执行那些包含在被装载类的方法中的指令。


                              图 java虚拟机的内部体系结构
  当java虚拟机运行一个程序时,它需要内存来存储许多东西,字节码,程序创建的对象,传递给方法的参数,返回值,局部变量,
  以及运算的中间结果,java虚拟机把这些东西组织在"运行时数据区"中。

                          图 java虚拟机的数据类型
  数据类型分为基本类型和引用类型,基本数据类型的变量持有原是值,原是值是真正的原始数据,而引用值是对象的引用而不是
  对象本身。
  当编译器把java源码编译成字节码的时候,它会用int或者byte来表示boolean.false用整数0表示。true所有非零整数表示。
  boolean数组是用byte数组来表示的。
  引用类型包含类类型,接口类型和数组类型。类类型的值是对象实例的引用,数组类型的值是数组对象的引用,数组是个真
  正的对象。接口类型的值对实现了该接口的某个实例的引用。


  a、java 方法区
   当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件,然后传入java虚拟机中,并提出其中
   的类型信息,并将这些信息存储到方法区里。包括变量和静态变量。当程序运行时,虚拟机会把程序运行时创建的对象都放入堆中。
   由于所有的线程都可以共享方法区,所以方法区的设计被设计成线程安全的。假如两个线程都企图访问一个java类。
   而这个类还没有装入java虚拟机,那么这时应该由一个方法去装载,而另外一个线程只能等待。
   方法区存储的信息有:
   类型的全限名
   类型的访问修饰符
   类型常量池
   字段信息
   方法信息
   静态变量
   ClassLoad引用
   Class 类引用


  b、堆
   java程序在运行时创建的所有类实例或者数组都放在堆中,一个java虚拟机实例只能有一个堆空间,因此所有的线程都共享
   这个堆,又由于java程序独占一个java虚拟机实例,因而每个java程序都有它自己的堆空间,它们彼此不干扰。但是一个java
   程序的多个线程却共享着同一个堆空间。因此需要考虑线程访问对象的同步问题了。
   堆空间也不必是连续的内存空间,在程序运行时它可以动态的扩展和收缩。只要有一个对象引用虚拟机就必须快速定位对象实例
   的数据。另外通过对象引用访问相应的类数据(存储于方法区的类型信息)。因此在对象中通常有一个指向方法区的指针。


                                  图:堆中对象数据
   在Java中数组是真正对象,数组和其它的类一样,拥有一个和他们相关联的Class实例。所有具有相同维度和类型的数组都是同
   一个类的实例。


                               图:用堆表示的数组
   在堆中每个数组对象必须保存数组的长度,数组数据,以及数组类数据的引用(方法区的类型信息)


  c、栈
   每当启动一个新的线程的时候,java虚拟机都会为他分配一个java栈。java栈是由许多帧组成的。java虚拟机只会对Java
   栈执行两种操作:以帧为单位压栈或者出栈。当一个线程调用一个java方法的时候,虚拟机就会在该java行程的java栈中
   压入一个新帧,这个新帧就是当前帧。这个方法就是当前方法,这个方法所在的类就是当前类,当前类的常量池就是
   当前常量池。当前方法会跟踪当前类和当前常量池。当前帧会存储参数,局部变量,中间运行结果。
   java方法可以两种方式完成,通过return返回或者抛出异常终止。虚拟机都会将当前的帧弹出java 栈然后释放掉,这样
   上一个方法的帧就成为当前帧。


  d、执行引擎
   运行java程序的每个线程都是一个独立的虚拟机执行引擎的实例,从线程生命周期的开始和结束,它要么在执行字节码,要么
   在执行本地方法。
   抽象的执行引擎每次执行一条字节码指令。java虚拟机中运行程序的每个线程(执行引擎实例)。
   计算机指令一般由操作码和操作数两部分组成。操作码和操作数,操作码决定要完成的操作,操作数指参加运算的数据及其所在的单元地址。
  
六、java class 文件
 java class文件是8位字节二进制流。数据项按照顺序存储在class文件中。占据多个字节空间的项按照高位在前的顺序分为几
 个连续字节存放。不同大小的项大小和长度位于实际数据之前。这个特性使的class文件流可以从头到尾被顺序解析。首先读取
 数据项的大小然后读取数据项的数据。
 class 文件内容:
 (1)、魔数(magic number),每个class 文件的前四个字节oxCAFEBABE,作用在于分辨java文件和非java 文件。
 (2)、版本号(version)
 (3)、常量池(constant_pool) 常量池存储了文字字符串,final变量值,类名和方法名的常量。
 (4)、访问标志(access_flags) 展示文件定义的类或者接口的几段信息。比如是类还是接口,使用那种修饰符,类和接口是
   抽象的还是公共的。
 (5)、fields_count和fields 字段和字段信息的描述,包括字段的名字,描述符,修饰符。
 (6)、methods_count和methods 方法和方法信息的描述,包括方法名和描述符,方法所捕获的异常表
  
七、类型和生命周期
 当一个class文件被装入java虚拟机一直到退出,整个过程经历了装载、连接、初始化、以及对象实例化,垃圾回收,对象终结,
 卸载几个主要阶段。
 (1)、类型装载、连接、初始化
  java虚拟机通过装载、连接、初始化一个java类型,使该类型可以被正在运行的java程序所使用。装载就是把二进制形式的
  java类型读入java虚拟机中,而连接就是把这种已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行状态。
  连接分为三个子步骤:验证、准备、解析。验证确保java类型数据格式正确。准备负责为该类型分配所需要的内存。
  解析负责常量池中的引用转换为直接引用。初始化阶段类变量赋初始值。


                        图 类型生命周期的开始
 所有的类和接口首次主动使用时初始化,下面六种情况符合主动使用的要求
  1) 当创建某个类的新实例时(通过new 或者反射克隆或者反序列化)。
  2) 当调用某个类的静态方法。
  3) 当使用某个类或者接口的静态字段。
  4) 当调用java API中的某些反射方法。比如类Class中的方法或者java.lang.reflect包中的类的方法。
  5) 当虚拟机启动时某个被标识为启动类(即含有main()方法的那个类)。
  6) 当初始化某个类的子类时(某个类初始化时,要求超类已经被初始化),接口除外。
 装载:装载分三个阶段,要装载一个类型,java虚拟机必须
  1) 通过类型完全限定名,产生一个代表该类型的二进制数据流。
  2) 解析这个二进制流为方法区内的内部数据结构。
  3) 创建一个表示该类型的java.lang.Class 类实例。
  装载的最终产品就是这个Class类实例对象。它成为java程序和内部数据结构之间的接口。要想访问关于该类型的信息(
  他们是存储在内部数据结构中的)就要调用该类型对应的Class实例的方法,这样一个过程就是把一个类型的二进制数据
  解析为方法区的内部数据结构、并在堆上建立一个Class对象。
 验证: 在类型被装载以后就需要连接了,连接的第一步就是验证。确认类型符合java语言的语义,并且它不会危及虚拟机的
  完整性。
 准备:在准备阶段,虚拟机为变量分配内存,设置默认初始值。单在真正的初始化阶段之前,类变量没有被初始化真正的初始值。


                              图 默认的初始化值
  在准备阶段,虚拟机实现可能为一些数据结构分配内存。比如方法表,它包含指向类中的每个方法(包括从超类继承的方法)的
  指针,方法表可以使继承的超类执行时不需要搜索超类。
 解析:解析的过程就是在类型常量池中寻找类、接口、字段、方法的符号引用、把这些符号引用替换成直接引用过程。
 初始化:为类变量赋予正确的初始值,准备阶段赋予的是默认的初始值,而在初始化阶段初始值是根据程序员制定的主观计划而生成的。
  初始值是类变量的初始化语句或者静态的初始化语句。语句是变量声明后面的符号或者表达式。
  class Exam{
   static int size = 3 × 0.5;
  }
  
  静态初始化语句是一个以static开头的字符串
  class Exam{
   static int size;
   static {
    size = 3*0.5;
   }
  }
  这些初始化都被java虚拟机收集到一起,放到一个特殊的方法里面。对于类来说,这个方法被称为类初始化方法;对于接口来说
  称为接口初始化方法。在class文件里被称为<clinit> 。通常java程序方法是无法调用这个<clinit>方法的。这种方法只能被
  java虚拟机调用。如果类没有声明任何类变量或者静态初始化语句,就不会有<clinit>方法。如果声明了类变量,但没有明确
  使用初始化语句也是没有<clinit>方法的,如果类仅包含静态的final变量的类变量初始化语句,而且采用编译时的常量表达式
  也不会在有<clinit>方法。只有那些的确需要执行java代码来赋予类变量正确初始值的类才会有类初始化方法(<clinit>)
  class Exam {
   static final width =15;
   static final length = width * 2;
  }//这个没有<clinit>方法,width,和length 不是类变量它们是常量,编译器知道它们的值,在装载的时候并没有作为类变量
  保存在方法区里面。
 
 (2)、主动使用和被动使用
  java虚拟机在首次使用类型是初始化它们。上面已经提到有六种活动是主动使用。
  对于子类、子接口、实现了接口的类来说引用了父类,父接口,接口中的字段是被动使用。不会被初始化也不会被加载。
  只有当字段的确被类或者接口声明时候才主动使用。
  class NewParent {
   static  int houreOfSleep = (int) (Math.random()+3.0);

   static {
    System.out.println("NewParent was init");
   }
  }

  class NewBaby extends NewParent {
   static int houreOfCry = 6+(int) (Math.random()+2.0);
   
   static {
    System.out.println("NewBaby was init");
   }
  }

  public class Exam1 {
   
   public static void main(String[] args) {
    int hours = NewBaby.houreOfSleep;
    System.out.println(hours);
   }
   
   static {
    System.out.println(" Exam1 was init");
   }
  }
  //output
  Exam1 was init
  NewParent was init
  3
  输出结果可以看出,NewBaby没有初始化。
  如果一个字段及时static 又是final 类型的,那么使用了一个编译时常量表达式初始化,使用这个字段不会被使用这个字段
  的类主动使用。
  interface Angry {
   String greeting = "gree";
   int angerLevel = Dog.getAngerLevel();
  }

  class Dog {
   static final String greeting = "Woof,world";

   static {
    System.out.println("Dog was init");
   }

   static int getAngerLevel() {
    System.out.println("Angry was int");
    return 1;
   }
  }

  public class Exam {
   public static void main(String[] args) {
    System.out.println(Angry.greeting);
    System.out.println(Dog.greeting);
   }

   static {
    System.out.println("Exam was init");
   }
  }
  
  //output
  Exam was init
  gree
  Woof,world
  如果Dog被初始化了,那么Dog was init应该被标准输出,但是没有说明没有被初始化。
  如果修改static final String greeting = "Woof,world";这条语句把final去掉,greeting字段又编译时常量字符串改
  成类变量,则Dog类被加载并且实例化了。
  static  String greeting = "Woof,world";
  //output
  Exam was init
  gree
  Dog was init
  Woof,world
  
 (3)、对象生命周期
  一旦类被装载,连接,初始化。它就可以随时被使用。程序可以访问它的静态字段,调用它的静态方法,或者创建它的实例
  1) 类实例化。
   类实例化的途径有四种:
   a) 明确使用new操作符。
   b) 调用Class类java.lang.reflect.Constructor对象的newInstance()方法。
   c) 调用现有对象的clone()方法。
   d) 通过java.io.ObjectInputStream类的getObject()方法反序列化。
   public class Exam2 implements Cloneable {

    Exam2() {
     System.out.println("created by invoking newInstance.");
    }

    Exam2(String msg) {
     System.out.println(msg);
    }

    public static void main(String[] args) throws ClassNotFoundException,
      InstantiationException, IllegalAccessException, CloneNotSupportedException {
     //方式1
     Exam2 obj1 = new Exam2("Created with new.");
     //方式2
     Class myClass = Class.forName("Exam2");

     Exam2 obj2 = (Exam2) myClass.newInstance();
     //方式3
     Exam2 obj3 = (Exam2)obj2.clone();
    }

   }

  一旦虚拟机完成了为新对象分配内存和为实例变量赋默认初始值后,它随后会为实例变量赋正确的初始值。根据创建对象方式
  不一样,java虚拟机使用三种技术之一来完成这个工作。
   a)、如果对象是通过调用clone()方法创建的,那么虚拟机把原来的被克隆的实例变量中的值拷贝到新对象中。
   b)、如果是ObjectInputStream 的readObject()方法调用反序列化的,那么通过从输入流中读入值来初始化那些非暂时性的实例变量。
   c)、通过调用对象的初始化方法来初始化。
   java编译器为它编译的每个类至少生成一个实例初始化方法。在java的class文件中这个方法被称为<init>。针对源代码的每个
   类的构造方法,java编译器都产生一个<init>()方法。
   
  2) 垃圾回收和对象终结。
   一旦一个对象不在被程序所用,虚拟机必须回收那部分内存。如果一个类声明了一个名为finalize()方法,垃圾回收器
   会在释放这个实例所占据的内存空间之前执行这个方法一次。
   protected void finalize(){
    ...
   }
   因为一个终结方法是一个普通的java方法,它可以直接被程序调用。这样的调用不会影响垃圾回收器自动调用过程。垃圾
   回收器最多调用一个对象的终结方法一次。当对象又被引用了,然后又不被引用,垃圾回收器不会第二次调用终结方法。
   垃圾回收器自动调用的finalize()方法抛出的任何异常都被忽略。垃圾回收器可以用任意顺序调用finalize()方法。
 
 3) 卸载类型。
   和对象不被使用时被垃圾回收器回收一样,类型在不引用这些类的时候可以卸载它们。如果程序不再引用某个类型,那么
   这个类型就无法再对未来的计算过程产生影响,类型变的不可触及的。可以被卸载掉。
   启动类装载器装载的类型永远的可触及的。所以永远不会被卸载,只有用户自定义的类装载器装载的类型才会变成不可触及。
   如果某个类型的Class实例被发现无法通过正常的垃圾收集堆触及,那么这个类型就是不可触及的。
   判断某个类型的Class实例在正常的垃圾收集过程中是否可以触及有两种方式。第一,如果程序保持对Class实例的明确引用,
   那么它是可触及的。第二,如果在堆中还存在一个可触及的对象,在方法区中它的类型数据指向一个Class实例。这个也是可
   触及的。

 

 八、连接模型
 (1) 动态链接和解析
  独立的class文件。看上去毫无相关,实际上它们之间通过接口符号相互联系,或者和java API的class文件相关联。当程序
  运行的时候,java虚拟机装载程序的类和接口,并且在动态链接的过程中把它们相互勾连在一起。
  常量池解析:class文件把所有的引用都保存在常量池里面,当一个类被首次装载的时候,所有类型符号的引用都被装载
  到类型的运行时常量池里面。如果一个特定的符号引用要被使用,它首先被解析。解析过程就是根据符号查找实体,再把
  呼号引用替换成直接引用的过程。


九、垃圾回收 
 java虚拟机中存放着正在运行的java程序创建的所有对象。垃圾收集就是自动释放不再被程序所需要的对象过程。
 垃圾收集器除了释放不再被引用的对象,还要处理堆碎片。堆碎片是在正常的程序运行过程产生的。
 (1) 垃圾收集算法
  垃圾收集算法必须要做两件事情.首先必须检测垃圾对象。其次必须回收垃圾对象所使用的空间并还给程序。
  a、引用计数收集器
   堆中的对象都有一个引用计数器。当一个对象被创建,并且指向该对象的引用被分配一个变量,这个对象的引用计数器被置1
   当任何其他变量被赋值为这个对象的引用时,计数器加1.当一个对象超过生命周期或者被设置为一个新值的时候,对象计数器
   减一。任何计数器为0的对象可以被当作垃圾收集。
  b、拷贝收集器
   拷贝收集器把所有活动的对象移动到一个新的区域,在拷贝的过程中他们被紧挨着布置。堆被分配两个区域,任何一个时候
   有一个区域是使用的。对象在同一个区域中分配,直到这个区域被耗尽。此时程序终止,堆被遍历,遍历遇到活动的对象拷贝到
   另外一个区域,当拷贝过程结束,程序恢复,内存将从新的堆区域分配,直到也被耗尽。程序再次终止再次遍历,活动对象又被
   拷贝到原来的区域,对于指定大小的堆来说需要两倍大小的内存。
 (2) 对象的finalize()方法:每个对象都拥有一个finalize()方法,这个方法是垃圾收集器在释放对象之前被执行。次方法主要是为了释放此对象
  占用的资源。

 

十、方法的调用和返回

 Java程序提供两种基本的方法:实例方法和类(静态)方法。实例方法被调用需要一个实例,而类方法不需要。实例方法是用动态(迟)绑定。
 而类方法使用静态(早)绑定。
 一旦一个方法被解析,Java虚拟机就准备调用它。如果这个方法是一个实例方法,它必须在一个对象中被调用。对每一次实例方法的调用,
 虚拟机需要在栈里存一个对象的引用(objectref)。如果该方法还有参数还需要存这些参数(args)。如果这个方法是一个类方法,就不需要
 存objectref

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics