java体系结构介绍
java虚拟机的主要任务是装载class文件并且执行其中的字节码 。
java虚拟机的执行引擎:
1.一次性解释字节码。
2.即时编译器,第一次被执行的字节码会被编译成本地机器代码,被缓存,可重用。
3.自适应优化器,开始解释字节码时监视程序的活动,记录使用最频繁的代码段。运行时只把最频繁的代码翻译成本地代码。
java程序通过调用本地方法和主机交互。
本地方法由其他语言编写,保存在动态连接库,与平台有关。
本地方法是联系java程序和底层主机操作系统的连接方法。
一个java应用程序可以使用两种类装载器:启动类装载器和用户定义的类装载器。
启动类装置器(系统唯一)是java虚拟机实现的一部分。通常使用某种默认方式从本地磁盘中装载类,包括java Api的类。
用户定义的类装载器能够用java编写,能被编译为class文件,能被虚拟机装载,能实例化,是运行中的java应用程序中可执行代码的一部分。
当被装载的类应用另一个类,虚拟机会使用装载第一个类的类装载器装载被引用的类。
被装载的类默认情况下只能看到被同一个类装载器装载的别的类,每一个类装载器都有自己的命名空间,不能互相访问,除非应用程序显示的允许。
使用类装载器的体系结构来控制任何从不同源文件中装载的代码之间的相互影响,特别是能够阻止恶意代码获取访问和破坏善意代码的权限。
Web浏览器是一个动态扩展的例子,使用用户定义的类装载器从网络下载用于java applet的class文件。
java class文件主要在平台无关性和网络移动性方面使Java更适应于网络。
class文件平台无关性:为Java程序提供独立于底层主机平台的二进制形式的服务。
java class文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统的二进制文件。
java编译器把Java源文件的指令翻译成字节码,所以能在指定的平台运行。
Java class文件中的字节顺序是高位在前。
Java class网络移动性:设计紧凑可以快速在网络上传送。
javaApi的方法进行任何潜在危险操作(比如本地磁盘写操作)之前都会通过查询安全管理器检验是否得到授权。
java1.2中安全管理器被访问控制器所替代,访问控制器是一个类用来执行栈校验,决定是否执行某种操作。
java语言能够提高开发者的效率,但执行速度较慢。
java使用对象需要严格执行类型规则,根本无法以可能导致内存冲突的方式直接管理内存。
java效率提升于对直接内存的约束,垃圾收集,数组边界检查,对对象引用的检查。
java提供的解决性能的方法:
1.随程序附带一种虚拟机。
2.把程序中对时间要求严格的部分作为本地方法实现。
3.把整个程序编译成一个单独的可执行文件。
4.在安装时,把程序编译成一个单独的可执行程序。
把Java程序编译成单独的可执行程序的方法(预编译)能够改善性能,但牺牲Java的动态扩展能力。
java内存管理和线程调度存在缺陷,无法确定垃圾收集器何时开始,持续时间;线程调度模糊不清。很难用于实时响应事件的软件。
平台无关
java语言基本数据类型在所有平台都是一致的,c++中int的占位宽度是由编译器根据目标平台的字长决定。
class文件的格式,包括多字节值的高位优先存放约定都有严格的定义,于平台无关。
编写平台独立的java程序必须遵循两条原则:
1.不要依赖及时终结来达到程序的正确性。
2.不要依赖线程的优先级来达到程序的正确性。
这两条原则可以防止Java虚拟机规范中允许的垃圾收集和线程在不同实现中的变换所带来的不利影响。
为了保证Java程序的平台独立,必须依赖同步而不是优先级来在线程之间协调相互作用。
安全
java体系结构采用了一个扩展的内置安全模型。
java沙箱安全模型接收任何来源的代码,限制它进行可能破坏系统的任何动作。
Java沙箱的基本组件:类装载器结构,class文件校验器,内置于Java虚拟机的安全特性,安全管理器及JavaApi
java沙箱组件中的类装载器和安全管理器可以由用户定制。
1.2中可以使用Java平台预先制作好的安全管理器,允许用户在一个和程序分离的ASCII策略文件中说明安全策略。
类装载器体系结构防止恶意代码去干涉善意代码,守护被信任的类库的边界,将代码归入某类(保护域)该类确定代码可以进行哪些操作。
防止恶意代码去干涉善意代码,通过为由不同的类装载器装入的类提供不同的命名空间实现。
同一个命名空间的类可以直接进行交互,不同的命名空间的类无法察觉彼此,除非显式提供交互的机制。
守护被信任的类库的边界,通过分别使用不同的类装载器装载可靠的包和不可靠的包来实现。
1.2开始,除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器,某个特定的类装载器试图以常用方式装载类型以前,默认把任务委派给它的双亲,请求它的双亲来装载这个类型。双亲再依次请求它的双亲,直到启动类装载器。
1.2以前内置的类装载器负责在本地装载可用的class文件。
1.2以后装载本地可用的class文件的工作被分配到多个类装载器中,以前的内置类装载器被命名为启动类装载器,负责核心JavaApi的class文件。
启动类装载器->标准扩展类装载器->类路径类装载器->网络类装载器。
类装载器必须将一个被装载的类放置在一个保护域中,一个保护域定义了这个代码在运行时得到什么样的权限。
class文件校验器进行四趟独立的扫描:
1.结构检查,在类被装载时进行,检查class文件的内部结构,保证安全编译。
2.语义检查,在连接过程中进行,确认类型数据遵从Java编程语言的语义。
3.字节码验证,在连接过程中进行,检验所有字节码的完整性。
4.符号应用的验证,在动态连接的过程中解析符号引用时进行,确认被引用的类、字段和方法确实存在。
每个class文件必须以四个同样的字节开始,0xCAFEBABE
字节码流代表Java的方法,由被称为操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数,操作数用于在Java虚拟机执行操作码指令时所需的额外的数据。
执行字节码时,依次执行每个操作码,这就构成了线程,每个线程被授予自己的Java栈,由不同的栈帧组成,每个方法调用获得一个自己的栈帧即内存片段,其中存储局部变量和计算的中间结果。
大多数Java虚拟机的实现采用延迟装载类策略,直到类真正地被程序使用才装载。
动态连接是一个将符号引用解析为直接引用的过程。
解析时,虚拟机执行两个基本任务:1.查找被引用的类(如果必要的话就装载它)2.将符号引用替换为直接引用(如指向一个类、字符或方法的指针或偏移量)
java虚拟机内置的安全特性:
1.只能使用类型安全的、结构化的方法去访问内存。
2.并未指明运行时数据空间在Java虚拟机内部是怎样分布的。
3.禁止对内存进行非结构化的访问。
4.异常的结构化错误处理。
代码数字签名的过程第一步是一个单向的散列计算,它输入大量的数据,但产生少量的数据成为散列。
Java安全体系结构优点之一是可以只对它需要的资源进行访问的有限权限。
当类装载器将类型装入Java虚拟机时,他们将为每一个类型指派一个保护域,保护域定义了授予一段特定代码的所以权限。
AccressController访问控制器的checkPermission自顶向下检查栈,遇到没有权限帧,抛出异常。
Permission类的impiles()方法确定由Permission对象多代表的权限,是否在本质上隐含在由一个不同的Permission对象的所代表的权限中。比如询问一个代表了读取/temp目录下的所有文件的权限的FilePermission对象是否隐含了读取/temp/f的权限,impiles()返回true。反之询问读取/temp/f的FilePermission对象是否隐含了读取/temp权限,impliles()返回false。
char sep = File.separatorChar;
Permission file = new FilePermission(sep+"tmp"+sep+"f","read");
Permission star = new FilePermission(sep+"tmp"+sep+"*","read");
System.out.println(file.implies(star)+"");//false
System.out.println(star.implies(file)+"");//true
通过调用doPrivileged(),一个方法仅仅能使用它现在已经被授予的权限。
网络移动性
java体系结构把传统的单一二进制可执行文件切割成小的二进制碎片-javaclass文件,class文件可以独立在网络上传播,因为java是动态连接、动态扩展的,最终用户不需要等待所有程序的class文件都下载完毕就可以运行程序了。
java应用程序从main方法开始,其他的类在程序需要的时候才动态连接。
动态扩展,java程序可以再运行时装载额外的程序。
为了减少在网络上传送程序的时间,class文件被设计得很紧凑,每条指令都只占据一个字节。
因为紧凑,java编译器不会做太多的局部优化。
jar文件允许在一次网络连接中传送多个文件,还允许数据压缩。
jini服务对象提供一个运行时基础结构,以允许服务提供者为客户机提供服务,也使得客户机可以找到并访问服务。
运行时基础结构采用一种网络协议-“探索”,两种对象级协议-“查找”,“加入”。探索让客户机和服务可以找到查找服务,加入让服务在查找服务中注册自己,查找让客户机询问查找服务,是否存在一种服务可以帮助自己完成工作。
jini使用的网络移动的对象提高了分布式系统编成的抽象级别,有效地把网络编程转变成面向对象编程。
java虚拟机
java虚拟机之所以称为虚拟,是因为它仅仅是由一个规范来定义的抽象计算机。
一个运行时的Java虚拟机实例的天职就是负责运行一个Java程序。程序退出时,这个虚拟机实例也随之消亡。
Java程序初始类中的main()方法,作为该程序初始线程的起点,其他线程都是由这个线程启动。
java虚拟机内部存在两种线程:守护线程和非守护线程。
守护线程通常是由虚拟机自己使用如执行垃圾收集任务的线程,但也可以把其他线程标记为守护线程。
java的初始线程是非守护线程。只要有任何非守护线程,这个java程序就得继续运行。
每个Java虚拟机实例都有一个方法区已经一个堆,它们是由该虚拟机实例中所有线程共享的。
当虚拟机加载一个class文件时,会从这个class文件包含的二进制数据中解析类型信息,将类型信息放入方法区。程序运行时,把所有该程序在运行时创建的对象放入堆。
当一个新线程被创建,它将得到自己的PC寄存器以及一个Java栈。如果线程正在执行一个Java方法(非本地方法),它的PC寄存器的值总是指示下一条将被执行的指令,Java栈总是存储该线程中Java方法调用的状态。而本地方法调用的状态则是以某种依赖于具体实现的方法存储在本地方法栈,也可能在寄存器或者其他特定实现的内存区中。
Java栈由许多栈帧组成,一个栈帧包含一个java方法调用的状态。调用时压入栈帧,返回时弹出栈帧。
java虚拟机没有寄存器,其指令集使用Java栈来存储中间数据,便于虚拟机在那些只有很少通用寄存器的平台实现,也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。
java虚拟机是通过某些数据类型来执行计算的,数据类型分为两种:基本类型和引用类型。
基本类型的变量是原始值,引用类型的变量持有引用值,指对某个对象的引用。
Java虚拟机也把boolean看做基本类型,但指令集对boolean只有很有限的支持。
当编译器把java源码编译为字节码时,它会用int或byte来表示boolean。
在java虚拟机中,false是由整数零表示,所有非零整数都表示true。boolean数组是做byte数组来访问的,但在堆区,也可以被表示为位域。
一个long在任何虚拟机中总是一个64位二进制补码表示的有符号整数。
java虚拟机中还有一个只在内部使用的基本类型,returnAddress,被用来实现java程序中的finally子句。
java虚拟机的引用类型有三种引用类型,类类型,接口类型,数组类型。
java虚拟机中最基本的数据单元就是字,它的大小是由每个虚拟机实现的设计者决定。
在java虚拟机中,负责查找并装载类型的那部分被称为类装载器子系统。
栈帧由三部分组成:局部变量区,操作数栈和栈数据区。
java栈帧的局部变量区被组织为一个以字长为单位、从0开始计数的数组。int、float、reference、returnAddress在数组中占据一项,byte、short、char存入数组前被转化为int值,long和double占据两项。
任何java虚拟机实现的核心都是它的执行引擎,它的行为使用指令集来定义。
所有属于用户运行程序的线程都是在实际工作的执行引擎。
java线程运行于10个优先级中的一个,1最低10最高,最高优先级线程会得到大多是cpu时间。
没有规定优先级的线程采用时间分片方式,所有线程都会得到一些cpu时间。
java class文件
java class文件时堆Java程序二进制文件格式的精确定义。
一个class文件只能包含一个类或者接口。
java class文件是8位字节的二进制流。
class文件的主要部分:
magic(魔数):class文件前4个字节称为它的魔数0xCAFEBABE,作用在于能分辨出是否是java class文件。
minor_version和major_version:主次版本号
constant_pool_count和constant_pool:常量池入口
access_flags:展示了文件中定义的类和接口的几段信息,如指明是类还是接口,使用了哪种修饰符,抽象的还是公共的等。
this_class:一个对常量池的索引,入口为CONSTANT_Class_info表
super_class:常量池入口是一个指向该类超类全限定名的CONSTANT_Class_info入口。
interfaces_count和interfaces:在文件中由该类直接实现或者由接口所扩展的父接口的数量。
fields_count和fields:对该类或者接口所声明的字段的描述。fields_count指出序列中有多少个file_info表
methods_count和methods:对该类或者接口中所声明方法的描述。methods_count指出列表中有多少个method_info表
attributes_count和attributes:该文件中类或者接口所定义的属性的基本信息。
类型的生命周期
java虚拟机通过装载、连接和初始化一个java类型。
装载:把二进制的Java类型读入Java虚拟机中。
连接:把读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中去。分为三步验证、准备、解析。验证确保类型数据格式正确并适用于Java虚拟机使用;准备负责为类型分配所需内存;解析负责把常量池中的符号引用转换为直接引用。
初始化期间给类变量赋予适当的初始值。
解析可以再初始化之后进行。
任何一个类的初始化都要求他的所有祖先类预先被初始化。
一个接口的初始化,并不要求它的祖先接口被初始化。
所有的类变量初始化语句和类型的静态初始化器被收集到一起放到一个方法,称为类初始化方法。在class文件中被称为
实例化类的途径:
1.明确使用new操作符
2.调用Class或者java.lang.reflect.Constructor对象的newInstance方法
3.调用任何现有对象的clone方法
4.通过ObjectInputStream类的getObject方法反序列化
5.隐含实例化,保存命令行参数的String对象
6.隐含实例化,java虚拟机装载每一个类型会暗中实例化一个Class对象代表这个类型
7.隐含实例化,通过执行包含字符串连接操作符的表达式产生对象
连接模型
class文件把它所有的引用符号保存在一个地方——常量池。
每一个class文件有一个常量池,每一个被java虚拟机装载的类或者接口都有一份内部版本的常量池,被称为运行时常量池。
在运行的某个时刻,某个特定的符号引用要被使用,首先先被解析,解析过程根据符号引用查找实体,再把符号引用替换成一个直接引用,这个过程被称为常量池解析。
每一个常量池入口都只被解析一次,之后的访问使用第一次解析出的直接引用。
连接不仅仅包括符号引用替换成直接引用,还包括检查正确性和权限。
虚拟机实现可以预先解析所有的符号引用——早解析,也可以在访问每一个符号的最后时刻解析——迟解析。
forName()是动态扩展最直接的方法。
解析类型是数组解析的最终结果是一个Class实例。
解析任何指向非数组类或者接口的符号引用使用以下步骤:
1.装载类型或者任何超类型
2.检查访问权限
3.连接并初始化类型和任何超类
4.校验类型
5.准备类型
6.可选的步骤,解析类型
7.初始化类型
指向类型、类变量和类方法的直接引用可能是指向方法区的本地指针。
指向实例变量和实例方法的直接引用都是偏移量。
垃圾收集
除了释放不在被引用的对象,垃圾收集器还要处理堆碎块。
垃圾收集可以提高生产率,帮助程序保存完整性,但加大了程序负担,影响性能;程序员对安排CPU时间释放对象缺乏控制。
区分活动对象和垃圾的两个基本方法是引用计数和跟踪。
引用计数收集器,堆中每个对象都有一个引用计数,创建分配给一个对象时置1,任何其他变量被赋值为对这个对象的引用时加1,对对象的引用超过了生存期或者被设置一个新值时减1,为0被垃圾收集。当一个对象被收集时,它引用的对象都减1,好处可以很快的执行,坏处无法检测出循环。每次引用计数的增加减少都带来额外开销。
跟踪收集器,追踪从根结点开始的对象引用图,遇到的对象打上标记,没有标记的可以被收集。基本的追踪算法被称为“标记并清除”。
压缩收集器,移动对象减少堆碎块,把活动的对象越过空闲区滑动到堆的一端。
拷贝收集器,把所有的对象移动到一个新的区域,不再有标记和清除的区分,对于指定大小的堆需要两倍的内存。
按代收集的收集器,通过把对象按照寿命来分组,可以应用于拷贝算法和标记清除算法。
自适应收集器,监视堆中的情形,对应地调整为合适的垃圾收集技术。
渐进式垃圾收集器就是不试图一次性发现所有可回收对象,而是每次发现并回收一部分。
火车算法把成熟对象空间划分为固定长度的内存块,算法每次在一个块中单独运行。
在java语言里,一个对象可以拥有终结方法,这个方法是垃圾收集器在释放对象前必须运行的。
在垃圾收集器看开,对象都有三种状态,可触及的,可复活的,不可触及。
1.2之后可触及状态进行扩充,软可触及,弱可触及,影子可触及。
栈和局部变量操作
java虚拟机把所有具有相同字符顺序的字符串文字处理为同一个String对象。如果不同类中出现相同的字符串,类方法使用指令将同String对象压入各自的栈。
java虚拟机指令集的大多数指令只处理一种特定的类型。
类型转换
java虚拟机包括许多进行基本类型转换工作的操作码,这些执行转换工作的操作码后面没有操作数,转换的值从栈顶端获得。
涉及byte、short、char类型的运算操作首先会把这些值转换为int类型,然后对int类型进行运算,得到int类型结果。
把两个byte类型值相加,最后会得到int类型结果,需要得到int类型,需要显示转换。
整数运算
java虚拟机所支持的所有的整数类型,它们都是带符号的二进制补码数。
BigInteger类实例可以描述任意长度的整数。
逻辑运算
java虚拟机的逻辑操作主要针对int和long类型,按照通用的位模式进行处理。
浮点运算
java虚拟机中浮点运算基于32位float和64位double类型进行。
浮点数由符号、尾数、基数、指数四部分组成。
浮点数=符号X尾数X基数的指数次幂
符号位的值要么是1,要么是-1
尾数是一个正数,确定浮点数的有效位数
如果1/基数 <= 尾数 < 1 则称这个浮点数为规范化的浮点数。
一个规范化浮点数的尾数,它的小数点左边的数字一定为0,紧接小数点右边的数字一定不为0。不符合的这规则的称为非规范化的浮点数。
对象和数组
内存只能以对象的形式在垃圾收集器堆中分配。除非作为对象的一部分,否则不能为基本类型在堆中分配内存。
只有对象引用和基本数据类型可以在Java栈中以局部变量形式存在,java栈不能容纳对象。
对象不能作为局部变量声明,只有对象引用和基本类型可以。
方法的调用与返回
java程序设计提供两种基本方法,实例方法和类(或者静态)方法。
实例方法在被调用前需要一个实例,而类方法不需要。
实例方法使用动态(迟)绑定,而类方法使用静态(早)绑定。
虚拟机为每一个调用的Java(非本地)方法建立一个新的栈帧。
栈帧包括为方法的局部变量所预留的空间,该方法的操作数栈,特定虚拟机实现的其他所有信息。
线程同步
java所使用的的同步机制是监视器。
java中的监视器支持两种线程:互斥和协作。
通过对象锁来实现互斥,允许多个线程在同一个共享数据上独立而互不干扰地工作。
协作则通过Object类的wait方法和notify方法实现,允许多个线程为同一个目标而共同工作。
java栈种的数据是属于拥有该栈的线程私有的。
线程每加锁一次,计数器加1,释放所一次,计数器减1,为0完全释放。