Class类文件结构Java跨平台的基础各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode)是构成平台无关性的基石,也是语言无关性的基础。Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。 Class类的本质Class文件是一组以8位字节为基础单位的二进制流
任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,Class文件实际上它并不一定以磁盘文件的形式存在。 Class文件格式各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。 Class文件格式详解Class的结构不像XML等描述语言,由于它没有任何分隔符号,所以在其中的数据项,无论是顺序还是数量,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变。 1、魔数与Class文件的版本 每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。文件格式的制定者可以自由地选择魔数值,只要这个魔数值还没有被广泛采用过同时又不会引起混淆即可。
2、常量池 常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。与Java中语言习惯不一样的是,这个容量计数是从1而不是0开始的 3、访问标志 用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等
4、类索引、父类索引与接口索引集合 这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中 5、字段表集合 描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量。 6、方法表集合 描述了方法的定义,但是方法里的Java代码,经过编译器编译成字节码指令后,存放在属性表集合中的方法属性表集合中一个名为“Code”的属性里面。
7、属性表集合 存储Class文件、字段表、方法表都自己的属性表集合,以用于描述某些场景专有的信息。如方法的代码就存储在Code属性表中。 字节码指令Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,这类指令包括如下内容。 运算或算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。 类型转换指令可以将两种不同的数值类型进行相互转换, 创建类实例的指令new。 创建数组的指令newarray、anewarray、multianewarray。 访问字段指令getfield、putfield、getstatic、putstatic。 数组存取相关指令把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。 检查类实例类型的指令instanceof、checkcast。 操作数栈管理指令如同操作一个普通数据结构中的堆栈那样,Java虚拟机提供了一些用于直接操作操作数栈的指令,包括:将操作数栈的栈顶一个或两个元素出栈:pop、pop2。 控制转移指令控制转移指令可以让Java虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序,从概念模型上理解,可以认为控制转移指令就是在有条件或无条件地修改PC寄存器的值。控制转移指令如下。 方法调用指令invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。 方法返回指令是根据返回值的类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用。 异常处理指令在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现 同步指令有monitorenter和monitorexit两条指令来支持synchronized关键字的语义 类加载机制
概述类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个部分统称为连接(Linking) 注意: 也就是说,实际上NotInitialization的Class文件之中并没有ConstClass类的符号引用入口,这两个类在编译成Class之后就不存在任何联系了。 加载阶段虚拟机需要完成以下3件事情: 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。但从整体上看,验证阶段大致上会完成下面4个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为: public static int value=123; 那变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。表7-1列出了Java中所有基本数据类型的零值。 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程 类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。 类加载器系统的类加载器对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 双亲委派模型从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C 语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。 双亲委派模型过程 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器, 依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。 双亲委派模型好处 Java类随着它的类加载器一起具备了带有优先级的层次关系,保证java程序稳定运行。 自定义类加载器-----对类进行加密和解密1、DemoUser-----业务类 /** *类说明:我们要处理的业务类 */ public class DemoUser { private int id = 1; private String name = "mark"; @Override public String toString() { return "DemoUser [id=" id ", name=" name "]"; } } 2、XorEncrpt-----加密和解密的服务类 /** *类说明:加密和解密的服务类 */ public class XorEncrpt{ //异或操作,可以进行加密和解密 private void xor(InputStream in, OutputStream out) throws Exception{ int ch; while (-1 != (ch = in.read())){ ch = ch^ 0xff; out.write(ch); } } //加密方法 public void encrypt(File src, File des) throws Exception { InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(des); xor(in,out); in.close(); out.close(); } //解密方法 //加密后的class文件 public byte[] decrypt(File src) throws Exception { InputStream in = new FileInputStream(src); ByteArrayOutputStream bos = new ByteArrayOutputStream(); xor(in,bos); byte[] data = bos.toByteArray();; return data; } public static void main(String[] args) throws Exception { File src = new File("D:\\XiangXue\\vipLesson\\JVM\\code\\src\\" "vip-jvm\\bin\\com\\xiangxue\\ch03\\deencrpt\\DemoUserSrc.class"); File dest = new File("D:\\XiangXue\\vipLesson\\JVM\\code\\src" "\\vip-jvm\\bin\\com\\xiangxue\\ch03\\deencrpt\\DemoUser.class"); XorEncrpt demoEncryptUtil = new XorEncrpt(); demoEncryptUtil.encrypt(src,dest); System.out.println("加密完成!"); } } 3、CustomClassLoader-----自定义的类加载器 /** *类说明:自定义的类加载器 */ public class CustomClassLoader extends ClassLoader{ private final String name; private String basePath; private final static String FILE_EXT = ".class"; public CustomClassLoader(String name) { super(); this.name = name; } public void setBasePath(String basePath) { this.basePath = basePath; } //实际解密 private byte[] loadClassData(String name){ int x = 0 ; byte[] data = null; XorEncrpt demoEncryptUtil = new XorEncrpt(); // use x; int y = 1; try { String tempName = name.replaceAll("\\.","\\\\"); data = demoEncryptUtil.decrypt(new File(basePath tempName FILE_EXT)); } catch (Exception e) { e.printStackTrace(); } return data; } //自定义类加载器需要重写findClass方法 @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = this.loadClassData(name); return this.defineClass(name,data,0,data.length); } } 4、DemoRun-----运行测试类 public class DemoRun { public static void main(String[] args) throws Exception { CustomClassLoader demoCustomClassLoader = new CustomClassLoader("My ClassLoader"); demoCustomClassLoader.setBasePath("D:\\XiangXue\\vipLesson\\JVM\\code" "\\src\\vip-jvm\\bin\\"); Class<?> clazz = demoCustomClassLoader.findClass("com.xiangxue.ch03." "deencrpt.DemoUser"); System.out.println(clazz.getClassLoader()); Object o = clazz.newInstance(); System.out.println(o); //new User(xxx,yyyy,ddd);// } } Tomcat类加载机制同一个tomcat容器下的两个应用以及tomcat的lib目录中都有UserServiceImpl类,tomcat怎么 样保证类的隔离性?
Tomcat本身也是一个java项目,因此其也需要被JDK的类加载机制加载,也就必然存在引导类加载器、扩展类加载器和应用(系统)类加载器。 加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下) 加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下。 加载tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下,比如servlet-api.jar 每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。 栈桢运行时的栈桢结构
局部变量表 操作数栈 动态连接 方法返回地址 方法调用详解1、解析 调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析。 2、分派 a、静态分派 多见于方法的重载。 “Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。 示例: /** *类说明:解析 */ public class StaticDispatch{ static abstract class Human{} static class Man extends Human{ } static class Woman extends Human{} public void sayHello(Human guy){ System.out.println("hello,guy!");//1 } public void sayHello(Man guy){ System.out.println("hello,gentleman!");//2 } public void sayHello(Woman guy){ System.out.println("hello,lady!");//3 } public static void main(String[]args){ Human h1 = new Man(); Human h2 = new Woman(); StaticDispatch sr = new StaticDispatch(); sr.sayHello(h1); sr.sayHello(h2); } } 运行结果: b、动态分派 静态类型同样都是Human的两个变量man和woman在调用sayHello()方法时执行了不同的行为,并且变量man在两次调用中执行了不同的方法。导致这个现象的原因很明显,是这两个变量的实际类型不同。 示例: /** *类说明:动态分派 */ public class DynamicDispatch { static abstract class Human{ protected abstract void sayHello(); } static class Man extends Human{ @Override protected void sayHello() { System.out.println("hello,gentleman!"); } } static class Woman extends Human{ @Override protected void sayHello() { System.out.println("hello,lady!"); } } public static void main(String[]args){ Human h1 = new Man(); Human h2 = new Woman(); h1.sayHello(); h2.sayHello(); } } 运行结果: c、动态分派的实现 来源:https://www./content-4-635751.html |
|