JVM

Java虚拟机的主要任务是装载class文件并且执行其中的字节码

通常不同的类装载器载入的类提供不同的命名空间

同一个命名空间内的类可以直接进行交互,不同的命名空间内的类并不能察觉到彼此的从在

当一个类装载器要装载类的时候,他会先默认地将这个任务委派给它的双亲类装载器,直到到达启动类装载器,如果一个类的双亲
有能力来装载这个类型,则这个类装载器返回这个类型,否则,这个类装载器试图自己来装载这个类型。

一个运行时的Java虚拟机实例的天职就是:负责运行一个Java程序,当启动一个Java程序时,一个虚拟机实例就诞生了,当程序
关闭退出,这个虚拟机实例就消亡。每个Java程序都运行于它自己的Java虚拟机实例中。

Java程序初始类中的main()方法将作为程序初始线程的起点,任何其他的线程都是由这个初始线程启动的

在Java虚拟机内部有两种线程:守护线程与非守护线程,守护线程通常由虚拟机自己使用,比如执行垃圾回收任务的线程,但是Java可以把任何线程标记为守护线程,而Java初始线程就是非守护线程只有还有任何非守护线程在运行,那么这个Java程序也继续运行,当程序中所有的非守护线程都终止时,虚拟机将自动退出

每个Java虚拟机都有一个类装载器子系统,它根据给定的全限定名来载入类型,同样每个Java虚拟机都有一个执行引擎,它负责
执行那些包含在被载入类的方法中的指令

当Java运行程序时,需要存储东西,把东西组织到运行时数据区,每个Java实例都有一个方法区以及一个堆,他们由虚拟机实例
中所有线程共享的,当虚拟机装载一个class文件时,它会从这个class文件中解析类型信息,然后把类型信息放到方法区中,当程序
运行时,虚拟机会吧所有运行时创建的对象都放到堆中

当一个线程创建时,他都将得到它自己的PC寄存器以及一个Java栈,如果线程正在执行的是一个Java方法,那么寄存器的值将总是
指示下一条将被执行的指令,而他的Java栈则总是存储该线程中Java方法调用的指令的状态——包括他的局部变量,被调用时传进来
的参数,它的返回值,以及运算的中间结果。

Java栈由许多栈帧或者帧组成的,一个栈帧包含一个Java方法调运状态,当线程调用一个Java方法时,虚拟机压人一个新的栈帧到该
线程的Java栈中,当方法返回时,这个栈帧被Java栈中弹出并抛弃

Java没有寄存器,使用栈来保存中间数据

Java为每个线程创建的内存区,这些内存是私有的,在任何线程都不能访问另一个线程的PC寄存器或Java栈

Java虚拟机数据类型包括两种:基本类型和引用类型

在内部用int或byte来标识boolean

引用类型包括三种:类类型,接口类型以及数组类型

JVM中,最基本的数据单元是字,一个字至少得容纳byte,short,int,char,float,returnAdress或者reference的值,两个字必须能容long或double的值

类装载子系统
负责查找并装载类型的那部分被称为类装载子系统

Java虚拟机有两类类装载器:启动类装载器和用户自定义类装载器,不同的类装载器装载的类在不同的命名空间中

对于每一个被装载的类型,JVM都会为它创建一个Class类的实例来代表该类型

类装载器负责装载.连接以及初始化,类装载器子系统除了定位和导入二进制class文件外,还负责导入类的真确性,为类变量分配并初始化内存,以及帮助解析符号引用

装载:查找并装载类型的二进制数据

连接:验证 确保被导入类型的正确性,准备:为类变量分配内存,并将其初始化为默认值;解析:把类型中的符号引用抓换为默认值

初始化:把类变量初始化为正确初始值

方法区:被装载的信息存储在一个逻辑被称为方法区的内存中,当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件,紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区,该类型的类变量同样也是在方法区。

所有的线程都共享方法区

对于每个装载的类型,虚拟机都会在方法区中存储:
这个类的全限定名
这个类型的直接超类的全限定名
这个类是接口类型还是类类型
这个类的访问修饰符
任何直接超接口的全限定名的有序列表

类型的常量池:虚拟机为每个被装载的类型维持一个常量池,包括直接常量(stirng,integer和floating point常量)和对其他类型 字段和方法的符号引用,池中的常量就像数组一样通过索引来访问

字段信息,方法信息,

类变量:类变量由所有的类实例共享的,及时没有实例,它也可以被访问

编译时常量:(用final声明以及用编译时已知的值初始化的类变量),作为常量池或字节流的一部分,编译时常量保存在方法区

指向ClassLoader类的引用

指向Class的引用

只有在需要的时候才装载相应的类

堆:Java程序运行时创建的所有实例或数组放在同一个堆中,而一个java虚拟机实例只有一个堆,所有的线程都共享这个堆,每个java程序的堆互不干扰

垃圾收集:主要工作就是自动回收那些不再被运行的程序引用的对象所占有的内存,或者移动那些正在使用的对象,以此减少堆碎片化


程序计数器:
每一个运行中的Java程序,其中的每一个线程都有它自己的PC寄存器,它是在该线程启动时创建的,PC寄存器的内容总是下一条将要被执行指令的地址

Java栈:
当启动一个新线程时,JVM都会为它分配一个Java栈,虚拟机只对栈执行两种操作:以帧为单位的压栈或出栈

每当线程调用一个Java方法时,JVM都会在该线程的Java栈栈中压入一个新帧,在执行这个方法时,它使用这个帧来存储参数 局部变量 中间运算结果等等

Java方法有两种返回,一种是return返回,称为正常返回,一种是抛出异常而终止,不管以哪种返回,虚拟就都讲当前帧弹出Java栈然后释放掉

Java栈上的所有数据都是此线程私有的,任何线程不能访问另一个线程的栈数据

栈帧由三部分组成:局部变量区,操作数栈和帧数据区
当虚拟机调用一个Java方法时,它从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后亚茹栈中。

局部变量区:
Java栈帧的局部变量区被组织为一个字长为单位,从0开始计数的数组,字节码指令通过从0开始的索引来使用其中的数据。局部变量区包含对应方法的参数和局部变量,编译器首先通过申明顺序把这些参数加入局部变量数组。

操作数栈:操作数栈使用压栈和出栈来使用的

虚拟机把操作数栈作为它的工作区:大多数指令都从这里弹出数据,执行运算,然后把结果压回操作数栈 

帧数据区 Java栈帧需要一些数据来支持常量池解析,正常方法返回以及异常派发机制,这些数据都保存在Java栈帧的帧数据区中。

执行引擎:
作为运行实例的执行引擎就是一个线程,运行中Java程序的每一个线程都是一个独立的虚拟机执行引擎实例,所有属于用户运行的线程,都是实际工作的执行引擎。

指令集:方法的字节码流是由Java虚拟机的指令序列构成的,每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数

当虚拟机执行一条指令的时候,可能使用当前常量池的项,当前帧的局部变量中的值,或者位于当前操作数栈顶端的值

执行引擎取得操作码,如果操作码由操作数,取得它的操作数,它执行操作码和跟随的操作数规定的动作,然后再取得下一个操作码,这个执行字节码的过程在线程完成前一直持续,通过它的初始方法返回,或者没有抛出异常标志着线程的完成。

线程:每个JVM实例都有一个主存,用于保存所有的程序变量,每一个线程都有一个工作内存,线程用它保存所使用和赋值的变量的工作拷贝。

把变量的值从主存拷贝到它的工作内存
把值从它的工作内存拷贝到主存


23页
1.java 网络移动性 通过序列化和反序列化 实现

2.java 体系结构: Java程序设计语言;Java class文件格式;Java应用程序接口(api);Java虚拟机

3.编写Java程序时, 用Java编程语言编写源代码,把他编译成Java class文件, 然后再虚拟机上运行class文件,编写程序通过调用类中的方法来访问系统资源,当程序运行时候,通过调用class文件中实现了Java API的方法来满足程序的Java Api调用

4.Java虚拟机和api一起组成了Java平台 java平台可以用软件实现,所以java程序可以在任何地方运行

5.Java虚拟机规范定义了虚拟机必须的特性,他可以用纯软件也可以用软件和硬件一起实现

24页
1.Java虚拟机包含一个类装载器(class loader),他可以从程序和API中装载class文件,Java API中只有程序执行时需要的那些类才会被装载。 字节码被执行引擎来执行

2.执行引擎包括:一次性解释字节码; 即时编译器:第一次被执行的字节码会被编译成本地机器代码缓存,当方法以后调用的时候可以重用; 自适应优化器,编译器把活动最频繁的代码编译成本地代码,不频繁的继续被虚拟机解释; 硬件芯片构成

25页
1.Java虚拟机由主机操作系统软件实现时候,java程序通过调用本地方法和主机交互;

2.java有2种方法:Java方法和本地方法。

3.java方法是有java语言编写,编译成字节码,存储在class文件中

4.本地方法是由其他语言(c,c++,或则汇编)编写的,编译成和处理器相关的机器代码,本地方法保存在动态链接库中,格式各个平台不同,运行中的java程序调用本地方法时,虚拟机装载包含这个本地放的的动态库,并调用这个方法,本地方法是java程序和底层主机操作系统的连接方法;

5.通过本地方法,java程序可以直接访问底层操作系统的资源,但程序与平台相关了, Jni(java Native Interface)使得本地方法可以在特定主机系统的任何一个java平台上运行

6.如果希望程序平台无关性,那么只能通过java API来访问底层系统资源

26页

1.一个java应用程序可以使用两种类装载器, 启动类装载器和用户定义的类装载器 ,启动类装载器是java虚拟机实现的一部分

2.Java应用程序能够在运行时安装用户定义的类装载器,它们实际上是运行中的java应用程序可执行代码的一部分

3.因为自定义类加载器,所以在运行的时候扩展程序,自定义的类加载器可以在不同的地方加在类

4.当被加在的类引用了另外一个类时,虚拟机就会使用加载第一个类的类加载器装载被引用的类

27页
1.默认情况下,被装载的类只能看到被同一个类装载器装载的别的类,运行时的java程序中的每一个类装载器都有它的自己的命名空间,不同的命名空间的类默认不能相互访问,除非显示调用,这样保证安全

28页

1.java class问价是可以运行在任何支持java虚拟机的硬件平台和操作系统的二进制文件,

2.编译传统的c++程序的时候,所获得的二进制包含目标处理器的机器语言,java编译器把java源文件的指令翻译成字节码,这种字节码就是java虚拟机的“机器语言”

3.java class文件中字节顺序是高位在前,与平台无关

29页
1.java api 是运行库的集合,提供一套访问主机系统资源的标准方法

2.java虚拟机和java api class问价是任何java平台都要实现的必要部分

3.所有被装载的class文件(包括从应用程序中和从java api提供的)和所有已经装载的动态库(包含本地方法)共同组成了在java虚拟机的上运行的真个程序

4.java api 的class文件天生与主机平台密切相关, 它调用本地方法, 对于程序而言无论平台如何,java api都会有童言的表现和可预测的行为,因此,java程序自身就能够具有平台无关性

5.api 的访问控制器是一个类,该类用来执行栈检验,以决定是否准许某种操作,保证安全

31页

1.java new操作符 它为新对象在堆中分配内存

32页

1.把java程序编译成单独的可执行程序的方法叫做预编译

33页

java从一个类到另一个类的引用时符号化的,在静态连接的可执行程序中,类之间的引用只是直接的指针或者偏移量,在java class文件中,指向另一个类的引用通过字符串清楚表明了所指向的这个类的名字 相对容易逆向

37页
1.java平台扮演一个运行时java程序与其下的硬件和操作系统之间的缓冲角色,java程序被编译为可运行于java虚拟机中的二进制程序,并且假定Java API 的class 文件在运行时都是可用的,接着虚拟机运行程序,那些API给予程序访问计算机资源的能力,无论java程序部署到何处,它只需要与java平台交互,而不需要担心底层的硬件和操作系统

2.java的基本数据类型的值域和行为都是自定的,在java虚拟机内部以及class文件中都是一样的

3.class 文件定义了一个特定的java虚拟机的二进制格式,class文件多字节值高位优先存放

39页

1.Sun得到了三个基础的API集合,表现了java平台不同的伸缩性   企业版(j2ee) 标准版(j2se)微型版(j2me)

40页

1.编写一个平台独立的程序时,必须遵守一条原则:不要直接或间接调用不属于Java API的本地方法

41页

1.本地方法适用:为了使用底层的主机平台的某个特性,而这个特性不能通过java api访问; 为了访问一个不同java编写的老的系统或者库;为了加快程序的性能,将一段代码作为本地方法实现

2.平台独立两条原则:不要依赖及时终结(finalization)来达到程序的正确性;不要依赖线程的优先级(thread prioritzation)来达到程序的正确性

3.不同的虚拟机中,一个特定的java程序中的对象可能在不同的时间被垃圾收集

4.java虚拟机规范中只保证程序中拥有较高优先级的可运行线程将会得到一些cpu时间 ,它只保证较高优先级的线程被阻塞是,较低优先级的线程将会进行,但是较高优先级的线程没有被阻塞的情况下,并没有禁止较低优先级的线程的进行,甚至某些虚拟机在较高级别线程未被阻塞,较低优先级的线程都可得到CPU时间,为保证平台独立必须依赖同步(sychronization)而不是优先级

42页

1 平台无关性步骤
a选择程序要运行的主机和设备的集合;b在目标宿主机中选择自认为足够好的java版本;c对于给个目标宿主机,选择一些程序运行;d编写程序,使它只通过java api标准运行库来访问计算机;e编写程序,是他不依赖垃圾收集器及时终结的正确性,也不依赖线程的优先级;f创建一个用户界面,是他在目标宿主机上能工作g在所有的目标运行时环境和所有的目标宿主机上测试

43页

1.可以用100%纯java来鉴定平台无关性

47页

1.Java沙箱的基本组件:a类装载器结构;b class文件检验器;c内置java虚拟机(及语言)的安全特性;d安全管理器及Java Api

48页

1.类装载器体系结构沙箱结构作用:a 它防止恶意代码不干涉;b 它守护了被信任的类库的边界;c它将代码归入某类,该类确定了代码可以进行哪些操作

2.一旦java虚拟机将一个名为Volcano的类装入一个特定的命名空间,他就不能在装载名为Volcano的其他类到相同的命名空间,可以把多个Volcano类装入一个Java虚拟机中,通过不同的命名空间完成,即利用多个类装载器

3.在Java虚拟机中,在通一个命名空间内的类可以直接交互,而不同的命名空间中的类甚至不能察觉彼此的存在。除非显式提供了允许他们进行交互的机制

49页

1.包访问控制,可以控制类的相互访问,但他们必须由同一个类装载器装载的

2.启动类装载器,它只负责装载那些核心Java API的class文件

3.用户自定义类装载器经常依赖其他类装载器——至少依赖于虚拟机启动时创建的启动类装载器——来帮助它实现一些类装载请求

4.除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器,在某个特定的类装载器试图以常用方式装载类型以前,它会默认将这个任务“委派”给它的双亲——请求它的双亲来装载这个类型。这个双亲在依次请求它自己的双亲来装载这个类型,这个委派一直向上继续,直到达到启动类装载器,通常启动类装载器是委派链中的最后一个类装载器,如果一个类装载器的双亲类装载器有能力来装载这个类型,则这个类装载器返回这个类型,否则,这个类装载器试图自己来装载这个类型

50页

1.装载本地可用的class文件的工作被分配到多个类装载器中启动类装载器只负责装载那些核心的Java API的class文件

2.由用户自定义类装载器来负责其他class文件的装载。

3.在Java虚拟机开始运行时,在应用程序启动以前,它至少创建一个用户自定义类装载器,所有这些类装载器被连接在一个双亲-孩子的关系链中,在这条链的顶端时启动类装载器,在这条链的末端是一个系统类装载器。

4.系统类装载器是由Java应用程序创建的,新的用户自定义类装载器的默认委派双亲,这个默认的委派双亲通常是一个用户自定义的类装载器(相对应启动类装载器,它是由Java虚拟机的实现提供的)它装载应用程序的初始类,它也可能是任何用户自定义类装载器,这是由实现java平台的设计者决定的。

51页

1.在右双亲委派模式的情况下,启动类装载器可以抢在标准扩展类装载器之前去装载类,而标准扩展类装载器可以抢在类路径装载器之前去装载那个类,类路径类装载器又可以抢在网络装载器之前去装载它

2.如果启动类装载器抢先装载,而网络类装载器没有机会下载,它只能使用由它的双亲返回的类,这样可以防止不可靠的代码用他们自己的版本来替代可信任的类

52页

1.运行时包,它指由同一个类装载器装载的,属于同一个包的、多个类型的集合。同一个运行时包中的类才可以相互访问

2.如果你不想让自己的类装载器装载来自一个包中的类,那么在类装载器请求装载该包中的类的时候,应该抛出异常,二步是把类的名字传给双亲类装载器

3.class文件检验器在class文件中发现了问题,它将抛出异常

53页

1.java虚拟机的class文件检验器在字节码执行之前,必须完成大部分检查工作,它只在执行前而不是在执行中多字节码进行一次分析,每一次遇到跳转指令时都进行检验

2.class文件检验器第一个趟:class文件的结构,对每一段将被当做类型导入的字节序列,class文件检验器都会确认它是否符合Java class文件的基本结构,  是否是魔数0xCAFEBABE开头 确认class文件中声明的主版本号和次版本号是否在Java虚拟机实现支持的范围之内, 检验确认class文件有没有被删节,尾部有没有附带其他的字节

54页

1.class文件中的包含的每一个组成部分都声明了它的长度和类型,检验器可以使用组成部分的类型和长度来确定整个class文件的正确的总长度

2.类型数据的语义检查 class文件检验器在运行时检查一些Java语言应该在编译时遵守的强制规则, 比如除object类以为所有类必须有一个超类,final类没有被子类化,final方法没有被覆盖,常量池中的条目是合法的,常量池的所有索引指向正确类型的常量池条目

3.字节码验证  Java虚拟机对字节流进行数据流分析,这些字节流代表的是类的方法

4.字节码流代表了Java的方法,它是由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着一个或者多个操作数。操作数是用于在Java虚拟机执行操作码指令时提供所需的额外的数据,

5.执行字节码时,依次执行每个操作码,这就在Java虚拟机内构成了执行的线程,每一个线程被授予自己的Java栈,这个栈是由不同的栈帧构成的,每一个方法调用将获得一个自己的栈帧。

6.栈帧其实就是一个内存片段,其中存储着局部变量和计算的中间结果

7.在栈帧中,用于存储方法的中间结果的部分被称为该方法的操作数栈

8.在执行一个操作码时,除了可以使用紧随其后的操作数,虚拟机还可以使用操作数栈中的数据,或局部变量中的数据,或是两者都用

9.字节码检验器保证通过字节码检验器的字节码流可以被Java虚拟机安全地执行,但并不能保证所有的可以被虚拟机安全执行的class文件通过检验器的检验

55页

1.在1.2.3趟扫描中,class文件检验器可以保证导入的class文件构成合理,内在一致,符合Java变成语言的限制条件,并且包含的字节码可以被Java虚拟机安全地执行,如果class文件检验器发现其中任何一点不正确,它将会抛出一个错误,这个class文件将不会被程序使用

2.符号引用的验证,在动态连接的过程中,如果包含在一个class文件中的符号引用被解析时,class文件检验器将进行第四趟检查

3.当扫描检查检测的class文件以为的其他类,需要装载新的类,大多数Java虚拟机采用延迟装载类的策略,直到类真正地被程序使用时才装载新的类,

56页

1.动态连接是一个将符号引用解析为直接引用的过程。当Java虚拟机执行字节码时,遇到一个操作码,这个操作码第一次使用一个指向另一个类的符号引用,那么虚拟机就必须解析这个符号应用

2.在解析时,虚拟机执行两个基本任务:1查找被引用的类,2将符号引用替换为直接引用   虚拟机必须记住这个引用,当下次遇到同样的引用时,就直接引用,不花时间再次解析这个符号引用

3.当Java虚拟机接卸一个符号引用时,class文件检验器的第四趟扫描确保了这个引用时合法的。当这个引用是个非法引用时,例如,这个类不能被装载,或这个类存在,但是不包含被引用的字段或方法,——class文件检验器抛出一个错误
NoSucnMethodError

4.如果修改了一个类,Java编译器常会重新编译这些类,从而在编译时检测是否有任何的的不兼容性,也有很多时候,编译器不对受影响的类进行重编译,它在运行时检查兼容性。

57页

1.Java虚拟机装载了一个类,除了做以上4中检查以为,在执行字节码时还进行其他一些内置的安全机制的操作:
类型安全的引用转换;结构化的内存访问(无指针算法);自动垃圾收集;数组边界检查;空引用检查,通过保证一个Java程序只能使用类型安全的,结构化的方法去访问内存,Java虚拟机使得程序更为健壮

2.另一个安全特性:并未指明运行时数据空间在Java虚拟机内部是怎么分布的,运行时数据空间是指一些内存空间,Java虚拟机用这些空间来存储运行一个Java程序时所需要的数据:Java栈(每个线程一个) 一个存储字节码的方法区,以及一个垃圾收集堆(它用来存储由运行的程序创建的对象)

58页

1.在Java虚拟机规范中并未说明虚拟机怎么布局它的内存数据,由设计者决定

2.调用本地方法就可以绕开Java的安全策略,因此安全管理器重包含了一个方法,该方法用来确定一个程序是否能装载动态链接库,因为在调用本地方法时动态连接库是必需的

3.Java虚拟机内置安全的最后一个机制,就是一场的结构化错误处理,抛出一个错误总是导致抛出错误的这个线程死亡,其他线程可以继续正常工作

4.抛出一个异常可能导致这个线程死亡,但是他经常作为一种手段,使程序能够将控制从异常的地方转到处理这个异常的情况

59页

1.安全模型第四个组成部分-安全管理器,它主要用于保护虚拟机的外部资源部被虚拟机内进行的恶意或有漏洞的代码侵犯

2.当Java API进行任何可能不安全的操作时,它都会向安全管理器请求许可,从而强制执行自定义的安全策略,要向安全管理器请求许可,Java API将调用安全管理器对象的“check”方法

3.当Java应用程序启动时,它还没有安全管理器,如果应用程序没有安装安全管理器,那么它将不会对请求Java API的任何动作做限制

4.当一个Java API即将进行一个潜在不安全的动作时,进行两个步骤,首先,Java API的代码检查有没有安装安全管理器,如果没有安装,则跳过第二步直接继续这个潜在不安全的动作。否则,它将调用安全管理器的合适的“Check”方法,如果这个操作被禁止,那么这个“check”方法会抛出一个安全异常,该Java API方法立即中止,这个潜在不安全的操作部执行,如果这个操作被允许,那么这个“check”方法将简单返回, Java API方法将继续进行,并执行这个潜在不安全的操作

60页

1.1.2中类java.lang.SecurityManager是一个具体的类,它提高了一个默认的安全管理器的实现,用户的应用程序可以显式地实例化并安装这个安全管理器

2.类装载器将类型放到保护域中,保护域封装了授予代码来源的所有权限,这些代码来源由装载的类型代表,每一个被装载虚拟机中的类型都属于一个且只属于一个保护域,这个保护域会被记下来,并且在决定这个代码是否被充许执行一些肯能不安全的操作时使用它。

3.当具体安全管理器的check方法被调用时,它们中的大多数都将请求传递给一个称为AccessController的类,这个AccessController使用了包含在保护域对象中的信息,这个对象所属的类的方法在调用栈中,AccessController进行栈检查以确定这个操作能否被执行

61页

1.老式的check方法,同时也是Java API代码触发调用时的潜在不安全动作
checkConnect(String host,int port) 打开一个指定主机和端口号的socket连接前被调用
checkConnect(String host,int port,Object context)在被传递的安全上下文中打开一个指定主机和端口号的socket连接前调用
checkAccept(String host,int port) 接收一个来自于指定主机和端口号的socket连接前被调用
checkCreateClassLoader()创建一个新的类装载器前被调用
checkAccess(Thread t)改变一个线程前被调用
checkAccess(ThreadGroup t)改变一个线程组前被调用
checkExit()应用程序退出前被调用
checkLink()装载一个包含本地方法的动态库前被调用
checkRead(FileDescriptor fd)读取指定的文件前被调用
checkRead(String file)读取指定的文件前被调用
checkRead(String file,Object context)在被传递的安全上下文中读取指定的文件前被调用
checkWrite(FileDescriptor fd)对指定的文件进行写操作前被调用
checkWrite(String file)对指定的文件进行写操作前被调用
checkDelete(String file)对指定的文件进行写操作前被调用
checkListen(int port)在指定的本地端口号上等待连接前被调用
checkMulticast(InedAddress maddr)加入,离开,发送或者接受IP组播前被调用
checkMulticast(InedAddress maddr,byte ttl)加入,离开,发送或者接受IP组播前被调用
checkPropertiesAccess() 访问和修改一般的系统属性前被调用
checkPropertiesAccess(String key)访问或修改指定的系统属性前被调用
checkTopLevelWindow(Object Window)不出示任何警告地显示指定的窗口前被调用
checkPrintJobAccess()初始化一个打印任务请求前被调用
checkSystemClipboardAccess()访问系统剪贴板前被调用
checkAWTEventQueueAccess()访问AWT事件队列前被调用
checkPackageAccess(String pkg)访问指定的包中的类型前被调用
checkPackageDefinition(String pkg)在指定的包中加入一个新类前被调用
checkSetFactory()设置被ServerSocket或Socket使用的socket类或者设置被URL使用的URL流处理器前被调用
checkMemberAccess()通过映像API访问类信息前被调用
新的1.2方法
checkPermission(Permission perm)进行某个操作(它需要指定的权限)前被调用
checkPermission(Permission perm,Object context)在被传递的安全上下文中进行某个操作(它需要指定的权限)前被调用


62页

1.checkPermission()方法接受一个Permission对象的引用,它指出了被请求的操作,在具体安全管理器类中,checkPermission()方法只是简单地调用了类java.security.AccessController中的静态checkPermission(),并将这个Permission对象传递给它,在使用具体安全管理器时,这个AccessController类时真正负责执行安全策略的实体。

2.认证可是使用户确认,由某些团体担保的一些class文件时值得信任的,并且这些class文件在到达用户虚拟机的途中没有被改变,这样,如果用户在一定程度上信任这个为代码作担保的团体,也就可以在一定程度上简化沙箱对这段代码实施的限制,可以对由不同团体签名的代码建立不同的安全限制

63页

1.要对一段代码作担保或者签名,必须首先生成一个公钥/私钥对,用户保管私钥,公钥公开。一旦有了公钥/私钥对,就将要签名的class文件和其他文件放到一个Jar文件中,然后使用一个工具对整个Jar文件签名,整个签名工具将首先对jar文件的内容进行单向散列计算,以产生一个散列。然后这个工具将用私钥对这个散列进行签名,并且将经过签名后的散列加到JAR文件的末尾,这个签名后的散列代表了你对这个JAR文件内容的数字签名,当发布这个包含签名散列的JAR文件时,那些有你公钥的人将对JAR文件验证两件事:这个JAR文件确实是你的签名的,并且在你签名后这个JAR文件没有任何改动

2.数字签名过程的第一步是一个单向的散列计算,它输入大量的数据,但产生少量的数据,称为散列,大量的输入就是组成这个JAR文件内容的字节流,

3.用私钥进行散列加密,只加密散列,不加密JAR,因为用私钥进行加密是一个相当费时的过程

64页

任何用私钥加密的东西都可以用公钥解密

1.用不同的输入产生一个相同的散列的计算是不可行的

2.在产生散列值并用私钥对它签名以后,随后一个步骤就是将这个加密后的散列值加到同一个JAR文件中,这个JAR文件还包含你最初产生这个散列的文件

65页

1.要认证一个已签名的JAR文件,接受者必须要用公钥对签名散列进行解密,得到的结果应该和从JAR文件计算而得到的散列值相等

66页

1.为解决公钥发布困难,建立证书机构来为公钥担保,证书机构用证书机构的私钥对客户的公钥进行签名,最终得到的数字序列称为证书, 用户得到证书以后,可以用证书机构的公钥对证书进行解密以得到被加密的公钥

67页

1.可以用jarsigner进行签名

70页

1.Java安全体系结构可以对代码授予不同层次的信任度来部分地访问系统

2.每一个代码段(class)文件将和一个代码来源关联,代码来源主要说明了代码从哪里来,如果它被某个人签名担保的话,是从谁那里来,在版本1.2的安全模型中,权限是授给代码来源的,因此,如果代码段请求访问一个特定的系统资源,只有当这个访问权限是和那段代码的代码来源相关联时,Java虚拟机才会把对那个资源的访问权限授予这段代码

71页

1.在版本1.2的安全体系结构中,对应于整个Java应用程序的一个访问控制策略是由抽象类java.security.Policy的一个子类的单个实例所表示的。在任何时候,每个应用程序实际上都只有一个Policy对象,获得许可的代码可以用一个新的Policy对象替换当前的Policy对象,通过调用Policy.setPolicy()并把一个新的Policy对象的引用传递给它来实现的。类装载器利用这个Policy兑现来帮助他们决定,在把一段代码导入虚拟机应该给它们什么样的权限。

2.安全策略是一个从描述运行代码的属性集合到这段代码所拥有的权限的映射,描述运行代码的属性被总称为代码来源

3.一个代码来源是由一个java.security.CodeSource对象表示的,这个对象中包含了一个java.net.URL,它表示代码库和代表了签名者的零个或多个证书对象的数组。证书对象是抽象类java.security.cert.Certificate的子类的一个实例,一个Certificate对象抽象表示从一个人到一个公钥的绑定,以及一个为这个绑定做担保的人,CodeSource对象包含了一个Certificate对象的数组,因为同一个端代码可以被多个团体签名,这个签名通常是从JAR文件中获得的。

4.权限是用抽象类java.security.permission的一个子类的实例表示的。一个Permission对象有三个属性:类型,名字和可选的操作。权限的类型是由Permission类的名字指定的,权限的名字封装在Permission对象内的

5.在Policy对象中,每一个CodeSource是和一个或多个Permission对象关联的,和一个CodeSource相关联的Permission对象被封装在java.security.PermissionCollection的一个子类实例中。类装载器可以调用Policy.getPolicy()来获得一个当前有效的Policy对象的引用。调用Policy对象的getPermission()方法,传入一个CodeSource从而得到和那个CodeSource对应的Permission对象的PermissionCollection。类装载器然后可以使用这个从Policy对象中得到的PermissionCollection来帮助判断应该导入的代码授予什么权限。


72页

1.java.security.Policy是一个抽象类,具体Policy子类采用:在一个ASCII策略文件中用上下文无关方法描述安全策略

2.一个策略文件包含了一系列grant子句,每一个grant子句将一些权限给一个代码来源

3.如果没用路径,则默认存储在当前目录下——Java应用程序使用策略文件的启动目录

4.在策略文件中,签名用别名来代表,这些签名是保存在keystore文件中的签名者的公钥,这个keystore可以在策略文件中用一个keystore语句显式说明

5.当类装载器将类型装入Java虚拟机时,它们将为每个类型指派一个保护域,保护域定义了授予一段特定代码的所有权限,装入Java虚拟机的每一个类型都属于一个且仅属于一个保护域

6.类装载器知道它装载的所有类或接口的代码库和签名者,它利用这些信息来创建一个CodeSource对象,它将这个CodeSource对象传递给当前Policy对象的getPermissions()方法,得到这个抽象类java.security.PermissionCollecion的子类实例。这个PermissionCollection包含了到所有Permission对象的引用,利用它创建的CodeSource和它从Policy对象得到的PermissionCollection,它可以实例化一个新的ProtectDomain对象,它通过将合适的ProtectionDomain对象传递给defineClass()方法,来将这段代码放到一个保护域中,

7.DefineClass()方法是类ClassLoader的一个实例方法,用户自定义类装载器调用它来将类型导入到Java虚拟机中

74页

1.虽然这个Policy对象代表了一个从代码来源到权限的全局映射,但是最终还是由类装载器负责决定代码执行时将获得什么样的权限

2.ProtectionDomain对象封装了一个到CodeSource对象的引用以及一个到java.security.Permission对象的引用

3.当一个类装载器将class文件导入方法区时,类装载器将把一个ProtectionDomain对象的应用和这些c lass文件的字节传递给defineClass()方法,这个defineClass()方法将class 文件所在的方法区中的类型数据和被传递的ProtectDomian对象关联

75 - 93 安全待看

95页

1.软件模式: 客户端/服务端       分布式处理 它模糊了客户端和服务器的区别

97页

1.对于在网络上传送程序来说,网络移动性的一个主要难题就是时间,Java体系结构通过把传统的单一二进制可执行文件切割成小的二进制碎片——Java class文件来解决问题。class文件可以独立在网络上传播,因为java程序时动态连接,动态扩展的,最终用户不需要等待所有的程序class文件都下载完毕,就可以开始运行程序了。第一个class文件到手,程序就开始执行,class文件本身也被设计得很紧凑,他们可以在网络上飞快地传递

2.Java体系结构为网络移动性带来的直接主要好处就是把一个单一的大二进制文件分割成小的class文件,这些class文件可以按需装载

98页

1.Java应用程序从某个类的main()方法开始执行,其他的类在程序需要的时候才动态连接,如果某个类在一次操作中没有被用到,这个类就不会被装载

2.Java体系充许动态扩展,动态扩展是装载class文件的另一种方式,可以延迟到Java应用程序运行时才装载,使用用户自定义的类装载器,或者Class的forname()方法,Java程序可以在运行时装载额外的程序,这些程序就会变成运行程序的一部分。

3.为了减少在网络上传送程序的时间,class文件被设计得很紧凑,每条指令都只占据一个字节

4.除了两个例外情况,所有的操作码和它们的操作数都是按照字节对齐的,两个例外是 在操作码和它们的操作数之间会添上一到三个字节,以便操作数都按照字边界对齐

5.Java编译器不会做太多的局部优化。优化工作一般在Java虚拟机,它在装载类以后,在解释执行,即时编译或者自适应编译的时候都可以优化代码

6.为了 解决文件下载总时间按随着下载的class文件数目增多而增多,java1.1包含了对JAR(Java打包归档文件)的支持,Jar文件充许在一次网络传输过程中传送多个文件,同时Jar文件中的数据可以压缩

99页

1.Java获得市场成功最重要的一点就是,Sun的工程师把Java技术和WWW融合了起来

2.Java applet展示了Java基于网络的所有特性:平台无关性,网络移动性和安全性

100页

1.当浏览器遇到一个包含applet标记的网页时,它把信息从标记送到一个运行中的Java程序,Java程序创建一个新的用户自定义的类装载器对象,来装载这个applet的初始class文件,然后它首先嗲用applet的初始类的inti()方法,再掉用start()方法,这样就初始化了applet。applet需要的其他class文件按照按需下载的原则在动态连接的处理过程中下载

2.一旦applet的初始化完成了,applet就如同网页的一部分一样在浏览器中显示出来

3.Java结构还有另外一个承诺:对象的网络移动性,通过对象序列化和远程方法调用(RMI),Java API提供了一个在本地对象模型上扩展而成的分布式对象模型,打破了Java虚拟机之间的界限,分布式对象模型使得一个虚拟机中的对象可以引用另一个虚拟机中的对象,调用那些远程对象的方法,在虚拟机之间把对象当做参数,返回值或者方法调用抛出的意外来交换,它们有效地把面向对象编程带入了网络

101页

1.Jini提供了一个运行时基础结构,以充许服务提供者为客户机提供服务,也使得客户机可以找到并访问服务,运行时基础结构在网络的三个部分中存在:网络上的查找服务,服务提供者和客户机,查找服务时基于Jini的系统的核心组织机制,新的服务在网络上出现,它们自会在查找服务中注册,当客户机试图查找某个服务来帮助它完成某个任务的时候,它们就去找查找服务。

2.运行时基础结构采用一种网络级协议——称为“探索”,以及两种对象级协议——分别称为“加入”和“查找”,“探索”让客户机和服务可以找到查找服务,“加入”让服务在查找服务中注册自己,“查找”让客户机询问查找服务,是否存在一种服务可以帮助自己完成工作

3.搜索过程 当某个服务提供者,比如提高存储服务的,支持Jini的磁盘驱动器插入到网络的时候,探索过程自动开始,一连上网,服务提供者就开始广播一个关于自己存在的通知,具体方法是向一个公开的端口发送组播包,在存在通知中,它会通报自己的IP地址和端口号,以便查找服务能够和它取得联系。

4.查找服务在公开的端口上监听存在通知包,一旦查找服务收到了通知,它会检查通知的详情。从包中包含的信息中,查找服务就可以做出判断,是否应该和通知的发出者取得联系。如果确认,它会使用包中包含的IP地址和端口号建立和服务提供者之间的直接TCP连接。查找服务可以使用RMI发送一个叫服务注册器的对象,发出存在通知的对象通过这个服务注册对象就可以查找服务发出join(加入)和lookup(查找)操作。

102页

1.加入过程 服务注册器对象是探索的结果,服务提供者一旦获得了它之后,就可以进行加入操作了——在查找服务中注册自己。服务调用服务注册器对象的register()方法来完成注册,调用时候传递一个服务条目作为参数,register()方法把服务条目的一份拷贝传递回查找服务,查找服务负责保存所有的服务条目。

2.服务条目包含几个对象,其中包含一种服务对象,客户机可以用它来和服务交互。

3.查找过程  一旦服务通过加入过程在查找服务中注册之后,客户机通过查询查找服务就可以使用它了

4.客户机通过服务注册器上的lookup()方法进行查找,客户机给lookup()方法传递一个叫做服务模板的参数,lookup()方法把这个服务模板送到查找服务,查找服务进行查询,返回零个或多个满足条件的服务对象。客户机可以从lookup()方法返回值中取得这些匹配的服务对象的引用

103页

1.Jini体系通过把面向对象编程引入网络,带来了面向对象的一个基本优点:接口和实现分离


104页

1.良好设计的对象接口可以使软件开发者在大规模的分布式系统项目中更加有效地协同开发。对象接口为面向对象程序的各个部分之间签订了合同,同样,对象接口也可以用来明确大项目中团队成员之间的互相合作关系,他们每人负责一块程序。接口和实现分离的另外一个好处就是,程序员可以用它来减少变化带来的冲击,设计良好的对象直接结合的唯一途径就是他们的接口,实现者在对象内部做的改变不会影响到其他对象中的代码

2.对象在网络上移动,是通过Java的底层结构,对象序列化,RMI(远程方法调用)来实现的,并通过Jini服务对象展现出来

3.网络移动性:Java设计的中心

Java网络移动性都会在一个主机Java应用程序的上下文中运行,网络移动的Java代码需要的class文件会用用户自定义的类装载器装载,用户自定义的类装载器可以用自定义的方法从网络上下载class文件,并且因为网络移动的class文件并非总是直的信任,需要用用户自定义的类装载器提供的单独的命名空间来防止恶意的或者有BUG的代码干涉从其他来源装载的代码,通常会有一个安全管理器或者一个控制器来建立网络移动代码的安全策略


4.Java体系的主要焦点在网络,Java虚拟机,Java class文件,java API和Java程序设计语言一起使编写网络移动的软件成为可能且可行的,通过代码和对象在网络上移动的支持Java帮助开发者。


106页

1.Java虚拟机之所以被称为是“虚拟”的,因为它仅仅由一个规范来定义的抽象计算机

2.Java虚拟机可能三种不同的东西: 抽象规范;一个具体的实现;一个运行中的虚拟机实例

3.当运行一个Java程序的同时,也就运行了一个Java虚拟机实例

4.当启动一个Java程序时,一个虚拟机实例也就诞生了,当该程序关闭退出,这个虚拟机实例也就随之消失,如果在同一台计算机上同事运行三个Java程序,将得到三个Java虚拟机实例,每个Java程序都将运行于它自己的Java虚拟机实例中

5.Java虚拟机实例通过调用某个初始类的main()方法来运行一个Java程序,而这个main()方法必须是public static void并且接受一个字符串数组作为参数,任何有这样一个main()方法的类都可以作为Java程序运行的起点

107页

1.在命令行输入  java Echo Greetings,Planet   第一个单词java告诉操作系统运行来自Sun Java 2 SDK的Java虚拟机,第二个词“Echo”则指出初始类的名字,Echo这个初始类必须有启动main()方法 Greetings,Planet作为该程序的命令行参数以字符串数组的形式传递给main(),arg[0] 为“Greetings”arg[1]为“Planet”

2.Java程序初始类中的main()方法,将作为该程序初始线程的起点,任何其他的线程都是由这个初始线程启动的

3.Java虚拟机内部有两种线程:守护线程和非守护线程,守护线程通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程,但是,Java程序也可以把它创建的任何线程标记为守护线程。Java程序中的初始线程是非守护线程

4.只要还有任何非守护线程在运行,那么这个Java程序也在继续运行,当该程序中所有的非守护线程都终止时,虚拟机将自动退出。若安全管理器充许,程序本身也能够调用Runtime类或者System类的exit()方法退出

5.一个虚拟机实例的行为是分别按照子系统,内存区,数据类型和指令几个术语来描述的,虚拟机规范只定义了外部特征,而不强制规定Java虚拟机实现内部的体系结构

108页

1.每个Java虚拟机都有一个类装载器子系统,它根据给定的全限定名来装入类型(类或接口),每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载类的方法中的指令

2.当Java虚拟机运行一个程序时,它需要内存来存储许多东西,Java虚拟机把这些东西都组织到几个“运行时数据区”中

3.不同的虚拟机实现可能具有不同的内存限制,规范本身对“运行时数据区”只有抽象的描述

4.每个Java虚拟机实例都有一个方法区以及一个堆,它们由该虚拟机实例所有线程共享的

5.当虚拟机装载一个class文件时,它从这个class文件包含的二进制数据中解析类型信息,然后把这些类型信息放在方法区中,当程序运行时,虚拟机会把该程序运行时创建的对象都放到堆中

6.当每一个新线程被创建时,它都将得到它自己的PC寄存器以及一个Java栈:如果线程正在执行的是一个Java方法(非本地方法),那么PC寄存器的值将总是指示下一条将被执行的指令,而它的Java栈则总是存储该线程中Java方法调用的状态——包括它的局部变量,被调用时传进来的参数,它的返回值,以及运算的中间结果

7.本地方法调用的状态,则是以某种依赖于具体实现的方式存储在本地方法栈中

109页

1.Java栈是由许多栈帧(stack frame)或帧(frame)组成的,一个栈帧包含一个Java方法调用的状态,当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧从Java栈中弹出并抛弃

2.Java虚拟机没有寄存器,其指令集使用Java栈来存储中间数据

3.Java虚拟机为每一个线程创建一个内存区,这些内存区都是私有的,任何线程都不能访问另一个线程的PC寄存器和Java栈

110页

1.Java虚拟机时通过某些数据类型来执行计算的,数据类型分为:基本类型和引用类型,基本类型的变量持有原始值,而引用类型的变量持有引用值

2.除了boolean以外,Java语言中的基本类型构成了Java虚拟机的数值类型,当编译器把Java源码编译为字节码时,它会用int或byte表示boolean,虚拟机中,false是由整数零来表示的,所有非零整数都表示true,涉及boolean值的操作会使用int,boolean数组时当做byte数据来访问

3.虚拟机中还有个只有内被使用的基本类型:returnAddress,Java程序员不能使用这个类型,这个基本类型被用来实现Java程序中的finally子句

111页

1.虚拟机中引用类型有三种:类类型,接口类型,以及数组类型。它们的值都是对动态创建对象的引用,类类型的值是对类实例的引用,数组类型的值是对数对象的引用,接口类型的值,对实现了改接口的某个类实例的引用,null表示引用标量没有引用任何对象

2.虚拟机规范定义了每一种数据类型的取值范围,但是没有定义它们的位宽,存储类型的值所需的占位宽度,有具体的虚拟机实现设计

3.Java虚拟机中,最基本的数据单元是字(word),它的大小是由每个虚拟机实现去设计的,字长足够大,至少一个字单元足以持有byte,short,int,char,float,returnAddress,reference类型的值,而两个字单元就足以持有long或者double类型的值,至少32位作为字长,通常根据地层主机平台的指针长度来选择字长

4.在运行时,Java程序无法侦测地层虚拟机的字长大小,字长大小也不影响程序的行为,它仅仅是虚拟机实现的内部属性

112页

1.虚拟机中有两类类装载器:启动类装载器和用户自定义类装载器,启动类装载器是虚拟机实现的一部分,用户自定义类装载器是Java程序的一部分,由不同的类装载器装载的类将被放在虚拟机内部的不同的命名空间中

2.用户自定义的类装载器是普通的Java对象,它的类必须派生自ClassLoader类,对于每一个被类装载的类型,Java虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型,和所有的其他对象一样,用户自定义的类装载器以及Class类的实例都放在内存中的堆区,而装载的类型信息都位于方法区中。

3.类装载器工作顺序: 装载——查找并装载类型的二进制数据;连接——执行验证,准备,以及解析
验证  确保被导入类型的正确性; 准备 为类变量分配内存,并将其初始化为默认值;解析 把类型中的附后引用转换为直接引用 初始化 ——把类变量初始化为真确初始值

4.每个Java虚拟机实现都必须有一个启动类装载器,它知道怎么装载受信任的类

5.尽管“用户自定义类装载器”本身是Java程序的一部分,但类ClassLoader中的四个方法是通往Java虚拟机的通道

113页
 待 第8章

114页

1.当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件——一个线性二进制数据流——然后将它传输到虚拟机中,然后虚拟机提取其中的类型信息,并将这些信息存储到方法区,该类型的类(静态)变量同样也是存储在方法区中。

2.所有线程都共享方法区,因此它们对方法区数据的访问必须被设计为线程安全的

3.方法区的大小不必是固定的,虚拟机可以根据应用的需要调整,方法区不必是连续的,方法区可以在一个堆(甚至是虚拟机自己的堆)中自由分配,虚拟机也可以充许用户或者程序员指定方法区的初始大小以及最小和最大尺寸

4.对每个装载的类型,虚拟机都会在方法区中存储以下类型信息: 这个类型的全局定名;这个类型的直接超类的全限定名;这个类型是类类型还是接口类型;这个类型的访问修饰符;任何直接超接口的全限定名的有序列表

5.在Java class文件和虚拟机中,类型名总是以全限定名出现,在Java源代码中,全限定名由类所属包的名称加一个“.”再加上类名组成,在class文件里,所有的“.”都被“/”代替

6.除了基本类型信息外,虚拟机还为每个被装载的类型存储以下信息:
该类型的常量池;字段信息;方法信息;除了常量以外的类(静态)变量;一个到类ClassLoader的引用;一个到Class类的引用

115页

1.常量池  虚拟机必须为每个被装载的类型维护一个常量池,常量池就是该类型所有常量的一个有序集合,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。池中的数据项就像数组一样是通过索引访问的。 待6章

2.字段信息 对于类型中声明的每一个字段,方法区中必须下面信息以及字段在类或者接口中的声明顺序  字段名;字段的类型;字段的修饰符(public ,private ,protected.static,final,volatile,transient的某个子集)

3.方法信息 以下信息以及在类或接口中的声明顺序   方法名;方法的返回类型(或void);方法参数的数量和类型(按声明顺序);方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的某个子集) 如果不是抽象的和本地方法 还必须保存方法的字节码;操作数栈和该方法的栈帧中的局部变量区的大小;异常表

4.类(静态)变量 类变量时所有类实例共享的,这些变量只与有关,因此它们总是作为类型信息的一部分存储在方法区,除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须在方法区总为这些类变量分配空间

5.编译时常量(就是那些用final声明以及用编译时已知的值初始化的类常量),每个使用编译时常量的类型都会复制它的所有常量到自己的常量池中火嵌入到它的字节码流中,作为常量池或字节码流的一部分,编译时常量保存在方法区中,但一般的类常量作为声明它们的类型的一部分数据保存,编译时常量作为使用它们的类型的一部分而保存

116页

1.指向ClassLoader类的引用  每个类型被装载的时候,虚拟机必须跟踪它是由启动类装载器还是由用户自定义类装载器装载的,如果是用户自定义类型装载器装载的,那么虚拟机必须在类型信息中存储对该装载器的引用,作为方法表中类型数据的一部分保存的 ,虚拟机在动态连接期间使用这个信息,当某个类型引用另一个类型的时候,虚拟机请求装载发起引用类型的类装载器来装载被引用的类型,为了能够正确地执行动态连接以及维护多个命名空间,虚拟机需要在方法表中得知每个类都是由哪个类装载器装载的

2.指向Class类的引用 对于每一个被装载的类型(不管是类还是借口),虚拟机都会相应地为它创建一个java.lang.Class类的实例,而且虚拟机必须以某种方式把这个实例和存储在方法区中的类型关联起来

3.在Java程序里,可以得到并使用指向Class对象的引用, Class类的静态方法可以得到任何一个已装载的类的Class实例的引用,只要这个类型可以被(或者已经被)装载到当前命名空间中,如果虚拟机无法把请求的类型装载到当前命名空间,抛出ClassNotFoundExcepion异常

4.可以调用任何对象引用的getClass()方法得到Class对象引用

5.给出指向Class对象的引用,就可以通过Class类中定义的方法来找出这个类型的相关信息,Class类使得运行程序可以访问方法区中保存的信息

6. getName()返回类型的全限定名;getSuperClass()返回类型的直接超类的Class实例,如果object或者超类为接口则返回null;isInterface()判断类型是否是接口;getInterfaces()返回一个Class对象数组,其中每个Class对象对应一个直接超接口,超接口在数组中以类型申明超接口的顺序出现,如果该类型没有直接超接口,getInterfaces()返回一个长度为零的数组;getClassLoader()返回装载该类型的ClassLoader对象的引用,如果类型是启动类装载器装载,返回null,所有这些信息都直接来自方法区

117页

1.方法表 为了跳高访问效率 虚拟机实现还包括其他数据类型以加快访问原始数据的速度,虚拟机为每个装载的非抽象类,都生成一个方法表,把它作为类信息的一部分保存在方法区,方法表是一个数组,它的元素是所有它的实例可能被调用的实例方法的直接引用,包括那些从超类继承过来的实例方法

2.Java虚拟机总能够通过存储于方法区的类型信息来确定一个对象需要多少内存,某个特定对象事实上需要多少内存,是跟特定实现相关的

3.Java程序在运行时创建的所有类实例或数组都放在同一个堆中,而一个Java虚拟机实例只存在一个堆空间,所有的线程都共享这个堆,多线程访问对象(堆数据)要考虑同步问题一个Java程序独占一个Java虚拟机实例,每个Java程序都有它自己的堆空间——它们不会彼此干扰。

4.Java虚拟机有一条在堆中分配新对象的指令,却没有释放内存的指令,虚拟机自己负责决定如何以及何时释放不再被运行程序引用的对象所占据的内存,程序本事不要考虑,通常,虚拟机把这个任务交给垃圾收集器

119页

1.java对象中包含的基本数据是由它所属的类及其所有超类申明的实例变量组成,只要有一个对象引用,虚拟机就必须能够快速地定位对象实例的数据,它也必须能通过该对象引用访问相应的类数据,所以在对象中通常会有一个指向方法去的指针

2.当程序运行时需要装换某个对象引用为另一种类型时,虚拟机必须要检查这种转换是否被充许,被装换的对象是否的确是被引用的对象或者它的超类型,当程序在执行instanceof操作时,虚拟机也进行了同样的检查,在这两种情况下,虚拟机都需要查看引用的对象的类数据,当程序中调用某个实例方法时,虚拟机必须运行动态绑定,它不能按照引用的类型来决定将要调用的方法,而必须根据对象的实际类,所以虚拟机必须能够通过对象引用得到类(类型)数据

121页

1.堆上的对象数据还有一个逻辑部分,对象锁,虚拟机中的每个对象都有一个对象锁,在任何时刻,只能有一个线程“拥有”对象锁,只有一个线程才能访问该对象的数据,此时其他希望访问这个对象的线程只能等待,直到拥有对象锁的线程释放锁,当某个线程拥有一个对象锁后,可以继续对这个锁追加请求,但请求几次必须释放几次,之后才能轮到其他线程

2.很多对象在整个生命周期内都没有任何线程加锁,在线程实际请求某个对象的锁之前,实现对象锁锁需要的数据是不必要的,只有第一次需要加锁的时候才分配对应的锁数据

3.每个Java对象逻辑上还与实现等待集合的数据相关联,每个类都从object那里继承了三个等待方法(wait)和两个通知方法(notify以及notifyAll),当某个线程在一个对象上调用等待方法时,虚拟机就阻塞该线程,并把它放在了这个对象的等待集合中,直到另一个线程在同一个对象上调用通知方法,虚拟机才会在之后的某个时刻唤醒一个或多个在等待集合中被阻塞的线程,等待集合的数据并不是必须的

122页

1.垃圾收集器必须跟踪程序引用的每个对象,附加给一些数据给这些对象

2.除了标记对象的引用情况外,垃圾收集器还要区分对象是否调用了终结方法,对于在其类中声明了终结方法的对象,在回收它之前,垃圾收集器必须调用它的终结方法,垃圾收集器对每个对象只能调用一次终结方法

3.在Java中,数组和其他对象一样,数组总是存储在堆中

4.和其他所有对象一样,数组页拥有一个与他们的类相关联的Class实例,所有具有相同维度和类型的数组都是同一个类的实例,而不管数组的长度是多少

5.数组类的名称由两部分组成:每一维用一个方括号“[”表示,用字符或字符串表示元素类型

6.多维数组被表示为数组的数组,在堆中的每一个数组对象还必须保存数组的长度,数组数据,以及某些指向数组的类元素的引用,虚拟机必须能够通过一个数组对象的引用得到数组的长度,通过索引访问其原色,调用所有数组的直接超类Object申明的方法

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

8.每当启动一个线程时,Java虚拟机都会为它分配一个Java栈,Java栈以帧为单位保存线程的运行状态,虚拟机只会对栈执行:以帧为单位压栈或出栈

124页

1.某个线程正在执行的方法被称为该线程的当前方法,当前方法适用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池,在线程执行一个方法时,它会跟踪当前类和当前常量池,当虚拟机遇到栈内操作指令时,它对当前帧内数据执行操作

2.每当调用一个Java方法时,虚拟机会在该线程的Java栈中压入了一个新栈,执行方法时候,适用这个帧来存储参数,局部变量,中间运算结果

3.java方法可以两种方式完成,一种用return返回,一种抛出异常中止,不管哪种方式返回,虚拟机都将当前帧弹出Java栈然后释放掉

4.Java栈中的所有数据都是此线程私有的

5.栈帧由三部分组成:局部变量区,操作数栈和帧数据区,当虚拟机调用一个Java方法时,它从对应类的类型信息中得到此方法的局部变量去和操作数栈的大小,并据此分配栈帧内存,然后压入Java栈中

6.Java栈帧的局部变量区被组织为一个以字节为单位,从0开始计数的数组,类型为int,float,reference和returnAddress的值在数组中占据一项,而类型为byte,short和char的值在存入数组前都将被转换为int值,类型为long和double的值在数组中占据连续的两项。

7.局部变量区包含对应方法的参数和局部变量,编译器首先按声明的顺序吧这些参数放入局部变量数组

125页

1.局部变量第一个参数this对于任何一个实例方法都是隐含加入的,它用来表示调用该方法的对象本身,类方法只与类相关,而与具体的对象无关,不能直接通过类方法访问类实例的变量,所以在类方法调用的时候没有关联到一个具体实例

2.byte,short,char,boolean在局部变量区和操作数栈中都会被转换成int类型的值,它们在栈帧中的时候是当做int来进行处理饿,只有当它们被存回堆或方法区时,才会转换成原来的类型。

3.在Java中,所有的对象都按引用传递,并且都存储在堆中,永远都不会在局部变量区或操作数栈中发现对象的拷贝,只会有对象引用

4.操作数栈被组织成一个以字节为单位的数组,它是通过标准的栈操作——压栈和出栈访问的

5.Java虚拟机没有寄存器,Java虚拟机的指令是从操作数栈中而不是从寄存器中取得操作数的,因此它的运行方式是基于栈的而不是基于寄存器的。虚拟机把操作数栈作为它的工作区——大多数指令都是从这里弹出数据,执行运算,然后把记过压回操作数栈

127页

1.除了局部变量区和操作数栈外,Java栈帧还需要一些数据来支持常量池解析,正常方法返回以及异常派发机制,这些信息都保存在Java栈帧的帧数据区中

2.每当虚拟机要执行某个需要用到常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它

3.除了用于常量池的解析外,帧数据区还要帮助虚拟机处理Java方法的正常结束或异常中止,如果是通过return正常结束,虚拟机必须恢复发起调用的方法的栈帧,紧跟着调用了完成方法的指令的下一条指令,加入方法有返回值,虚拟机将它压入到发起调用的方法的操作数栈

4.帧数据区保存了一个对方法异常表的引用,当某个方法抛出异常时,虚拟机根据帧数据区对应的异常表来决定如何处理。如果在异常表中找到了匹配的catch子句,就会把控制权交给catch子句内的代码,如果没有发现,方法会立即异常中止,然后虚拟机使用帧数据区的信息恢复发起调用的方法的帧,然后在发起调用的方法的上下文中重新抛出同样的异常

128页

1.和引用其他类的字段或方法一样,对通一个类的方法和字段的引用初始时也是符号,因此在使用之前也需要解析,解析后的常量池数据将指向方法区中对应方法的信息,虚拟机需要使用这些信息来决定方法的局部变量区和操作数栈的大小

2.实现方法:从堆中对不同方法独立的一个帧 ,另外一个是从一个连续的帧中分配,充许相邻方法的栈帧可以相互重叠

130页

1.本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,它甚至可以直接使用本地处理器中的寄存器,或者直接从本地内存的堆中分配任意数量的内存,它和虚拟机拥有同样的权限

2.任何本地方法接口都会使用本地方法栈,当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈,然而当调用本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法,本地方法调用Java方法,线程会保存本地方法栈的状态并进入到另一个Java栈

131页

1.任何Java虚拟机实现的核心都是它的执行引擎,在Java虚拟机规范中,执行引擎的行为使用指令集来定义

2.“执行引擎”三种解释:一个是抽象的规范,一个是具体的实现,一个是正在运行的实例,抽象规范使用指令集规定了执行引擎的行为,具体实现可能使用多种不同的技术——包括软件方面,硬件方法或书中技术的集合,运行时实例的执行引擎就是一个线程

3.运行中Java程序的每一个线程都是一个独立的虚拟机执行引擎的实例,从线程声明周期的开始到结束,它要么在执行字节码,要么执行本地方法,Java虚拟机的实现可能用一些对用户不可见的线程,比如垃圾收集器,这样的线程不需要是实现的执行引擎的实例,所有属于用户运行程序的线程,都是在实际工作的执行引擎

4.指令集  方法的字节码流是由Java虚拟机的指令序列构成的,每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数,操作码表明需要执行的操作,操作数向Java虚拟机提供执行操作码需要的额外信息,当虚拟机执行一条指令的时候,可能使用当前常量池的项,当前帧的局部变量中的值,或者位于当前帧操作数栈顶端的值

132页

1.执行引擎取得操作码,如果操作码有操作数,取得它的操作数,它执行操作码和跟随得操作数规定的动作,然后再取得下一个操作码,这个执行字节码的过程在线性横完成前一直持续,通过从它的初始方法返回,或者没有捕获抛出的异常都可以标志着线程的完成

2.如果一条指令请求一个对本地方法的调用,执行引擎就会调用这个本地方法,运行这个本地方法就是Java虚拟机对这条指令的执行,当本地方法返回了,虚拟机继续执行下一条指令,如果本地方法异常中止了,虚拟机就按照好比这条指令抛出异常一样的步骤处理异常

133页

1.Java虚拟机指令集关注的中心是操作数栈,一般把要使用的值会压入栈中,虽然Java虚拟机没有保存任意值的寄存器,但每个方法都有一个局部变量集合,指令集实际的工作方式就是把局部变量当做寄存器,用索引来访问,,要使用保存在局部变量中的值之前,必须先将它从压入操作数栈

135页

1.实现技术:解释,即时编译,自适应优化,芯片级直接执行,实现可以自由选择任何技术来执行字节码,只要遵守Java虚拟机指令集的定义

2.自适应:自适应优化的虚拟机开始的时候对所有的代码都是解释运行,它会监视代码的执行情况,当自适应优化的虚拟机判断出某个特定的方法是瓶颈的时候,它启动一个后台线程,它自己吗编译成本地代码,优化这些本地代码

137页

1.Java虚拟机规定了所有最高的线程会得到大多数的CPU时间,较低优先界别的线程只有在所有比它优先级跟高的线程全部阻塞的情况下才能保证得到CPU时间,级别低的线程在级别高的线程没有被阻塞的时候也可能得到CPU时间,但是这没有任何保证

2.Java线程的行为时通过术语——变量,主存,和工作内存——来定义的,每一个Java虚拟机实例都有个主存,用于保存所有的程序变量(对象的实例变量,数组的元素以及类变量)。每一个线程都有个工作内存,线程用它保存所使用和赋值的变量的“工作拷贝”,局部变量和参数,每个线程私有,从逻辑上看成工作内存或者主存的一部分

3.Java虚拟机规范定义了许多规则,用来管理线程和主存之间的底层交互行为,基本上底层线程行为的规则,规定了一个线程何时可以做以及何时必须做:a.把变量的值从主存拷贝到它的工作内存;b.把值从它的工作内存写到主存

4.如果访问某个没有被同步的变量,充许线程用任何顺序来更新主存,不使用同步,多线程程序在不同虚拟机上表现不同的行为,当时通过正确的使用同步,可以创建多线程的Java程序,它们按照可以预期的方式,可以在任何虚拟机上工作

138页

1.Java本地接口,JNI,是为可移植性准备的

142页

1.Java class文件时对Java程序二进制格式的精确定义,每一个Java class文件都对一个Java类或接口作出了全面描述,一个class文件中只能包含一个类或者接口

143页

1.Java class文件时8位字节的二进制流,数据项按顺序存储在class文件中,相邻的项之间没有任何间隔,占据多个字节空间的项按照高位在前的顺序分为几个连续的字节存放

2.在class文件中,可变长度项的大小和长度位于其时间数据之前,这个特性使得class文件流可以从头到尾被顺序解析,首先读出项的大小,然后读出项的数据

144页

1.magic(魔数) 每个Java class文件的前4个字节被称为它的魔数:0xCAFEBABE,魔数的作用在于,可以轻松地分辨出Java class文件和非Java class文件,如果一个文件不是以oxCAFEBABE开头,那它肯定不是Java class文件

2.minor_version和major_version
class文件的下面四个字节包含了次,主版本号,class文件格式一旦发生变化,版本号也会随之变化,对应Java虚拟机来说,版本号确定了特定的class文件格式,通常只有给定主版本号和一系列次版本号后,Java虚拟机才能读取class文件,如果class文件的版本号超过了java虚拟机所能处理的有效范围,java虚拟机将不会处理该class文件

3.constant_pool_count和constant_pool
Java虚拟机把常量池组织为入口类表的形式,在实际列表constant_pool之前,是入口在列表中的计数constant_pool_count  常量池中的许多入口都指向其他的常量池入口,而且class文件中紧随着常量池的许多条目也会指向常量池中的入口,在整个class文件中,指示常量池入口在常量池列表中位置的整数索引都指向这些常量池入口,列表中的第一项索引为1,第二项索引为2,以此类推,尽管constant_pool类表中没有索引值为0的入口,但缺失的这一入口也被constant_pool_count计数在内

4.每个常量池入口从一个长度为一个字节的标志开始,这个标志指出了列表中该位置的常量类型,一旦Java虚拟机获取并解析这个标志,Java虚拟机就会知道在标志后的常量类型是什么

类型                    标志值                         描述
CONSTANT_ Utf8             1                      UTF-8编码的Unicode字符串
          Integer          3                       int类型字面值
          Float            4                        float类型字面值
          Long             5                        long类型字面值
          Double           6                        double类型字面值
          Class            7                        对一个类或接口的符号引用
          String           8                         String类型字面值
          Fieldref         9                         对一个字段的符号引用
          Methoderef       10                        对一个类中声明的方法的符号引用
          InterfaceMethoderef 11                     对一个接口中声明的方法的符号引用
          NameAndType          12                    对一个字段或方法的部分符号引用

145页

1.access_flags
紧接常量池后的两个字节称为access_flags,它展示了文件中定义的类或接口的几段信息

标志值                   值                     设置后的含义
ACC_PUBLIC               0x0001                  public类型
ACC_FINAL                0x0010                  类为final的类型
ACC_SUPER                0x0020                  使用新型的invokespecial语义
ACC_INTERFACE            0x0200                   接口类型
ACC_ABSTACT              0x0400                   abstract类型
在access_flags中所有未使用的位都必须由编译器置为0,Java虚拟机必须忽略它


146页

1.接下来两个字节为this_class项,它是对一个常量池的索引,在this_class位置的常量池入口必须为CONSTANT_Class_info表。该表由两个部分组成——标签和name_index,标签部分是一个具有CONSTANT_Class值的常量,在name_index位置的常量池入口为一个包含了类或接口全限定名的CONSTANT_Utf8_info表,

147页

1.super_class
紧随this_class之后的是super_class,它是一个两个字节的常量池索引,在super_class位置的常量池入口时一个指向该类超类全限定名的CONSTANT_Class_info入口,除了Object类以外,常量池索引对于所有的类均有效,对于Object类,super_class值为0,对于接口,常量池入口super_class位置的项为java.lang.Object

2.interfaces_count和interfaces
interfaces_count含义为:在文件中由该类直接实现或者由接口所扩展的父接口的数量。在计数后面,名为interfaces的数组,它包含了对每个由该类或者接口直接实现的父接口的常量池索引。每个父接口都使用一个常量池中的CONSTANT_Class_info入口来描述,该CONSTANT_Class_info入口指向接口的全限定名,这个数组只容纳那些直接出现在类声明的implements子句或接口声明的extends子句中的父接口。超类按照implements子句和extends子句中出项的顺序在这个数组中显现。

3.fields_count和fields
fields_count的计数,它是类变量和实例变量的字段的数量总和。这个计数后面的是不同长度的field_info表的序列,只有在文件中由类或者接口声明了字段才能在field类表中列出。在field类表中,不列出从超类或父接口继承而来的字段。每个field_info表都展示了一个字段的信息,此表包含了字段的名字,描述符和修饰符。

4.methods_count和methods
fields后面的是对在该类或者接口中所声明的方法的描述。methods_count的计数,它是对于该类或者接口中声明的所有方法的总计数。这个总计数只包括在该类或者接口中显式定义的方法,methods_count后面的是方法本身,它在一个method_info表的列表中,methods_info包含了方法相关的一些信息。

148页

1.attribute它给出了在该文件中类或者接口所定义的属性的基本信息。attributes_count指出现在后续attributes列表中的attribute_info表的数量总和,每个attribute_info的第一项是指向常量池中CONSTANT_Utf8_info表的索引,该表给出了属性的名称。

2.常量池中容纳的符号引用包括三种特殊的字符串:全限定名,简单名称和描述符。所有的符号引用都包含类或者接口的全限定名,字段的符号引用除了全限定类型名以为,还包括简单字段名和字段描述符。方法的符号引用除了全限定类型名以外,还包括简单方法名和方法描述符。

3.全限定名,当常量池入口指向类或者接口时,它们给出类或者接口的全限定名。在class文件中,全限定名中的点用斜线取代了。

4.字段名和方法名以简单名称形式出现在常量池入口中。

5.指向字段和方法的符号引用还包含描述符字符串,字段的描述符给出了字段的类型,方法描述符给出了方法的返回值和方法参数的数量,类型以及顺序。
基本类型终结符
B   byte
C   char
D   double
F   float
I   int
J   long
S   short
Z   boolean     对象类型中的Classname部分为全限定名

L<classname>  ObjectType
[             ArrayType
V             方法返回类型为void
(ParameterDescriptor*) Return Descriptor       MethodDescriptor

150页

1.实例方法的方法描述符并没有包含作为第一个参数传给所有实例方法的隐藏this参数,但所有调用实例方法的Java虚拟机指令都会隐式传递this参数。

2.常量池是一个可变长度cp_info表的有序序列,cp_info表中的tag(标志)项是一个无符号的byte类型值,它表明了表的类型和格式,cp_info表一共有11种类型

3.CONSTANT_Utf8_info表
可变长度的CONSTANT_Utf8_info表适用一种UTF-8格式的变体来存储一个常量字符串
文字字符串;被定义的类和接口的全限定名;被定义的类的超类的全限定名;被定义的类和接口的父接口的全限定名;由类或者接口声明的任意字段的简单方法和描述符;由类或者接口声明的任意方法的简单名称和描述符;任意引用的类和接口的全限定名;任意引用的字段的简单名称和描述符;任何引用的方法的简单名称和描述符;与属性相关的字符串

4.CONSTANT_Utf8_info表中各项:
tag项的值为CONSTANT_Utf8(1),length项给出了后续bytes项的长度(字节数) bytes项中包含变体UTF-8格式存储的字符串中的字符
152页

1.CONSTANT_Integer_info
固定长度的CONSTANT_Integer_info表用来存储常量int类型值,该表只存储int值,不存储符号引用
tag的值为CONSTANT_Integer(3)   bytes项中按照高位在前的格式存储int类型值

153页

1.固定长度的CONSTANT_Class_info表使用符号引用来表述类或接口。无论指向类,接口,字段,还是方法,所有的符号引用都包含一个CONSTANT_Class_info表
tag项的值为CONSTANT_Class(7), name_index项给出了包含类或者接口全限定名的CONSTANT_Utf8_info表的索引
由于Java中的数组时完善的对象,CONSTANT_Class_info表也能够用来描述数组类,CONSTANT_Class_info表中的name_index项指向CONSTANT_Utf8_info表,该表中包含了数组描述符

154页

1.CONSTANT_String_info表
固定长度的CONSTANT_String_info表用来存储文字字符串值,该值也可表示为java.lang.String的实例,该表只存储文字字符串值,不存储符号引用
tag项的值为CONSTANT_String(8)  string_index项给出了包含文字字符串的CONSTANT_Utf8_info表的索引

2.CONSTANT_Fieldref_info表
固定长度的CONSTANT_Fieldref_info表描述了指向字段的符号引用
tag项的值为CONSTANT_Fieldref(9)
class_index给出了声明被引用字段的类或者接口的CONSTANT_Class_info入口的索引
name_and_type_index提供了CONSTANT_NameAndTyep_info入口的索引,该入口提供了字段的简单名称以及描述符

155页

1.CONSTANT_Methodref_info表
固定长度的CONSANT_Methoderef_info表适用符号引用来表述类中声明的方法(不包括接口的方法)
name_and_type_index提供了CONSTANT_NameAndType_info入口的索引,该入口提供了方法的简单名称以及描述符。如果方法的简单名称开始于“<”符号,该方法必须为一个实例初始化方法,它的简单名称必须为<init>,它的返回值必须为void类型,否则,该方法的名称必须为一个有效的Java程序设计语言的标识符。

156页

1.CONSTANT_InterfaceMethodref_info表适用符号引用来描述接口中的声明的方法

2.CONSTANT_NameAndType_info
该表提供了所引用字段或者方法的简单名称和描述符的常量池入口
name_index项给出了CONSTANT_Utf8_info入口的索引,该入口给出了字段或者方法的名称,该名称或者为一个有效的Java程序设计语言的表示符,或者为<init>

descriptor_index提供了CONSTANT_Utf8_info入口的索引,该入口提供了字段或者方法的描述符

157页

1.在类或者接口中声明的每一个字段都是由class文件中的一个名为field_info的可变长度的表进行描述。在一个class文件中,不会存在两个具有相同名字和描述的字段(class文件中的两个字段可以拥有同一个名字,只要它们的描述符不同)

2.  field_info表的格式

access_flags
name_index
descriptor_index
attributes_count
attributes

3.access_flags项的标志

标志名称                 值                    含义
ACC_PUBLIC              0x0001               public
ACC_PRIVATE             0x0002               private
ACC_PROTECTED           0x0004               
ACC_STATIC              0x0008
ACC_FINAL               0x0010
ACC_VOLATILE            0x0040
ACC_TRANSIENT           0x0080

接口中声明的字段必须有且只有ACC_PUBLIC,ACC_STATIC,ACC_FINAL三种标志
name_index项提供了 给出字段简单名称的CONSTANT_Utf8_info的索引
descriptor_index提供了给出字段描述符的CONSTANT_Utf8_info入口的索引
Java虚拟机规范定义的三种可能会出现在此的属性是:ConstanValue,Deprecated,Synthetic

158页

1.在class文件中,每个类和接口中声明的方法,后者由编译器产生的方法,都由一个可变的长度的Method_info表来描述,有可能在class文件中出现的两种编译器产生的方法是:实例初始化方法(名为<init>)和类与接口初始化方法(名为<clinit>)

159页

1. 实例初始化方法(<init>)可以只使用ACC_PUBLIC,ACC_PRIVATE和ACC_PROTECTED标志,类与接口初始化方法(<clinit>)只由Java虚拟机直接调用,永远不会被Java字节码直接调用,这样,<clinit>方法的access_flags中的标志位,除去ACC_STRICT之外的所有位都应该被忽略。在access_flags中未用到的位都被设为0,Java虚拟机实现忽略他们

2.可能在这里出现的属性:Code,Deprecated,Exception,Synthetic

3.所有Java虚拟机实现都必须能够识别下列三种属性:Code,ConstantValue和Exception

160页

1.attribute_info表格式   attribute_name_index
                           attribute_length
                           info

161页

1.在所有不是抽象或者本地方法的method_info信息中,都存在一个Code_attribute表
attribute_length项给出除去起始6个字节后,Code属性以字节为单位的长度

2.max_stack
在方法执行的任意时刻,max_stack项给出该方法的操作数栈的最大长度(以字为单位)
max_locals项给出方法的局部变量所需存储空间的长度,无论虚拟机什么时候调用被Code属性所描述的法国法,它都必须分配一个长度为max_locals的局部变量数组,这个数组用来存储传递给方法的参数以及方法所使用的局部变来那个

162页

1.code_length给出该方法字节码流的长度,字节码本身将会出现在code项中
exception_table是一个exception_info表的列表,每个exception_info表都描述了一个异常表项,excetption_table表在列表中按照方法执行抛出异常时Java虚拟机检查匹配异常处理器(catch子句)的顺序进行排列。
exception_info表 表格式

                start_pc   代码数组起始处到异常处理器起始处的偏移量
                 end_pc    代码数组的起始处到异常处理器结束后的一个字节的偏移量
                 handler_pc
                 catch_pc  给除该异常处理器所捕获的异常类型的CONSTATN_Class_info入口的常量池索引, 如果catch_type的值为0,那么异常处理器将处理所有的异常,一个值为0的catch_type用于实现finally子句

163页 -170页  空

174页

1.Java虚拟机通过装载,连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用, 装载就是把二进制形式的Java类型读入Java虚拟机中; 连接就是把这种已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中去。

2.连接分为三个步骤 验证,准备和解析 “验证”步骤确保了Java类型数据格式正确并且适于Java虚拟机使用;“准备”步骤则负责为该类型分配它所需的内存;“解析”把常量池中的符号引用转换为直接引用, 当前面三步完成时,该类型已经为初始化做好了准备,在初始化期间,都将给类变量赋以恰当的初始值。

175页

1.所有的Java虚拟机实现必须在每个类或接口首次主动使用时初始化。

当创建某个类的新实例时(字节码new,或者通过不明确的创建,反射,克隆或者反序列化)
当调用某个类的静态方法时
当调用某个类或接口的静态字段,或者对该字段赋值时,用final修饰的静态字段除外,它被初始化为一个编译时的常量表达式。
当调用Java API中的某些反射方法时,比如类Class中方法或者java.lang.reflect包中的类的方法
当初始化某个类的子类时(某个类初始化时,它的超类要求必须已经初始化)
当虚拟机启动时某个被标明为启动类(含有main()方法的那个类)

除了以上,其他的使用Java类型的方式都是被动使用,它们不会导致Java类型的初始化

2.某个类的所有祖先类必须在该类之前被初始化,但对于接口,只有某个接口所声明的非常量字段被使用时,该接口才会被初始化

175页

1.装载一个类型:
通过该类型的完全限定名,产生一个代表该类型的二进制数据流
解析这个二进制数据流为方法区内的内部数据结构
创建一个表示该类型的java.lang.Class类的实例

176页

1.产生二进制数据的方式
从本地文件系统装载一个Java class文件
通过网络下载一个Java class文件
从一个ZIP,JAR,CAB或者其他某种归档文件中提取Java class文件
从一个专有数据库中提取Javaclass文件
从一个Java源文件动态编译为class文件格式
动态为某个类型计算其class文件数据

176页

1.虚拟机必须把这些二进制数据解析为与实现相关的内部数据结构,装载的最终产品就是这个Class类的实例对象,它成为Java程序与内部数据结构之间的接口,要访问关于该类型的信息,程序就要调用该类型对应的Class实例对象的方法,这样一个过程,就是把类型的二进制数据解析为方法区中的内部数据结构,并在堆上建立一个Class对象的过程,这就成为“创建”类型

2.当类型被装载后,就准备进行连接,连接过程的第一步是验证——确认类型符号Java语言的语义,并且不会危及虚拟机的完整性

3.不管怎样,在打多数Java虚拟机实现中特定类型的检查一般都在特定的时间发生

177页

正式的验证需要完成的检查
final的类不能拥有子类
final的方法不能被覆盖
确保在类型和超类型之间没有不兼容的方法声明(比如方法有同样的名字,参数在数量,顺序,类型上都相同,但是返回类型不同)

检查所有的常量池入口相互之间一致
检查常量池中的所有特殊字符串符合格式
检查字节码的完整性

178页

1.在准备阶段,虚拟机把类变量新分配的内存根据类型设置为默认值  char 的初始值为 '\u0000'

179页

1.为了准备让一个类或者接口被首次主动使用,最后一个步骤是初始化,也就是为类变脸赋予正确的初始值(程序员希望这个类变量所具备的初始值)

2.一个正确的初始值是通过类变量初始化语句或者静态初始化语句给出的,一个类变量初始化语句是变量声明后的等号和表达式。

3.所有的类变量初始化语句和类型的静态初始化器都被Java编译器收集在一起,放到一个特殊的方法中,在类和接口的Java class文件中,这个方法被称为"<clinit>",通常的Java程序无法调用这个<clinit>方法的,这种方法只能被Java虚拟机调用,专门用类型的静态变量设置为它们的正确初始值。

4.初始化步骤:
如果类存在直接超类的话,且直接超类还没有被初始化,就先初始化直接超类;如果累存在一个类初始化方法,就执行此方法,初始化接口并不需要初始化它的父接口,因此初始化一个接口只需:如果接口存在一个接口初始化方法的话,就执行此方法。

5.<clinit>()方法的代码并不显式地调用超类的<clinit>()方法,在Java虚拟机调用类的<clinit>()方法之前,它必须确认超类的<clinit>()方法已经被执行了

180页

1.类变量初始化语句和静态初始化语句的代码顺序是按照它们在类或者接口声明中出项的顺序。

181页

1.并非所有的类都需要在它们的class文件中拥有一个<clinit>()方法,如果类没有声明任何类变量,也没有静态初始化语句,那么它就不会有<clinit>()方法,如果类声明了类变量,但是没有明确使用类变量初始化语句或者静态初始化它们,那么类不会有<clinit>()方法,如果类仅包含静态final变量的类变量初始化语句,而且这些类变量初始化语句次啊用编译时常量表达式,类页不会有<clinit>()方法,只有那些的确需要执行Java代码来赋值类变量正确初始值的类才会有类初始化方法

182页

1.所有在接口中声明的隐式公开 静态 和最终字段都必须在字段初始化语句中初始化,如果接口包含任何不能在编译时被解析称为一个常量的字段初始化语句,接口就会拥有一个<clinit>()方法

183页

1.只有当字段的确是被类或者接口声明的时候才是主动使用

184页

1.如果一个字段既是静态的又是final的,并且使用一个编译时常量表达式初始化,使用这样的字段,就不是对生命该字段的类的主动使用,Java编译器把这样的字段解析成对常量的本地拷贝(存在于各引用者的常量池或者字节码流中,或者二者都有)

185页

1.一旦一个类被装载,连接和初始化,它就随时可以使用了,程序可以访问它的静态字段,调用它的静态方法,或者创建它的实例

2.实例化一个类有四种途径:明确地使用new操作符;调用Class或者java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。

186页

1.第一个隐含实例化对象可能就是保存命令行参数的String对象,每一个命令行参数都会有一个String对象的引用,把他们组织成一个String数组并作为一个参数传递到每一个程序的main()方法中。

2.Java虚拟机装载的每一个类型,它会暗中实例化一个Class对象来代表这个类型;当Java虚拟机装载了在常量池中包含CONSTANT_String_info入口的类的时候,它会创建新的String对象的实例来表示这些常量字符串。

3.隐含对象的创建途径是通过执行包含字符串连接操作符的表达式产生对象,如果字符串不是一个编译时常量,用于中间处理的String和StringBuffer对象会在计算表达式过程中创建

188页

1 当Java虚拟机创建一个类的新实例时,不管是明确的还是隐含的,首先都需要在堆中为保存对象的实例变量分配内存,所有在对象的类和它的超类中声明的变量都要分配内存。一旦虚拟机为新的对象准备好了堆内存,它立即把实例变量初始化为默认的初始值。

189页

1 一旦虚拟机完成了为新对象分配内存和为实例变量赋默认初始值后,它随后就会为实例变量赋正确的初始值。

2 根据创建对象的方法不同,Java虚拟机使用三种技术之一完成这个工作。如果对象是通过clone()调用来创建的,虚拟机把原来被克隆的实例变量中的值拷贝到新对象中。如果对象是通过一个ObjectInputStream的readObject()调用反序列化的,虚拟机通过从输入流中读入的值来初始化那些非暂时性的实例变量。否则,虚拟机调用对象的实例初始化方法,实例初始化方法把对象的实例变量初始化为正确的初始值。

3 Java编译器为它编译的每一个类都至少生成一个实例初始化方法,在Java的class文件中,这个实例初始化方法称为“《init》”,针对源代码中每一个类的构造方法,Java编译器都产生一个《init》()方法,如果类没有明确地声明任何构造方法,编译器默认产生一个无参数的构造方法,它仅仅调用超类的无参数构造方法

4 一个《init》()方法中可能包含三种代码:调用另一个《init》()方法,实现对任何实例变量的初始化,构造方法体的代码。

5 如果构造方法通过明确地调用同一个类的另一个构造方法(一个this()调用)开始,它对应的《init》()方法由两部分组成:
一个同类的《init》方法的调用。
实现了对应构造方法的方法体的字节码。

6 如果构造方法不是通过一个this()调用开始的,而且这个对象不是Object,《init》()方法由三部分组成:
一个超类的《init》()方法的调用
任意实例变量初始化方法的字节码
实现了对应构造方法的方法体的字节码
如果这个类是Object,《init》方法就不存在

7 如果构造方法通过明确地调用超类的构造方法(一个super()调用)开始,它的《init》()方法会调用对应的超类的《init》()方法,如果构造方法没有明确地从this()或者super()调用开始,对应的《init》()方法默认会调用超类的无参数《init》()方法

195页

1 对于除Object以外的每一个类,不管是同类的还是直接超类的,《init》()方法都必须从另一个《init》()方法调用开始,《init》()方法不允许捕获由它们所调用的《init》()方法抛出的任何异常

2一个对象不再为程序所引用了,虚拟机必须回收(垃圾收集)那部分内存,实现可以决定何时应垃圾收集不再被引用的对象——或者决定时候根本不收集它们,并没有要求Java虚拟机实现一定要释放不再被引用的对下昂所占据的内存。

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

4.垃圾收集器(最多)只会调用一个对象的终结方法一次——在对象编程不再被引用的之后的某个时候,在占据的对象被重用之前,如果终结方法代码执行后,对象重新被引用了,随后再次变得不被引用,垃圾收集器不会第二次调用终结方法,垃圾收集器自动调用的finalize()方法抛出的任何异常都将被忽略。

196页

1.Java程序可以在运行时通过用户自定义的类装载器装在类型来动态地扩展程序,所有被装载的类型都在方法区占据内存空间。如果Java程序持续通过用户自定义的类型装载类型,方法去的内存会不断增长,如果某些动态装载的类型只是临时需要,当它们不再被引用滞后,占据的内存空间可以通过卸载类型而释放。

2.如果程序不再使用某类型,那么这个类型变成不可触及的,可以被垃圾收集。

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

4.如果程序保持对Class实例的明确引用,它就是可触及的;如果在堆中还存在一个可触及的对象,在方法区中它的类型数据指向一个Class实例,那么这个Class实例就是可触及的。

198页

1.Java的连接模型允许用户自行设计类装载器,这样以来就可以在运行时定制扩展用户的程序,通过用户自定义的类装载器,你的程序可以装载在编译时并不知道或许尚未存在的类或者接口,并动态连接它们

2.当编译一个Java程序的时候,会得到程序中每个类或者接口的独立的class文件,它们之间通过接口(harbor)符号相互联系,或者与Java API的class文件相联系.当运行程序的时候,Java虚拟机装载程序的类和接口,并且在动态连接的过程中把他们相互勾连起来.

3.当一个类型首次装载时,所有来自于类型的符号引用都装载到了类型的运行时常量池.

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

5.常量池按照一系列项组织,每一项拥有一个唯一的索引,使用符号引用的Java虚拟机指令指定位于常量池中的符号引用的索引.

6.Java虚拟机为每一个装载的类和接口保存一份独立的常量池.

7.来自相同或不同方法中的几条指令,可能指向同一个常量池入口,但是每一个常量池入口都只被解析一次.当符号引用被一条指令解析过后,以后直接使用第一次解析的结果

199页

1.连接不仅仅包括把符号引用替换成直接引用,还包括检查正确性和权限.

2.不管何时执行解析,都在程序执行过程中第一次实际访问一个符号引用的时候才抛出错误.

3.除了简单地在运行连接模型之外,Java程序还可以在运行时决定连接哪个类型,Java的体系结构允许动态扩展Java程序,这个过程包括运行时决定所使用的类型,装载它们,使用它们.通过传递类型的名字到java.lang.Class的forName()方法,或者用户自定义的类装载器的loadClass()方法,可以动态扩展Java程序.用户自定义的类装载器可以从java.lang.ClassLoader的任何子类创建,两种方法都可以使运行中的程序去调用在源代码中为提及的,而是在程序运行中决定的类型

201页

1.如果没用特别使用类装载器的要求,应该用forName(),因为forName() 使用默认的启动类装载器,他是最直接的动态扩展的方法,如果需要请求的类型在装载时就初始化的话,则不得不使用forName()方法或者调用它的三参数版本并且传递true作为initialize参数的值时,返回的类型一定已经被连接,初始化过了.

2.每一个类装载器拥有一个独立的命名空间,就为在不同的命名空间中装载的类型提供了一层安全保护,如果安全上需要包含一种定制方式把类型装载到保护域中,就需要使用类装载器而非forName()

3.创建用户自定义的类装载器,重要的原因就是能够以定制方式把类型的全限定名装换成一个Java class文件格式的字节数组,比如从网络上加载,从数据库中取出,从加密文件中提取等

4.当解析常量池中的入口需要装载类型的时候,虚拟机使用装载引用类型的同一个类装载器来装载所需的类型,指向数组类的符号引用的最终解析结果是一个Class实例,表示该数组类.

5.如果数组的元素类型是一个引用类型,虚拟机用当前类装载器解析元素类型.如果数组时关于基本类型的数据,那么虚拟机立即就会创建关于那个元素类型的新数组类,维数在此时确定,然后创建一个Class的实例来代表这个类型

203页

1.如果一个CONSTANT_Class_info入口的name_index项指向的CONSTANT_Utf8_info字符串是由一个左方括号开始的,比如"[I",那么它指向的是一个数组类,指向数组类的符号引用的最终解析结果是一个Class实例,表示该数组类.

203 - 260 页 空

260页

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

2.除了释放不再被引用的对象,垃圾收集器还要处理堆碎块

261页

1.垃圾收集算法都必须做两件事情,首先,它必须检测出垃圾对象,其次,它必须回收垃圾对象所使用的堆空间还给程序.

2.垃圾检测通常通过建立一个跟对象的集合并且检查从这些跟对象开始的可触及性来实现.
如果正在之星的程序可以访问到的跟对象和某个对象之间存在引用路径,这个对象就是可触及的.从这些根对象开始,任何可以被触及的对象都被认为是"活动"的对象,无法被触及的对象被认为是垃圾,因为它们不再影响程序的未来执行.

3.Java虚拟机的根对象集合总是包含局部变量中的对象引用和栈帧的操作数栈;另外是被夹在的类的常量池中的对象引用;还一个传递到本地方法中的,没有被本地方法"释放"的对象引用;还一个方法区中的类数据本身可能被存放在使用垃圾手偶机器的堆中,以便使用和释放对象同样的垃圾收集算法检测和卸载不再被使用的类.

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

5.区分活动对象和垃圾的两个基本方法是引用计数和跟踪,引用计数垃圾收集器通过为堆中的每一个对象保存一个计数来区分活动对象和垃圾对象,这个计数记录下对那个对象的引用次数;跟踪垃圾收集器实际上跟踪从根节点开始的引用图,在追踪中遇到的对象以某种方式打上标记,当追踪结束时,没有被打上标记的对象被判定是不可触及的,可以被当做垃圾收集.

262页

1.引用计数收集器,因为无法检测出循环引用,所以现在不为人接受,

2.跟踪收集器追踪从根节点开始的对象引用图,在追踪过程中遇到的对象以某种方式打上标记.当追踪结束时,未被标记的对象就知道是无法触及的,从而可以被收集.

3.基本的追踪算法分为两个阶段,标记阶段,垃圾收集器便利引用树,标记每一个遇到的对象,在清除阶段,未被标记的对象被释放了,使用的内存被返回正在执行的程序.

4.Java虚拟机的垃圾收集器对付堆碎块的策略,标记并清除收集器通常使用两种策略是压缩和拷贝.它们都是快速地移动对象来减少堆碎块.

5.压缩收集器把活动的对象越过空闲区滑动到堆的一段,这个过程中,堆的另一段出项一个大的连续空闲区,所有被移动的对象的引用页被跟新,指向新的位置.

6.跟新被引用的对象的引用有时候通过一个间接对象引用,不直接引用堆中的对象,对象的引用实际上指向一个对象句柄表,对象句柄才指向堆中对象的实际位置,当对象被移动了,只有这个句柄需要被根性为新位置.所有的程序中对这个对象引用仍然指向这个具有新值的句柄.

7.拷贝垃圾收集器,按代收集的收集器:把对象按照寿命来分组解决效率地下问题

8.自适应收集器:根据堆中的情形,对应地调整为合适的垃圾收集技术.

264页

1.渐进式垃圾收集器就是不试图一次性返现并回收所有不可触及的对象,而是每次发现并回收一部分,

2.火车算法

267页

1.一个对象可以拥有终结方法:这个方法是垃圾收集器在释放对象钱必须运行,给一个类加上终结方法,只需在类中声明一个方法
protected void finalize() throws Throwable{ super.finalize();}

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

268页

1.只有垃圾收集器运行对象的终结方法,无法预测何时对象会被垃圾收集,所以也无法 预测对象的终结方法何事运行,应该避免编写的程序的正确性以来于对象的终结方法所运行的时机.

2.在垃圾收集器看来,堆中的每一个对象都有三种状态之一:可触及的,可复活的,以及不可出触及的.

3.如果垃圾收集器可以从根节点开始通过追踪"触及"到这个对象,它就是可触及的.每一个对象都是从可触及状态开始它的生命周期的,只要程序还保留至少一个可以触及的引用到该对象,它就一直保持可触及状态.一旦程序释放了所有到该对象的引用,然后这个对象就变成可复活状态.

4.可复活状态:它在从根节点开始的追踪图中不可触及,但是有可能在垃圾收集器执行某些终结方法时触及.

269页

1.任何处于可复活状态的对象都可能再次复活,垃圾收集器不能归还可复活的对象所占据的内存,知道它确信不再有任何终结方法由机会把这个对象复活,在执行所有可复活对象可能声明的finalize()方法之后,垃圾收集器会把那些处于可复活状态的对象转化为可触及的状态或者到不可触及状态.

2.不可触及状态标志着不但对象不再被触及,而且也不可能通过任何终结方法复活,不可触及的对象不再对程序执行产生影响,可以自由地回收它们所占据的内存.

3.软可触及,弱可触及,影子可触及

4.引用对象封装了指向其他对象的连接,被指向的对象称为引用目标,所有的引用对象都是抽象的java.lang.ref.Reference类的子类的实例. SoftReference,WeakReference,PhantomReference

5.要创建一个软引用,弱引用或者影子引用,简单地把引用传递到对应的引用对象的构造方法中去.

270页

1.一旦一个引用对象创建后,它将一直维持到它的引用目标的软引用,弱引用或者影子引用,直到它被程序或者垃圾收集器清除.要清除一个引用对象,程序或者垃圾收集器只需要调用引用对象的clear()方法.

2.垃圾收集器可以随意更改不是强可触及的任何对象的可触及性状态,当使用软引用,弱引用或者影子引用的时候,如果对可触及性状态的改变由兴趣,可以把引用对象和引用队列关联起来.

3.引用队列java.lang.ref.ReferenceQueue类的一个实例,垃圾收集器在改变可触及性状态时会添加所涉及的引用对象,设置并且观察引用队列,当垃圾收集器堆你感兴趣的对象改变可触及性状态时,你就可以异布得到通知了.

4.要把一个引用对象和一个引用队列关联起来,可以简单地在创建引用对象时把引用作为构造方法的参数传递到引用队列,如此创建的引用对象除了保持对引用目标的引用外,还保持对引用队列的引用,当垃圾收集器对引用目标的可触及性状态做了改变时,它就会把引用对象加入到与它关联的引用队列中区

271页

1.为了把引用对象加入到它所关联的队列中,垃圾收集器执行它的enqueue()方法,enqueue()方法是在超类Reference中定义的,只有在创建引用对象时关联了一个队列,并且仅当该对象的enquee()方法第一次执行时,才能把引用对象加入到这个队列中.

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

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

4.弱可触及  对象既不是强可触及的也不是软可触及的,但是从根节点开始可以通过一个或多个弱引用对象触及.垃圾收集器必须归还弱可触及对象所占据的内存.

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

6.影子可触及 对象不是强可触及,软可触及,也不是弱可触及,并已经被断定不会被任何终结方法复活,并且它可以从根节点开始通过一个或多个影子引用对象触及. 垃圾收集器从不会清除一个影子引用,所有的影子引用都必须由程序明确地清除.

7.不可触及的对象已经准备好被回收了.

272页

1.要使用一个软引用或者弱引用的引用目标,可以调用引用对象的get()方法,如果引用目标没有被清除,则会得到堆引用目标的一个强引用,清除 则返回null 影子引用的 get() 方法,得到null 一个对象如果到达了影子引用就不能复活.

2.虚拟机的实现需要在抛出OutOfMemoryError之前清除软引用

3.软应用可以让你在内存中缓存那些需要从外部数据源费时取回的数据.

4.弱引用:垃圾收集器可以自行决定是否清除指向软可触及的对象的软连接,而它必须在判断出对象处于连接状态时就立即清除弱引用.

5.影子可触及性表示对象即将回收,可以利用引用队列中影子引用来触发一些拟希望在对象声明周期的最后时刻需要完成的动作,在完成了影子可触及对象的临终清理之后,必须调用指向它的影子引用对象的clear()方法.

280页

1.Java虚拟机是基于栈的机器,几乎所有Java虚拟机的指令都与操作数栈相关,大多数指令都会在执行自己功能的时候进行入栈,出栈操作.

2.许多操作码执行常量入栈操作,操作码在执行常量入栈操作之前,使用以下三种方式指明常量的值:常量值影式包含在操作码内部,常量值在字节码流中如同操作数一样紧随在操作码之后,或者从常量池中取出常量.

3.一些操作码自行指明入栈的常量的类型和值,Java虚拟机为经常压入栈的各种不同类型的数据定义了一些操作码,这些操作码更有效率.

iconst_1 将int类型值1压入栈
fconst_1将float类型值1压入栈
dconst_1将double类型值1压入栈

281页

1.
aconst_null操作码将一个空的对象引用类型压入栈.
bipush byte1(类型为byte) 将byte1转换为int类型,然后将其压入栈
sipush byte1,byte2 将byte1和byte2(类型为short)装换为int类型,压入栈
ldc indexbyte1 从由intexbyte1指向的常量池入口中取出一个字长的值,然后将其压入栈
ldc_w indexbyte1, indexbyte2 从由indexbyte1和indexbyte2指向的常量池入口中取出一个字长的值,然后将其压入栈


282页

1.Java源代码中所有的字符串文字最终都作为入口存储于常量池中,如果同一个应用程序的对个类都是用同样的字符串文字,那么此字符串文字将在使用它的所有类的class文件中出现.Java虚拟机把所有具有相同字符顺序的字符串文字处理为同一个String对象,如果多个类使用同一个字符串文字,Java虚拟机将只会创建一个值的对象表示所有的字符串文字.

2.当虚拟机解析一个字符串文字的常量池入口时,它"拘留"这个字符串,首先,虚拟机检查这个字符串中字符的顺序是否已经被拘留了,如果是,虚拟机就会使用已拘留的字符串同样的引用,否则,它将会创建一个新的String对象,把对这个新String对象的引用加入到已拘留的字符串集合中去,然后,再把这个引用赋给新的已拘留的字符串.

282页

1.
nop 不做任何操作
pop 从操作数栈中弹出栈顶部的一个字
pop2 从操作数栈中弹出最顶端的两个字
swap 交换栈顶部的两个字
dup  复制栈顶部的一个字
dup2 复制栈顶部的两个字
dup_x1复制栈顶部的一个字,并将复制内容及原来弹出的两个字节的内容压入栈
dup2_x2复制操作数栈顶部的两个字,并将复制内容及原来弹出的四个字长的内容压入栈

283页

1.
iload  vindex 将位置为vindex的int类型局部变量压入栈
iload_0 将位置为0的int类型局部变量压入栈
fload  vindex 将位置为vindex的float类型局部变量压入栈
fload_1 将位置为1的float类型变量压入栈
lload  vindex 将位置为vindex和(vindex+1)的long类型局部变量压入栈
lload_1将位置为1和2的long类型局部变量压入栈
dload double类型  aload对象引用

284页

1.
istore vindex 从栈中弹出int类型值,然后将其存到位置为vindex的局部变量中
istore_0 从栈中弹出int类型值,然后将其存到位置为0的局部变量中
fstore float类型 lstore long类型 dstore double类型 astore引用类型

285页

1.无符号8位局部变量索引,把方法中局部变量数限制在256以下,一条单独的wide的指令可以将8位索引在扩展8位,跟随在wide操作码和修改过的操作码之后的两个字节组成指向局部变量的16位无符号索引.

wide iload indexbyte1 ,indexbyte2  从局部变量位置为index的地方取出int类型值,并将其压入栈
wide istore indexbyte1,indexbyte2 从栈中取出int类型值,然后将其存入位置为index的局部变量中

290页

1.Java虚拟机包括许多进行基本类型装换的操作码,它们后面没有操作数,转换的值从栈顶端获得,Java虚拟机从栈顶端弹出一个值,对它进行转换,然后再把转换结果压入栈.
i2l 将int类型值转换为long类型值
i2b将弹出的int类型值截短为byte类型,然后再对其进行代符号扩展,恢复成int类型
i2s                       short
i2c将弹出的int类型值截短为char类型,然后再对其进行零扩展,恢复成int类型

291页

1.Java虚拟机中没有把long,float,double类型值直接转换为比int类型占据更小空间的数据类型的操作码,把float类型转换为byte类型需2个步骤:首先,float类型值必须通过f2i指令转换为int类型值,然后,所得的int类型值再通过i2b指令转换成byte类型值.

2.不存在byte,short,char装换成int的操作码,因为任何byte,short和char类型的值在压入栈的时候,就已经有效地转换成int类型值了.

3.涉及byte,short和char类型的运算操作首先会把这些值转换为int类型,然后对int类型值进行运算,最后得到int类型的解雇,因此,如果把两个byte类型值相加,最后会得到一个int类型的结果,如果需要得到 byte 类型结果,必须将这个int类型的结果显式装换为byte类型.

296页

1.Java虚拟机所支持的所有的整数类型--byte,short,int和long,它们都是带符号的二进制补码数.在一个二进制补码数中,最重要的位就是它的符号位,符号位为1,表示负整数;符号位为0,表示正整数和数字0

2.能够被二进制补码方案表示的数的范围为:2的总位数次幂,负数可以通过负数和2的某次方幂相加而得出. (补码:正数一样,负数取反加1)

3.在带符号二进制补码数上进行的加法运算与无符号二进制的加法运算一样,运算结果是在该类型的有效范围内的情况下进行.

4.Java虚拟机中出项的整数运算的溢出并不会导致抛出异常,其结果只被简单地截短以符合数据类型.整数除0时会抛出一个ArithmeticException异常.

298页

1.iadd从栈中弹出两个int类型数,相加,然后将所得int类型结果压回栈
ladd

2.iinc vindex,const把常量与一个位于vindex位置的int类型局部变量相加
wide iinc,indexbyte1,indexbyte2 constbyte1.constbyte2把常量与一个位于index位置的int类型局部变量相加

3.isub 从栈中弹出两个int类型数,相减(顶端的值充当减数,底端的值充当被减数),然后将所得int类型结果压回栈

4.imul从栈中弹出两个int类型数,相乘,然后将所得int类型结果压回栈

5.idiv从栈中弹出两个相同类型的值,底端的数除以栈顶端的数,结果被压回栈.对于整数除法所产生的结果进行取整操作,如果除数被0除,则会抛出ArithmeticException异常.

299页

1.irem从栈中弹出两个int类型数,相除,然后将所得int类型余数压回栈,两个操作数从栈中弹出两个值,底端的数作为被除数或者分子,栈顶端的数作为除数或者分母,除法的余数被压回栈.取余操作时,如果除数为0,将会抛出ArithmeticException异常.

2.ineg 从栈中弹出一个int类型数,取反,然后将所得int类型结果压回栈

304页

1.Java虚拟机的逻辑操作主要针对int和long类型

<< ishl 向左堆int类型值进行移位操作  (进行模32运算(long为64),1<<35和1<<3得到的结果都是8)
>> ishr 向右对int类型进行算术移位操作,高位用符号位填充
>>> iushr向右堆int类型值进行逻辑移位操作,符号位用0填充

iand 堆两个int类型进行逻辑"与"操作
ior 或
ixor 异或

虚拟机机种用int表示boolean类型.

313页

1.fadd 将两个float类型值弹出栈,相加,再将float类型的结果压入栈
2.fsub 将两个float类型值弹出栈,相减,再将float类型的结果压入栈
3.fmul 将两个float类型弹出栈,相乘,再将float类型的结果压入栈
4.fdiv 将两个float类型值弹出栈,相除,再将float类型的结果压入栈,任何浮点除法都不会导致异常抛出
5.frem将两个float类型弹出栈,取余,在将float类型的结果压入栈,所有的浮点取余操作都不会导致异常抛出
6.fneg 取反

318页

1.除非作为对象的一部分,否则不能为基本类型在堆中分配内存.只有对象引用和基本类型可以在Java的栈中以局部变量形式存在,Java栈不能容纳对象.

2.实例化一个新对象需要通过new操作码来实现,new操作码后面紧随着两个字长的操作数,这两个字长的操作数结合起来表示常量池中的一个不带符号的16位长度的索引,在特定偏移量位置处的常量池入口给出了新对象所属类的信息如果没有信息,虚拟机会解析这个常量池入口,它会为堆中的对象建立一个新的实例,用默认初始值初始化对象实例变量,然后把新对象的引用压入栈.

319页

1.new indexbyte1,indexbyte2 在堆中创建一个新的对象,将其引用压入栈

2.
putfield indexbyte1 indexbyte2 设置对象字段(由index指定)的值,值value和对象引用objectref均从栈中获得
getfield indexbyte1 indexbyte2 将对象字段压入栈,对象引用objectref从栈中获得

3.putstatic indexbyte1 indexbyte2 设置静态字段的值(值从栈中取得)
getstatic indexbyte1 indexbyte2 将静态字段压入栈

320页

1.checkcast indexbyte1,indexbyte2 如果栈中的对象引用不能转换为位于index位置的类,则抛出ClassCastException异常
instanceof indexbyte1,indexbyte2 如果栈中的对象是位于index位置的类的实例,则向栈中抛入true(1),否则压入false(0).

2.newarray操作码用来创建基本类型的数组,不是对象引用的数组.基本类型由紧随newarray操作码的单字节操作数"atype"指定.
newarray atype 从栈中弹出数组长度,使用atype所指定的基本类型数据类型分配新数组,将新数组的对象引用压入栈
anewarray indexbyte1,indexbyte2 从栈中弹出数组长度,使用由indexbyte1和indexbyte2所指定的类分配新对象数组,将新数组的对象引用压入栈
multianewarray indexbyte1,indexbyte2,demensions 从栈中弹出数组的维数,使用由indexbyte1和indexbyte2所指定的类分配新多维数组,将新数组的对象引用压入栈

321页

1.arraylength 从栈顶端弹出一个数组引用,然后把这个数组的长度压入栈

2.
获取数组元素:baload 将byte类型或者boolean类型的数组的索引index和数组引用arrayref弹出栈,将arrayref[index]压入栈

3.bastore 将一个值保存到数组元素的操作码,值,索引和数组引用都从栈顶端弹出,
将byte类型或者boolean类型的数组的值value,缩影index和数组引用arrayref弹出栈,赋值为arrayref[index] = value

326页

1.if语句能够转换成任意一组操作码,每一种操作码都会从栈顶端弹出一个或者两个值,然后进行比较,从栈中弹出一个值的操作码把该值与0比较;如果比较成功,Java虚拟机将按照由比较操作码的操作数所提供的偏移量执行分支或者跳转操作

2.对于所有的条件分支操作码,Java虚拟机通过同样的过程来决定下一条将要进行的指令,虚拟机首先执行由操作码所决定的比较,如果比较失败,虚拟机将继续执行条件分支语句后面的代码,如果比较成功,虚拟机将会使用紧随操作码后的两个操作数字节来产生一个带符号的16位的偏移量,虚拟机给当前PC寄存器加上这个偏移量来获取目标地址,目标地址必须指向同一个方法中的一条指令的操作码,程序会继续从目标地址开始运行.

3.
ifeq branchbyte1 ,branchbyte2 从栈中弹出int类型值,如果该值为0,则跳转到偏移量指定位置执行分支操作
ifne branchbyte1,branchbyte2 从栈中弹出int类型值,如果该值不为0,
iflt   小于 
ifle   小于等于
ifgt    大于
ifge   大于等于
if_icmpeq branchbyte1 ,branchbyte2 从栈中弹出int类型值value1和value2,如果value1==value2,则跳转到偏移来那个指定位置执行分支操作
if_icmpne !=
if_icmplt <
if_icmple <=
if_icmpgt >
if_icmpge >=
lcmp 从栈中弹出 long 类型值value1和value2,进行比较,将所得int类型结果压入栈
fcmpg 从栈中弹出float类型值value1和value2,进行比较,将所得int类型结果压入栈
ifnull branchbyte1 ,branchbyte2 从栈中弹出引用值value,如果vlaue ==null,则跳转至偏移量指定位置执行分支操作
ifnonull
if_acmpeq branchbyte1,branchbyte2 从栈中弹出引用值value1和value2,如果value1==value2,则跳转至偏移量指定位置执行分支操作

goto branchbyte1,branchbyte2 跳转至偏移量指定位置执行分支
goto_w branchbyte1,branchbyte2 branchbyte3 branchbyte4 跳转至偏移量指定位置执行分支

表条件分支

lookupswitch  <0-3 byte pad> defaultbyte1,defaultbyte2,defaultbyte3,defaultbyte4,npairs1,npairs2,npairs3,nparis4, casevaule/branch offset pairs  弹出键值,与case值相匹配,如果匹配成功,则跳转至相关程序分支偏移量处,否则跳转至默认程序分支偏移量.  内部排序按照大小顺序排列
tableswitch 弹出键值,如果不再低/高值范围内,则跳转至默认程序分支偏移量处,否则获取程序分支偏移量,并跳转至该偏移量处.

338页

1.Java虚拟机将在表中查找异常,然后跳转到实现catch字句的字节码序列,每个捕获异常的方法斗鱼异常表相关联,该异常表与方法的字节码序列一起送到class文件中,每一个被try语句捕获的异常都与异常表中的一个入口相对应;

2.异常表中的每一个入口包括:
起点,终点,将要跳转到的字节码序列中的PC指针偏移量,被捕获的异常类的常量池索引

3.如果异常在方法执行时抛出,Java虚拟机将会在整个异常表中搜寻相匹配的项如果当前程序计数器在异常表入口所指定的范围内,而且所抛出的异常类是该入口所指向的类,那么该入口即为搜寻的入口,

4.Java虚拟机按照每个入口在表中出项的顺序进行检索,当遇到第一个匹配时,虚拟机将程序计数器设为新的pc指针偏移量位置,然后从该位置继续执行,如果没有发现相匹配的项,虚拟机将当前栈帧从栈中弹出,再次抛出同样的异常,当Java虚拟机弹出当前栈帧时,虚拟机马上终止当前方法的执行,并且返回至调用本方法的方法中,但是并未继续正常执行该方法,而是在该方法中抛出同样的异常,使得虚拟机在该方法中再次执行同样的搜寻异常表的操作.

athrow  弹出Throwable对象引用,抛出异常   指令athrow从栈顶弹出字,并假设它是一个对象的引用,该对象为Throwable类的子类的实例,所抛出异常的类型由弹出的对象引用类型决定

342页

1.Java虚拟机在每个try语句块和与相关的catch字句的结尾处都会"调用"finally字句的子例程,finally字句结束后(finally字句最后一句正常执行完毕,不包括抛出异常,或执行return,continue,break),隶属于这个finally字句的微型子例程执行"返回"操作,程序在第一次调用微型子例程的地方继续执行后面的语句.

2.jsr 指Java虚拟机跳转到微型子例程的操作码,当Java虚拟机遇到jsr或者jsr_w指令,它会把返回地址压入栈,然后从微型子例程开始处继续执行,当Java虚拟机遇到jsr或者jsr_w指令,它会把返回地址压入栈,然后从微型子例程的开始处继续执行,返回地址是紧接在jsr或jsr_w操作码和操作数后字节码的地址,该地址类型为returnAddress

3.微型子例程执行完毕后,将调用ret指令,ret指令的功能是执行从子例程中返回的操作,ret指令只用一个操作数,这个操作数是一个存储返回地址的局部变量的索引.

4.jsr branchbtye1,branchbyte2 把返回地址压入栈,跳转至偏移量指定位置处执行分支操作
ret index  返回存储在局部变量index中的地址

5.在每一个子例程的开始处,返回地址都从栈顶弹出,并且存储在局部变量中,稍后,ret指令将从这个局部变量取出返回地址,因为finally本身会抛出异常或者含有return,break,continue等语句,这个jsr指令压入栈的额外返回地址必须立即从栈中清除,不再执行ret指令

350页

1.实例方法和静态方法区别:
实例方法在调用之前,需要一个实例,而类方法不需要
实例方法使用动态(迟)绑定,而类方法使用静态(早)绑定

2.
invokevirtual indexbyte1 ,indexbyte2 把objectref(对象引用)和args(参数)从栈中弹出,调用常量池索引指向的实例方法

invokestatic indexbyte1,indexbyte2 把 args从栈中弹出,调用常量池索引指向的类方法

3.到方法的引用最初是符号化的,所有的调用指令都指向一个最初包含符号引用的常量池入口,当Java虚拟机遇到一条调用指令时,如果还没有解析符号引用,那么虚拟机要确定解析符号引用作为执行指令调用执行过程中的一部分.

4.在执行invokevirtual指令之前,Java虚拟机使用invokevirtual操作码之后的indexbyte1和indexbyte2这两个操作数,产生一个指向当前类常量池的无符号16位索引,这个常量池入口包含一个指向将要调用的方法的符号引用.

5.一旦解析了一个方法后,Java虚拟机就准备调用它,如果这个方法是一个实例方法,它必须在一个对象中被调用,对每一次实例方法的调用,虚拟机需要在栈里存在一个对象引用objectref,如果该方法需要参数,那么除了objectref,虚拟机还需要在栈中存在该方法所需要的参数args,如果是类方法,则不需要objectref.  objectref和args必须在调用指令执行前,被其他指令压入调用方法的操作数栈.

351页

1.虚拟机为每一个Java方法建立一个新的栈帧,栈帧包括:为方法的局部变量所预留的空间,该方法的操作数栈,以及特定虚拟机实现需要的其他所有的信息,局部变量和操作数栈的大小在编译时计算出来,并放置到class文件中区,然后虚拟机就能够了解到方法的栈帧需要多少内存,到虚拟机调用一个方法的时候,它为该方法创建适当大小的栈帧,再将新的栈帧压入Java栈.

2.处理实例方法时,虚拟机从所调用方法栈帧内的操作数栈中弹出objectref和args ,虚拟机把objectref作为局部变量0放到新的栈帧中,把所有的args作为局部变量1,2...等处理,objectref是隐式传给说有实例方法的this指针. 类方法则指需要参数

3.当objectref和args被赋给新栈帧中的局部变量之后,虚拟机把新的栈帧作为当前栈帧,然后将程序计数器指向新方法的第一天指令.

4.当调用本地方法时,虚拟机不会将一个新的栈帧压入Java栈,当线程进入到本地方法的那一刻,它就将Java栈抛在身后,直到本地方法返回以后,Java栈才被重新使用.

352页

1.invokesepcial调用<init>()原因在于:子类的<init>()方法需要调用超类的<init>()方法的能力, 用invokesepcial 调用<init>()方法

2.invokespecial只用来调用私有实例方法,不能调用私有类方法,私有类方法由invokestatic指令调用 当使用 invokespecial指令时,虚拟机会按照引用的类型来选择调用的方法.

3.super和invokespecial,当Java虚拟机解析一个invokespecial指令中指向超类方法的符号引用时它会动态搜寻当前类的超类,找到离得最精的超类中的该方法的实现,

356页

1.操作码invokeinterface与invokevirtual的功能相同:它调用实例方法并使用动态绑定,区别在于:当引用的类型为类的时候,使用invokevirtual,当引用类型为接口时,使用invokeinterface


362页

1.ireturn 弹出int类型值,压入调用方法的栈并返回
areturn 弹出对象引用,压入调用方法的栈并返回
return 返回void
如果有返回值,它必须被放置在操作数栈中,返回值从操作数栈中弹出,然后又被压入调用方法栈帧的操作数栈中,弹出当前栈帧,调用方法的栈帧成为当前栈帧,程序计数器被重置,其值为调用方法紧随调用返回方法那条指令的下一条指令.

364页

1.Java所使用的同步机制是监视器,监视器支持两种线程:互斥和协作,Java虚拟机通过对象锁来实现互斥,允许多个线程在同一个共享数据上独立而互不干扰地工作,协作则是通过Object类的wait方法和notify方法来实现,允许多个线程为了同一个目标而共同工作

366页

1.一个线程只有在它正持有监视器时才能执行等待命令,而且它只能通过再次成为监视器的持有者才能离开等待区.

367页

1.Java虚拟机的一些运行时数据区会被所有的线程共享,其他的数据是各个线程私有的,堆和方法去是被所有线程共享的,Java程序需要为两种多线程访问数据进行协调:
保存在堆中的实例变量
保存在方法区中的类变量
程序不需要协调保存在Java栈中的局部变量,因为Java栈中的数据时属于拥有该栈的线程私有的.

2.在Java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的,对于对象来说,相关联保护对象的实例变量,对于类来说,监视器保护类的类变量,如果一个对象没有实例变量,或者一个类没有类变量,相关联的监视器就什么都不监视.

368页

1.Java虚拟机为每一个对象和类都关联一个锁,如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样的锁了.

2.类锁实际上用对象锁实现,当Java虚拟机装载一个class文件的时候,它会创建一个java.lang.Class类的实例,当锁住一个类的时候,实际上锁住的是哪个类的Class对象

3.Java虚拟机中的一个线程在它到达区域开始处的时候请求一个锁,Java程序中每一个监视区域都和一个对象引用先关联,当一个线程到达监视区域的第一条指令的时候,线程必须对该引用对象加锁,否则线程不充许执行其中的代码,一旦获得了锁,线程就进入了被保护的代码,当贤臣离开这块代码的时候,不管他是如何离开,它都会释放相关对象上的锁.

4.Java编程人员不需要自己动手加锁,对象锁是在Java虚拟机内部使用的,在Java程序中,你只需要编写同步语句或同步方法就可以标志一个监视区域,当Java虚拟机运行你的程序的时候,每一个进入一个监视区域的时候,它每次都会自动锁上对象或者类.

5.要建立一个同步语句,在一个计算对象引用的表达式上synchronized关键字就可以了

369页

1.monitorenter   弹出objectref,获得和objectref相关联的锁
monitorexit      弹出objectref,释放和objectref相关联的锁

2.当Java虚拟机遇到monitorenter的时候,它获得栈中objectref所引用的对象的锁,如果线程已经拥有了那个对象的锁,锁的计数器会加1,每条monitorexit指令会引起计数器减1

3.同步整个方法,只需要在方法修饰符中加上synchronized关键字,当虚拟机解析堆方法的符号引用时,它判断这个方法是否是同步的,如果是同步的,虚拟机就在调用方法之前获取一个锁,对于实力方法来说,虚拟机在方法将要调用的时候获取对象相关联的锁,对于类方法来说,它获取方法所属的类的锁,当同步方法执行完毕的时候,不管是正常结束还是抛出异常,虚拟机都会释放这个锁

374页

1.Object类声明了5个方法,程序员可以用来访问Java虚拟机同步的协调支持,这些方法都是被申明成public和final,只有在同步方法或者同步语句中才能调用这些方法,也就是说,在这些方法被调用的时候,相关联的对象必须已经被加锁了

void wait()   进入监视器的等待区,直到被其他线程唤醒
void wait(long timeout) 进入间失去的等待区,直到被其他线程唤醒,或者经过timeout所指定的毫秒数后自动唤醒
voidt wait(long timeout,int nanos)  进入监视区的等待区,直到被其他线程唤醒,或者进过timeout和nanos相加所指定的纳秒数自动唤醒
void notify() 唤醒监视器的等待区中的一个等待线程(如果没有线程在等待,就什么都不干)
void notifyAll() 唤醒监视器的等待区中的所有线程(如果没有线程在等待,就什么都不干)


















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值