.java文件编译成.class文件然后交给不同系统的虚拟机系统下载不同系统的虚拟机系统下载解释成不同的代码.那为

《深入理解java虚拟机》Java Class类文件结构及类加载机制 - CSDN博客
转自:http://blog.csdn.net/chjttony/article/details/7907261
一、Java Class类文件结构
Java语言从诞生之时就宣称一次编写,到处运行的跨平台特性,其实现原理是源码文件并没有直接编译成机器指令,而是编译成Java虚拟机可以识别和运行的字节码文件(Class类文件,*.class),字节码文件是一种平台无关的中间编译结果,字节码文件由java虚拟机读取,解析和执行,java虚拟机屏蔽了不同操作系统和硬件平台的差异性。
如今的java虚拟机已经称为一种通用平台,不但能够运行java语言,Groovy,JRuby,Jython等一大批动态语言也可以直接在Java虚拟机上运行,其原理也是这些动态语言的编译器将源码文件编译为和Java相同的字节码文件,这样Java虚拟机就可以像执行java语言一样执行这些动态语言了。
字节码class类文件是由一系列字节码命令组成,用于表示程序中各种常量、变量、关键字和运算符号的语义等等。Java的Class类文件是一组以8为字节为单位的二进制流,各个数据项严格按照顺序紧凑地排列在Class类文件之中,中间没有添加任何分隔符,当遇到需要占用8位字节以上空间的数据项时,按照高位在前的方式分割成若干个8位字节进行存储。
Java虚拟机规定,Class类文件格式采用类似C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表:
(1).无符号数:
属于基本类型的数据,以u1, u2, u4, u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码的字符串值。
由多个无符号数或其他表作为数据项构成的复合数据类型,所以表都习惯性地以“_info“结尾。表用于描述有层次关系的复合结构数据,整个Class文件本质就是一张表。
Java Class类文件结构如下:
minor_version
major_version
constant_pool_count
constant_pool
constant_pool_count-1
access_flags
this_class
super_class
interfaces_count
interfaces
interfaces_count
fields_count
field_info
fields_count
methods_count
method_info
methods_count
attributes_count
attribute_info
attributes
attributes_count
Class类文件没有任何分隔符,是严格按照这个结构表顺序排列,下面具体介绍各个名称含义:
(1).magic:
每个Class文件的头4个字节被称为魔数,它的唯一作用是用于确定这个文件是否为一个能被java虚拟机所接收的Class类文件,即用于判定文件是否是符合规范的java Class文件。虽然说后缀名“.class”可以表明文件是一个Class文件,但是文件后缀名是可以随意被改动的,基于安全的考虑,很多文件都通过魔数值来唯一确定文件类型,java的Class文件魔数是:0xCAFEBABE.
(2).minor_version和major_version:
每个Class文件的第5和第6个字节代表Class文件的次版本号,第7和第8个字节代表Class文件的主版本号。
Class文件的主、次版本号是由JDK决定的,JDK1.0~JDK1.1使用了45.0~45.3的版本号(45是主版本号,点”.“之后的是次版本号),从JDK1.1开始,每个大版本的JDK主版本号加1.
Class主、次版本号的一个作用时,高版本的Java虚拟机可以向前兼容,运行低版本JDK编译的Class字节码文件,而低版本的java虚拟机不能运行高版本JDK编译的Class字节码文件。当低版本的java虚拟机运行高版本JDK编译的Class字节码文件时,通常会报类似如下的异常:
JDK1.0~JDK1.1使用了45.0~45.3的版本号,JDK1.2使用了46.0~46.65535的版本号,JDK1.3使用了47.0~47.65535的版本号,JDK1.4使用了48.0~48.65535的版本号,JDK1.5使用了49.0~49.65535的版本号,JDK1.6使用了50.0~50.65535的版本号,JDK1.7使用51.0~51.65535的版本号。
在编译时可以通过指定-target参数来改变主版本号,如JDK1.6编译时如果没有给定target参数,则编译出来的Class文件的主版本号是50,如果给定”-target 1.4 -source 1.4”参数之后,则主版本将变为48,如果给定”-target 1.5 ”参数之后,则主版本将变为49。
(3). constant_pool_count和constant_pool:
constant_pool_count代表Class文件中常量池的数目,由于常量池的计数从1开始,因此常量池的容量是constant_pool_count-1。
第0项常量空出做特殊考虑,为了满足一些指向常量池的索引值在某些特定情况下需要表达“不指向任何一个常量池”的意思。
constant_pool常量池是Class类文件中出现的第一个表类型数据,常量池主要存放两大类常量:
a.字面量(Literal):包括文本字符串、final类型常量值。
b.符号引用(SymbolicReferences):包括类和接口的全限定名、字段的名称和描述符、方 法的名称和描述符。
(4). access_flags:
用于表示Class或接口层次的访问标志,即类或接口层面的访问控制信息,通常存储的信息包括:Class类文件是类、接口、枚举或是注解;是否定义为public类型;是否定义为abstract类型;类是否被定义为final等等。
(5). this_class、super_class和interfaces:
this_class类索引用于确定类的全限定名,super_class父类索引用于确定父类的全限定名,interfaces接口索引用于确定接口的全限定名,由于java中可以实现多个接口,因此使用interfaces_count来存储接口数量。
(6). field:
field_info字段表用于描述接口或者类中声明的变量,field字段包括了类级变量(静态变量)和实例级变量(成员变量),但不包括方法内部的局部变量。
fields_count字段数目表示Class文件中的类和实例变量总数,字段存放的信息包括:字段访问标志、是否静态、是否final、是否并发可见volatile、是否可序列化transient、数据类型、字段名称等等。
注意:字段表中不包含从父类或者接口中继承而来的字段,但是会添加原本代码中不存在的字段,例如this,以及内部类对外部类访问而自动添加的外部类实例字段等。
(7).method:
method_info方法表用于描述类或者接口中声明的方法,methods_count用于表示Class文件中方法总数,method方法存储了方法的访问标识、是否静态、是否final、是否同步synchronized、是否本地方法native、是否抽象方法abstract、方法返回值类型、方法名称、方法参数列表等信息。
方法的代码指令并没有直接存放在方法表中,而是存放着属性表中的方法表Code中。
注意:如果父类的方法在子类没有被重写,方法表中不会出现来自父类的方法信息,但是编译器会自动添加类构造器”&clinit&”方法和实例构造器”&init&”方法。
Java编译器的方法特征签名只包括:方法名称、参数顺序和参数类型,不包括方法返回值类型,因此java的方法重载不能通过方法的返回值类区别,但是在Class文件中,方法特征签名包括方法的返回值类型,因此Class文件中可以共存两个名称和参数完全相同而返回值类型不同的方法。
(8). attribute:
attribute_info属性表是Class文件格式中最具扩展性的一种数据项目,用于存放field_info字段表、method_info方法表以及Class文件的专有信息,属性表不要求各个属性有严格顺序,只要求不与已有的属性名字重复即可,属性表中存放的常用信息如下:
Java代码编译后的字节码指令
ConstantValue
final关键字定义的常量值
Deprecated
类、方法表、字段表
被声明为Deprecated的字段或方法
方法抛出的异常
InnerClasses
内部类列表
LineNumberTable
java源码行号和字节码指令的对应关系
LocalVariableTable
方法的局部变量描述
SourceFile
源文件名称
类、方法表、字段表
标识方法或字段为编译器自动生成
Class文件是二进制文件,使用支持二进制的文本编辑器打开之后显示的全是二进制数据,非常的不便于阅读和理解,使用JDK提供的javap工具可以简单将Class反编译,编译理解Class文件的结构,例子如下:
public class Test {
public int getNum(int i) {
return i + 1;
javap反编译之后的字节码文件:
public class Test extends java.lang.Object
SourceFile: &Test.java&
minor version: 0
major version: 50
Constant pool:
const #1 = class
const #2 = Asciz
const #3 = class
const #4 = Asciz
java/lang/O
const #5 = Asciz
//实例构造器
const #6 = Asciz
//void返回类型
const #7 = Asciz
//属性表Code属性
const #8 = Method
//方法特征签名
java/lang/Object.&&init&&:()V
const #9 = NameAndType
方法名称和返回值&&init&&:()V
const #10 = Asciz
LineNumberT
//属性表源码行号和字节码指令对应表
const #11 = Asciz
LocalVariableT
//属性表方法局部变量表
const #12 = A
//Test类实例对象本身
const #13 = Asciz
//对象类型,Test类
const #14 = Asciz
//方法名称
const #15 = Asciz
//方法参数列表为一个int类型和返回值为int类型
const #16 = A
//参数名称i
const #17 = Asciz
//参数类型int
const #18 = Asciz
const #19 = Asciz
//构造函数(默认构造方法)
public Test();
//属性表Code属性
Stack=1, Locals=1, Args_size=1
invokespecial
#8; //Method java/lang/Object.&&init&&:()V
LineNumberTable:
LocalVariableTable:
//属性表方法局部变量表
//自定义方法
public int getNum(int);
Stack=2, Locals=2, Args_size=2
LineNumberTable:
LocalVariableTable:
二、类加载机制
Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程。
在加载阶段,java虚拟机需要完成以下3件事:
a.通过一个类的全限定名来获取定义此类的二进制字节流。
b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。
c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口。
Java虚拟机的类加载是通过类加载器实现的, Java中的类加载器体系结构如下:
(1).BootStrap ClassLoader:启动类加载器,负责加载存放在%JAVA_HOME%\lib目录中的,或者通被-Xbootclasspath参数所指定的路径中的,并且被java虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库,即使放在指定路径中也不会被加载)类库到虚拟机的内存中,启动类加载器无法被java程序直接引用。
(2).Extension ClassLoader:扩展类加载器,由sun.misc.Launcher$ExtClassLoader实现,负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
(3).Application ClassLoader:应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径classpath上所指定的类库,是类加载器ClassLoader中的getSystemClassLoader()方法的返回值,开发者可以直接使用应用程序类加载器,如果程序中没有自定义过类加载器,该加载器就是程序中默认的类加载器。
注意:上述三个JDK提供的类加载器虽然是父子类加载器关系,但是没有使用继承,而是使用了组合关系。
从JDK1.2开始,java虚拟机规范推荐开发者使用双亲委派模式(ParentsDelegation Model)进行类加载,其加载过程如下:
(1).如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。
(2).每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。
(3).如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。
双亲委派 模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。双亲委派模式的实现:
protected synchronized Class&?& loadClass(String name, Boolean resolve) throws ClassNotFoundException{
//首先检查请求的类是否已经被加载过
Class c = findLoadedClass(name);
if(c == null){
if(parent != null){//委派父类加载器加载
c = parent.loadClass(name, false);
else{//委派启动类加载器加载
c = findBootstrapClassOrNull(name);
}catch(ClassNotFoundException e){
//父类加载器无法完成类加载请求
if(c == null){//本身类加载器进行类加载
c = findClass(name);
if(resolve){
resolveClass(c);
若要实现自定义类加载器,只需要继承java.lang.ClassLoader&类,并且重写其findClass()方法即可。java.lang.ClassLoader&类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个&Java&类,即&java.lang.Class&类的一个实例。除此之外,ClassLoader&还负责加载&Java&应用所需的资源,如图像文件和配置文件等,
ClassLoader中与加载类相关的方法如下:
getParent()
返回该类加载器的父类加载器。
loadClass(String name)
加载名称为&二进制名称为name&的类,返回的结果是&java.lang.Class&类的实例。
findClass(String name)
查找名称为&name&的类,返回的结果是&java.lang.Class&类的实例。
findLoadedClass(String name)
查找名称为&name&的已经被加载过的类,返回的结果是&java.lang.Class&类的实例。
resolveClass(Class&?& c)
链接指定的&Java&类。
注意:在JDK1.2之前,类加载尚未引入双亲委派模式,因此实现自定义类加载器时常常重写loadClass方法,提供双亲委派逻辑,从JDK1.2之后,双亲委派模式已经被引入到类加载体系中,自定义类加载器时不需要在自己写双亲委派的逻辑,因此不鼓励重写loadClass方法,而推荐重写findClass方法。
在Java中,任意一个类都需要由加载它的类加载器和这个类本身一同确定其在java虚拟机中的唯一性,即比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,只要加载它的类加载器不相同,那么这两个类必定不相等(这里的相等包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof关键字的结果)。例子代码如下:
package com.
public class ClassLoaderTest {
public static void main(String[] args)throws Exception{
//匿名内部类实现自定义类加载器
ClassLoader myClassLoader = new ClassLoader(){
protected Class&?& findClass(String name)throws ClassNotFoundException{
//获取类文件名
String filename = name.substring(name.lastIndexOf(“.”) + 1) + “.class”;
InputStream in = getClass().getResourceAsStream(filename);
if(in == null){
throw RuntimeException(“Could not found class file:” + filename);
byte[] b = new byte[in.available()];
return defineClass(name, b, 0, b.length);
}catch(IOException e){
throw new ClassNotFoundException(name);
Object obj = myClassLoader.loadClass(“com.test.ClassLoaderTest”).newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.test. ClassLoaderTest);
输出结果如下:
com.test.ClassLoaderTest
之所以instanceof会返回false,是因为com.test.ClassLoaderTest类默认使用Application ClassLoader加载,而obj是通过自定义类加载器加载的,类加载不相同,因此不相等。
类加载器双亲委派模型是从JDK1.2以后引入的,并且只是一种推荐的模型,不是强制要求的,因此有一些没有遵循双亲委派模型的特例:
(1).在JDK1.2之前,自定义类加载器都要覆盖loadClass方法去实现加载类的功能,JDK1.2引入双亲委派模型之后,loadClass方法用于委派父类加载器进行类加载,只有父类加载器无法完成类加载请求时才调用自己的findClass方法进行类加载,因此在JDK1.2之前的类加载的loadClass方法没有遵循双亲委派模型,因此在JDK1.2之后,自定义类加载器不推荐覆盖loadClass方法,而只需要覆盖findClass方法即可。
(2).双亲委派模式很好地解决了各个类加载器的基础类统一问题,越基础的类由越上层的类加载器进行加载,但是这个基础类统一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类加载器进行类加载。为了解决这个问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器。
JavaEE只是一个规范,sun公司只给出了接口规范,具体的实现由各个厂商进行实现,因此JNDI,JDBC,JAXB等这些第三方的实现库就可以被JDK的类库所调用。线程上下文类加载器也没有遵循双亲委派模型。
(3).近年来的热码替换,模块热部署等应用要求不用重启java虚拟机就可以实现代码模块的即插即用,催生了OSGi技术,在OSGi中类加载器体系被发展为网状结构。OSGi也没有完全遵循双亲委派模型。javac.exe、 java.exe、 java虚拟机三者之间的区别与联系是什么?
[问题点数:50分,结帖人prince_lang]
javac.exe、 java.exe、 java虚拟机三者之间的区别与联系是什么?
[问题点数:50分,结帖人prince_lang]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关推荐:
2008年10月 Java大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。java虚拟机学习笔记 - 设计前沿
java虚拟机学习笔记
文章出处:网上收集 作者:未知 发布时间:
1.编译顺序:&&&&&&&&&&&&&&&& 编译器&&&&&&&&&&&&&&&&&&&& 虚拟机&&&&& 虚拟机&&&&&&&&& java源文件*.java-------&字节码*.class------&类装载器---&执行引擎一个.class文件只能包含一个类或接口。因此.java文件中定义了多少类,编译时就会生成多少.class文件(内部类不算)。2.java程序可以选择两种方式访问底层系统,由程序员选择:(1).通过java程序调用javaapi调用本地方法,访问底层系统,与平台无关。(2).通过java程序直接调用本地方法,访问底层系统与平台相关。本地方法即操作系统提供的方法。3.类装载器:装载java编译器编译好的字节码*.class和java api的字节码到方法区。java有两种类装载器:(1).启动类装载器:系统唯一,属于虚拟机的一部分,用特定语言编写(与虚拟机体层语言相通)使用默认方式装载类,主要用来装载核心类库。(2).用户自定义类装载器:可有任意多个,用java编写,属于java应用程序的一部分,能被编译成字节码,并被虚拟机所装载。一个装载器装载一个类及其该类所调用的一切类,使他们相互联系,并形成一个命名空间(name space),每一个类装载器对应一个命名空间。即java中名字空间的原理。类装载器成线形排列,自底向上,顶部为启动类装载器。除启动类装载器外,其他类装载器都由用户实例化,用来装载不同的类。当要装载一个类时,底部的装载器试图将该类交给父装载器装载,而该父类又试图交给他的父类装载,一直向上,直到启动类装载器。若父类装载器无法装载,则交给子类装载器装载,子类装载能装载的部分,将余下部分交给他的子类,直到底部。如:装载器a,b,c,d,e,f,启动a---&b---&c---&d---&e---&f---&启动当有一个类fun需要被装载时,他会一直上溯到顶部即启动类装载器。如果启动类装载器无法装载fun则交给f装载,f装载能装载的部分,将其余部分交给e,然后一直这样下去。如上所述,运行过程中每个类装载器装载的类形成一个运行时包,同一运行时包里的类可以互相访问,但不能访问包外部的类。4.虚拟机的生命周期:每个java程序都有自己的虚拟机实例,随着程序的产生和消亡而产生与消亡。5.java程序运行时的内存结构:程序空间分为方法区,堆,java栈,本地方法栈。(1)方法区存放装载的类数据信息包括:基本信息:每个类的全限定名。每个类的直接超类的全限定名(可约束类型转换)。该类是类还是接口。该类型的访问修饰符。直接超接口的全限定名的有序列表。每个已装载类的详细信息:运行时常量池:存放该类型所用的一切常量(直接常量和对其他类型,字段,方法的符号引用),它们以数组形式通过索引被访问,是外部调用与类联系及类型对象化的桥梁。它是类文件(字节码)常量池的运行时表示。(还有一种静态常量池,在字节码文件中)。字段信息:类中声明的每一个字段的信息(名,类型,修饰符)。方法信息:类中声明的每一个方法的信息(名,放回类型,参数类型,修饰符,方法的字节码和异常表)。静态变量到类classloader的引用:即到该类的类装载器的引用。到类class的引用:虚拟机为每一个被装载的类型创建一个class实例,用来代表这个被装载的类。(2)堆存放所有生成的对象及对象的实例变量。(3)java栈以帧的形似存放本地方法的调用状态(包括方法调用的参数,局部变量,中间结果等)。每调用一个方法就将对应该方法的方法帧压入java栈,成为当前方法帧。当调用结束(返回)时,就弹出该帧。编译器将原代码编译成字节码(.class)时,就已经将各种类型的方法的局部变量,操作数栈大小确定并放在字节码中,随着类一并装载入方法区。当调用方法时,通过访问方法区中的类的信息,得到局部变量以及操作数栈的大小。java栈帧(即方法帧)由局部变量区,操作数栈,帧数据区组成。局部变量区为一个以字为单位的数组,每个数组元素对应一个局部变量的值。调用方法时,将方法的局部变量组成一个数组,通过索引来访问。若为非静态方法,则加入一个隐含的引用参数this,该参数指向调用这个方法的对象。而静态方法则没有this参数。因此,对象无法调用静态方法。操作数栈也是一个数组,但却是通过栈操作来访问。所谓操作数是那些被指令操作的数据。当需要对参数操作时如a=b+c,就将即将被操作的参数压栈,如将b和c压栈,然后由操作指令将他们弹出,并执行操作,此处由iadd指令将b和c弹出并相加,然后压入操作数栈(一系列均由iadd执行)然后由i_storex指令将结果弹出,存到索引x指向的局部变量区数组内(此处索引x指向局部变量a)。虚拟机将操作数栈作为工作区。帧数据区处理常量池解析,异常处理等。(4)本地方法栈:与调用的本地方法的语言相关,如调用的是一个c语言方法则为一个c栈。本地方法可以回调java方法。若有java方法调用本地方法,虚拟机就运行这个本地方法。在虚拟机看来运行这个本地方法就是执行这个java方法,如果本地方法抛出异常,虚拟机就认为是这个java方法抛出异常。(5)执行程序时,通过对象的引用在方法区中查找装载的类,若还没有装载,则查找字节码(类名.class),并将其装载入方法区。在执行过程中,虚拟机会将对象的符号引用(即对象名)替换为直接的指针,以提高访问速度。(6)因此,大体可以表述为:方法区:存储类包括接口的各种信息,字节码装载到此处。java栈:存储被调用的方法的各种信息,只有调用该方法时,才会将该方法帧压入java栈。堆:存储对象的信息,包括对象的实例变量,但不包括对象的方法。只有调用对象的方法时,才将方法帧压入java栈中。6.java数据类型:数值类型:浮点类型:float double整数类型:byte,short,int,long,char(int和char可以互换)。引用类型:类类型,接口类型,数组类型。7.java的引用类型:引用与指针。引用代表被引用的对象,它只是引用对象的代表,并不占用内存,也不能修改。如引用变量没有引用对象,则该引用变量=null。指针存放对象的地址,它是一个变量,可以被修改,和其他变量一样,占用内存。8.方法区所有线程共享方法区,但为满足线程安全,方法区中每一个类必须被设定为临界资源,即同一时刻某一个类只能被一个线程访问。9.类标识:由于一个程序可以多次装载同一个类且该类可以存在于不同的名字空间中(即可由不同的装载器装载),因此必须将装载该类的装载器的标识加上,才能唯一标识一个类。10.对象对象实例变量存储在堆中,对象符号引用则在常量池,方法属性表等可能出现的地方。通过对象的引用可以访问对象的实例数据和创建该对象的类的数据。对象的引用指向堆中的对象。实例结构有两种,见书本98页。当调用对象的方法时,需要进行动态绑定。即,不能根据对象来确定需要调用的方法,而是根据对象的类数据来确定需要调用的方法。此时,也需要通过对象的引用来访问类数据。动态绑定就是在运行时才绑定,而不是在编译时绑定。11.数组数组也是类的对象。具有相同类型和维数的数组属于同一个类(不管长度只看维数)。数组的长度属于对象实例。多维数组也是一维数组。如二维数组,即为一个一维数组,该一维数组的每个元素是一个数组的引用。数组和普通对象一样也存储在堆中。数组名为数组的引用,通过索引即数组标号来访问数组内容。12.异常在java栈帧的帧数据区内保存有针对该方法的异常表的引用。异常表记载了该方法的字节码(*.class)受catch子句保护的范围(即try子句里的字节码)。当某个方法抛出异常时,虚拟机在对应的异常表中寻找匹配的catch子句,并将控制权交给catch子句中的代码。13.java执行引擎实现平台无关性,以java方法帧里的操作数栈为中心,将局部变量数组当作cpu的寄存器。每操作一个数据都要压人操作数栈,然后返回至局部变量区。java虚拟机规定强类型转换,即低精度可以隐式转换到高精度,高精度必须强制转换到低精度。14.线程线程即存在于进程中的某个执行体。每个线程必须遵守对象锁定,线程等待和通知。对象锁定使线程互斥的访问对象资源。等待和通知则是遵守线程合理调度以达到同一个目的。java对象通过指令集达到上锁目的,同过继承object类的wait(),notify(),notifyall()方法来等待和通知。当某个线程调用某个对象的wait()方法时,该线程被阻塞,并加入到该对象的线程阻塞队列中,直到另一个线程调用同一对象的通知方法,才能唤醒阻塞队列中的线程。15.常量池常量池用来存放类型的各种信息,包括类型的各种直接常量,和对其他类型,字段,方法的符号引用。常量池分为两种,存储在.class字节码中的常量池和存储在方法区中的运行时常量池。常量池以入口形式(类似于中断向量表)出现,每个入口都指向一个表,表中存储常量的信息。但从常量池的入口的标志位就可以判断对应的表中存储的常量类型。常量池入口以一个标志位开始,该标志位指示该常量的类型。每个入口对应一个表,该表以符号_info结尾,表中存放常量的压缩形式。常量池除了存放直接常量外还容纳如下几种符号引用:类和接口的全限定名。字段名称和描述符(该描述符是一个指示字段类型的字符串。字段是一个类或接口的类变量或实例变量)。方法名称和描述符(该描述符指示方法返回类型,参数类型,数量,顺序)。运行时,虚拟机用常量池的全限定名和方法,字段的描述符来建立类与类的关系。常量池仅仅是一个引用和描述符的集合,并不接受任何赋值操作。所有对象的创建,方法和类变量的调用均要从常量池中获取信息,但实例变量的调用从堆里获得。(猜想)符号引用是由虚拟机解析后得到具体的地址来使用。常量池解析就是将常量池中的符号引用替换成直接引用。当要使用某个类的方法或字段时,首先从常量池中找到该方法或字段的符号引用,然后进行解析,找到其物理地址。把代码中出现的各种符号引用,类与类的联系,进行常量池解析,叫做动态连接。16.常量池结构常量池由很多狠多的单元组成,每一个单元都形如(入口|内容),访问常量池单元时通过索引找到入口,然后访问其内容。但有时单元的内容也可能是一个常量池入口(比如类或接口的常量池单元,入口包含该类的符号引用即constant_class_info,而内容则指向一个constant_utf8_info的常量池单元,该单元里存放了该类的全限定名)。而直接常量如int,float等,内容处就是常量的值。17.方法区的结构方法区存储所有关于类型,接口的信息。方法区包含:常量池:存储类型的直接常量和所有的字段,方法,其他类型的符号引用(仅仅是引用,并不存放具体信息)。字段信息:所有声明的字段(包括字段名,类型,修饰符)。方法信息:所有定义的方法(包括方法名,返回类型,修饰符,方法的字节码,方法栈帧的大小,方法的异常)。类变量信息:虚拟机在方法区中为所有类变量分配空间,以后的初始化,赋值等操作也在方法区中进行,以便为所有类实例共享。为提高访问速度,虚拟机在方法区中为每个非抽象类设置了一个方法表,该表是一个数组,每个元素是一个方法的直接引用。当类的对象调用方法时,就在方法表中搜索(抽象类没有实例,所以不用调用方法,所以没有方法表)。18.堆堆存放类的实例和数组(包括实例变量,指向对应方法区中类数据的引用)。19.一个例子class test{public static void main(string args[]){string a=new string(&hello&);string b=new string(&hello&);string c=&hello&;string d=&hello&;}}则a==b返回false,c==d返回ture。因为:==比较双方是否是同一个对象。首先:string a=new string(&hello&)string b=new string(&hello&)a和b分别各自新建了hello的对象和引用变量,即在堆中有两个hello,他们各自的引用是a和b。而:string c=&hello&string d=&hello&先建立一个字符串类实例hello,再建立两个字符串引用变量c和d,然后让c和d都指向开始建立的hello实例。因此c和d指向的是同一个对象。
??????????????

我要回帖

更多关于 虚拟机xp系统下载 的文章

 

随机推荐