企业网站的推广阶段和特点门户网站的种类
企业网站的推广阶段和特点,门户网站的种类,怎么用wordpress建立自己的网站吗,学做电商的网站有哪些1.引言java源文件经过编译后生成字节码class文件#xff0c;需要经过虚拟机加载并转换成汇编指令才能执行#xff0c;那么虚拟机是如何一步步加载这些class文件的对于java程序员是完全透明的#xff0c;本文尝试全面分析jvm类加载机制。2.思考开始之前我们来简单思考一下需要经过虚拟机加载并转换成汇编指令才能执行那么虚拟机是如何一步步加载这些class文件的对于java程序员是完全透明的本文尝试全面分析jvm类加载机制。2.思考开始之前我们来简单思考一下如果让你来写虚拟机类加载你觉得要怎么做首先肯定有一个加载过程虚拟机要读取class字节码。其次为了保证虚拟机的安全性需要对输入做校验只有校验通过了才能继续执行程序设计总是这样才能保证系统安全稳定。再次校验通过后将字节码转换成类对象。最后将类对象建立全局索引方便引用。如果把类加载也当成一个工程子模块从逻辑上看我们上面的分析没有什么问题但工程实践经验表明实际情况肯定要复杂一些因为随着深入总有新问题产生至于复杂多少需要我们继续深入分析。3.类的生命周期类从被加载到jvm内存开始到卸载出内存需要经过7个阶段加载Loading、验证Verification、准备Preparation、解析Resolution、初始化Initialization、使用Using和卸载Unloading。类生命周期的七个阶段并非串行执行比如在进行验证时与此同时准备阶段或解析阶段已经开始了阶段是相互嵌套并行执行的只是按照逻辑分类可以这样进行区分。又比如正常情况下解析阶段过后是初始化阶段但为了支持java语言的运行时绑定也成为动态绑定或晚期绑定在初始化阶段之后才开始解析阶段。类的主动引用和被动引用什么情况下开始类加载呢一般下面四种情况必须立即进行类初始化工作如果类加载没做自然也必须立马做遇到new、getstatic、putstatic、invokestatic这4条字节码指令时如果类没有进行过初始化则需要先触发初始化。对类进行反射调用时。当初始化一个类时如果父类还没初始化则需要先初始化父类。包含main方法的主类当虚拟机启动时需要初始化该主类。以上4种情况称为对类的主动引用。除了以上4种情况其他对类的引用被称为被动引用case1:子类引用父类的的静态字段只会触发父类的初始化而不会触发子类初始化。public class SuperClass {public static int value 123; static {System.out.println(SuperClass init!);}
}
public class SubClass extends SuperClass {static {System.out.println(SubClass init!);}
}
public class NotInitialization {public static void main(String[] args) {System.out.println(SubClass.value);}
}上述代码运行之后最后输出的是“SuperClass init!”。case2:通过数组定义来引用类不会触发此类的初始化public class NotInitialization {public static void main(String[] args) {SuperClass[] superArray new SuperClass[10];}
}这段代码并不会触发SuperClass初始化即不会输出“SuperClass init!”。注这段代码会触发[LSuperClass初始化这个类代表SuperClass一维数组相对c/cjava对一维数组的封装抱枕了安全性当数组发生越界时将抛出java.lang.ArrayIndexOutOfBoundsException。case3:常量在编译阶段会存入调用类的常量池中本质上没有直接引用到定义常量的类因此不会触发定义常量的类的初始化。public class ConstClass {public static final String HELLOWORLD Hello World; static {System.out.println(ConstClass init!);}
}
public class NotInitialization {public static void main(String[] args) {System.out.println(ConstClass.HELLOWORLD);}
}对常量ConstClass.HELLOWORLD的引用实际被转换成NotInitialization类自身常量池的引用。接口加载过程差异点接口和类加载过程略有不同根本差异点在于当一个类初始化时要求其父类全部已经初始化过了但在一个接口初始化时并不要求其父接口全部初始化只有在真正使用到父接口时才会初始化。4.类加载过程类加载过程包含5个阶段加载、验证、准备、解析和初始化。下面来分析一下这5个阶段JVM都做了什么。加载阶段加载阶段需要完成3个事通过一个类的全限定名来获得定义此类的二进制字节流。将这个字节流所代表的静态存储结构转换成方法区的运行时数据结构。在堆中生成一个代表这个类的java.lang.Class对象作为方法区数据的访问入口。虚拟机规范并没有要求二进制字节流要从哪里获取也就是说在加载阶段是开了口子的很开放的富有创造性的程序员在这个舞台玩出了各种花样比如字节流获取可以从从ZIP获取最终形成了jar、war等格式。从网络中获取典型场景是Applet。从其他文件生成比如jsp。运行时计算典型场景就是动态代理技术用ProxyGenerator.generateProxyClass来为特定接口生成*$Proxy代理类的二进制字节流。还有其他方式只有你想不到的。相对于类加载其他阶段的透明性加载阶段是程序员可控性最强的阶段因为加载阶段可以使用系统提供的类加载器也可以用户自定义类加载器我们在下文还会详细说明Java的类加载器。验证阶段验证阶段属于连接阶段的第一步是出于虚拟机自身安全考虑确保二进制字节流包含的信息符合虚拟机的要求。这也说明了Java语言是相对安全的语言使用纯粹的Java代码无法做到诸如访问数据边界以外的数据将一个对象转换成一个未知类型跳转到不存在的代码行之类的行为。验证阶段一般需要完成4个阶段的校验过程文件格式校验、元数据校验、字节码校验和符号引用校验。文件格式校验文件格式校验主要是完成语法校验即检查二进制字节流是否符合Class文件格式规范目标是保证输入的字节流能正确地解析并存储于方法区之内格式上符合描述Java类型信息的要求。校验项具体包含是否以魔数0xCAFEBABE开头。主次版本号是否在虚拟机处理范围。常量池常量是否有不被支持的常量类型即检查常量tag标志。CONSTANT_Utf8_info型常量中是否有不符合UTF8编码的数据。Class文件中各个部分和文件本身是否有被删除的或被附加的其他信息。...经过了这层验证字节流才会进入方法区内存储。我们也可以看到验证阶段其实是和加载阶段交织在一起的。元数据校验元数据校验阶段是对字节码信息进行语义分析以保证数据符合Java语言规范比如是否继承了一个不允许被继承的父类。具体验证点包含这个类是否有父类因为除了java.lang.Object外所有类都有父类。这个类是否继承了final修饰的类。如果这个类不是抽象类那么是否实现了其父类和接口之中要求实现的所有方法。类中的字段、方法是否与父类产生了矛盾比如是否覆盖了父类的final方法出现了不符合规范的方法重载等等。...元数据阶段主要是完成数据类型校验。字节码校验字节码验证阶段将对类的方法体数据流和控制流进行验证分析是整个验证阶段最复杂的阶段。这个阶段保证方法在运行时不会出现危害虚拟机安全的行为。比如需要做保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作比如不会出现操作数栈放置了一个int类型的数据使用时却按long类型来来载入本地变量表中。保证跳转指令不会跳转到方法体以外的字节码指令上。保证方法体内的类型转换是有效的比如允许把子类型赋值给父类数据类型但不允许把父类对象类型赋值给子类数据类型。...但方法体内逻辑校验无法做到绝对可靠即不能指望校验程序准确地检查出程序能否在有限时间之内结束运行。符号引用校验符号引用校验可以看做是对类自身以外的信息进行匹配性校验发生时机是虚拟机将符号引用转换成直接引用时。校验内容包含符号引用中通过字符串描述的全限定名能否找到对应的类。在指定类中是否存在符号方法的字段描述符以及简单名称所描述的方法、字段。符号引用中类、字段和方法的访问性是否允许当前类的访问。...符号引用验证的目的是保证解析动作能正常执行如果没有通过符号引用验证将抛出java.lang.IncompatibleClassChangeError异常的子类比如java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。以上是验证阶段校验的内容。准备阶段准备阶段正式为类变量分配内存并设置类变量的初始值这些变量将在方法区中进行分配也就是准备阶段分配的是类变量并非实例变量。准备阶段初始化类变量零值以下是基本类型的零值举个例子假设一个类变量定义如下public static int value 123; 那么value在准备阶段初始化value 0而非123那什么时候会变成123呢把value变成123的是putstatic指令是存放在类构造器clinit()方法中而类构造器方法在初始化阶段才会执行。但也存在特殊情况类变量赋值不是0的情况比如在类中定义常量如果类字段的字段表属性表中存在ConstantValue属性则在准备阶段就会赋值。public static final int value 123; 解析阶段解析阶段是虚拟机将常量池内的符号引用Symbolic References替换为直接引用(Direct References)的过程。符号引用在符号引用验证中提到过它以CONSTANT_Class_infoCONSTANT_Fieldref_info、CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info等类型的常量出现。符号引用和直接引用的差别在于符号引用是以一组符号来描述所引用的目标符号可以是任何形式的字面量只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内部布局无关引用的目标不一定已经加载到内存中。直接引用是可以直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。解析什么时候发生呢虚拟机并未明确指定一般虚拟机实现会根据需要来判断解析可能发生在类被加载器加载时就对常量池中的符号引用进行解析亦或等到一个符号引用将要使用前才去解析。此外对同一个符号的多次解析请求是很常见的为了避免重复解析虚拟机实现可能会对第一次解析的结果缓存即在运行时常量池中记录直接引用并把常量标识为已解析状态。解析动作主要针对类、接口、字段、类方法、接口方法四类符号引用。类或接口的解析过程假设当前代码所处的类是D如果要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用那虚拟机完成整个解析的过程需要包含以下3个步骤如果C不是数组类型那虚拟机将会把代表N的全限定名传递给D的类加载器去加载这个类C在加载过程中由于元数据、字节码验证的需要可能触发其他类的加载过程。如果C是数组类型并且数组元素是一个对象类型以[Ljava.lang.Integer为例那将会按照第一点的规则加载数组元素类型。虚拟机会生成一个代表此数组维度和元素的数组对象。如果上述步骤没有出现异常那么C在虚拟机中已经成为了一个有效的类或接口了。在解析前还需要检验D是否具备对C的访问权限如果没有访问权限将抛出java.lang.IllegalAccessError。字段解析解析一个未被解析过的字段前首先需要对字段表内class_index(即字段的类索引)的CONSTANT_Class_info符号引用进行解析也就是或在进行字段解析之前需要先完成类或接口的符号解析。假设一个需要解析的字段所属的类或接口为C虚拟机规范要求按如下步骤对C后序字段进行搜索如果C本身就包含了简单名称和字段描述符都与目标相匹配的字段则返回这个字段的直接引用查找结束。如果在C中实现了接口将会按照继承关系从上到下递归搜索各个接口和它的父接口如果接口中包含简单名称和字段描述符都与目标相匹配的字段则返回这个字段的直接引用查找结束。如果C不是java.lang.Object将会按照继承关系从上往下递归搜索其父类如果在父类中包含了简单名称和字段描述符都与目标相匹配的字段则返回这个字段的直接引用查找结束。否则查找失败抛出java.lang.NoSuchFieldError异常。如果在父类和接口中存在同名字段会发生什么呢如果是这样情况编译器将拒绝编译比如下面这种情况public class FieldResolution {interface Interface0 {int a 0;} interface Interface1 extends Interface0 {int a 1;} interface Interface2 {int a 2;}static class Parent implements Interface1 {public static int a 3;} static class Sub extends Parent implements Interface2 {public static int a 4;} public static void main(String[] args) {System.out.println(Sub.a);}
}若将Sub静态成员变量publc static int a 4注释掉编译器将返回“The field Sub.A is ambiguous”。下面我们将类方法解析和接口方法解析两者是分开的。类方法解析类解析和字段解析一样需要先解析出类方法表的class_index索引所属类或接口的符号引用。如果类或接口符号引用接口成功我们依然用C来表示类或接口接下来将按如下步骤进行搜索如果发现C是一个接口将抛出java.lang.IncompatibleClassChangeError异常。在类C中查找是否有简单名称和描述符都与目标相匹配的方法如果有则返回这个方法的直接引用查找结束。在类C的父类中递归查找是否有简单名称和描述符都与目标相匹配的方法如果有则返回方法的直接引用查找结束。在类C的接口列表和它们的父接口中递归查找是否有简单名称和描述符和目标相匹配的方法如果查找到则说明类C是一个抽象类将抛出java.lang.AbstractMethodError异常。若以上未查询到则宣告查找失败抛出java.lang.NoSuchMethodError。查找结束若成功返回还需要对方法权限进行校验。接口方法解析与类方法解析类似步骤如下C是否是类如果是则抛出java.lang.IncompatibleClassChangeError异常。在接口中查找。在接口的父接口中递归查找。若以上未查询到则宣告查找失败抛出java.lang.NoSuchMethodError。以上是解析阶段所做的工作。初始化阶段类初始化阶段是类加载过程的最后一步到了初始化阶段才真正执行类中定义的字节码。在准备阶段变量已经赋过一次初始值初始化阶段将执行类构造器clinit()方法的过程。clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}块中的语句结合产生的。编译器收集的顺序是由语句在源文件中出现的顺序决定的比如静态语句块只能访问定义在静态语句块之前的变量。clinit()方法和类的构造方法不同并不需要显示的调用父类构造器虚拟机保证在子类clinit()方法执行之前父类构造器先执行因此在虚拟机中第一个执行类构造器的类为java.lang.Object。clinit()方法对于类或接口来说并不是必须的。虚拟机会保证一个类的clinit()方法在多线程环境中被正确地加锁和同步。5.类加载器ClassLoader上一节我们在一开始就聊到虚拟机允许用户自定义类加载器这就给java语言带来了很大的灵活性类加载器可以说是Java语言的一项创新也是Java语言流行的重要原因之一。类加载器在类层次划分、OSGi、热部署、代码加密等领域大放异彩成为Java技术体系的一块重要基石。类和类加载器类的唯一性是有类加载器决定的比较2各类是否相等除了比较类本身还需要比较类加载器。如果一个类被2个不同的类加载器加载那么加载的类是2个不同的类。如下所示public class ClassLoaderTest {public static void main(String[] args) {ClassLoader myLoader new ClassLoader() {Overridepublic Class? loadClass(String name) throws ClassNotFoundException {try {String fileName name.substring(name.lastIndexOf(.) 1) .class;InputStream is this.getClass().getResourceAsStream(fileName);if (is null) {return super.loadClass(name);}byte[] b new byte[is.available()];is.read(b);return defineClass(name, b,, 0, b.length);}catch (IOException e) {throw new ClassNotFoundException(name);}}};Object obj myLoader.loadClass(ClassLoaderTest).newInstance();System.out.println(obj instanceof ClassLoaderTest);}
}运行结果false。类加载器的层次结构“横看成岭侧成峰”站在Java虚拟机角度看只存在2种不同类型的类加载器一种是启动类加载器Bootstrap ClassLoader这个类加载器是C实现的是虚拟机自身一部分另外一种是其他类加载器这种类加载器都是虚拟机外部的由Java实现并且全部继承自抽象类java.lang.ClassLoader。站在Java程序员角度看可以分成以下3种启动类加载器上面刚提到过负载加载存放在JAVA_HOME/lib路径中的类或者被-Xbootclasspath参数所指定路径下的类。启动类加载器无法被Java程序直接引用。扩展类加载器Extension ClassLoader,这个加载器由sum.misc.Launcher$ExtClassLoader实现负责加载JAVA_HOME/lib/ext路径下的类或者由java.ext.dirs系统变量所指定下的类库。应用程序类加载器Application ClassLoader,这个类加载器由sum.misc.Launcher$AppClassLoader来实现。由于这个类加载器可以由ClassLoader.getSystemClassLoader()获取因此也叫系统类加载器。AppClassLoader复杂加载用户类路径ClassPath上指定的类库开发者可以直接使用。以下是类加载器的层次结构双亲委派模型类加载器双亲委派模型是JDK1.2引入被应用于几乎所有的Java程序中。但它并不是一个强制性的约束模型二是Java设计者推荐的一种类加载方式。双亲委派有他的适用场景它能够适用于绝大多数场景模型可以保证Java程序的稳定运行防止重复加载和任意修改。那具体是如何做到的呢双亲委派模型的工作过程如下如果一个类加载器收到了类加载的请求它首先不会自己尝试加载这个类而是把请求委派给父类加载器去完成如上图所示每一层次的类加载器都是如此因此所有的加载请求最终都应该传送到顶层的启动类加载器中只有父类反馈自己无法完成这个加载请求时即它的搜索范围没有找到指定的类字加载器才会尝试自己加载。比如java.lang.Object无论哪个类加载器需要加载这个类最终都由Bootstrap ClassLoader加载因此Object在程序的各个类加载器环境都是同一个类。相反如果如果不用双亲委派模型进行加载用户自定义了一个Object类并放置在类路径下最终可能会引发程序混乱。双亲委派模型很好地解决了基础类的统一问题保证了虚拟机的安全性。非双亲委派模型线程上下文类加载器双亲委派模型适用于大部分场景但也有它自身的缺陷假设基础类由Bootstrap类加载器加载但是基础类需要回调用户的代码基础代码却是由应用类加载器加载这个时候该怎么办呢JNDIJava Naming and Directory Interface服务就是上面描述的这种场景JNDI是Java的标准服务它自身的代码由Bootstrap类加载器加载由于JNDI的目的就是对资源进行集中管理和查找它需要调用独立厂商实现的JNDI接口提供者SPI的代码独立厂商提供的代码jar包放置在ClassPath下如果使用双亲委派模型加载类的方式是搞不定的怎么办呢为了解决这个困境Java设计团队引入了线程上下文类加载器Thread Context ClassLoader,虽然它确实不太优雅但解决问题啊。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置如果创建线程还未设置它将从父线程中继承一个如果在应用程序的全局范围内都没有设置过那么这个类加载器就是AppClassLoader。有了线程上下文类加载器JNDI服务就可以加载所需要的SPI代码即父类加载器可以请求子类加载器完成类加载动作这其实是违反了双亲委派原则的。实际上JNDI、JDBC、JCE、JAXB、JBI等所有涉及SPI加载动作的基本都采取的这种方式。总结线程上下文加载器之所以打破双亲委派模型是因为双亲委派模型依赖的单一方向的并不能解决父类加载器去依赖子类加载器这种逆方向需求。Tomcat类加载器实际上不只是Driver驱动的实现是这样只要有需要在双亲委派机制无法满足需求前提下在tomcat、spring等等的容器框架也是通过一些手段绕过双亲委派机制。双亲委派模型要求除了顶层的启动类加载器之外其余的类加载器都应当由自己的父类加载器加载。tomcat 为了实现隔离性没有遵守这个约定每个webappClassLoader加载自己的目录下的class文件不会传递给父类加载器。如下图所示从图中的委派关系中可以看出CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用从而实现了公有类库的共用。CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类但各个WebAppClassLoader实例之间相互隔离。JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件它出现的目的就是为了被丢弃当Web容器检测到JSP文件被修改时会替换掉目前的JasperLoader的实例并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。总结tomcat之所以破坏双亲委派模型我想主要在于双亲委派模型只看到了共享性没有看到隔离性需求即共享是有条件的共享。OSGI类加载器非双亲委派模型的另一种需求来自程序动态性追求。比如代码热替换HotSwap、模块热部署Hot Deployment。可以哪USB热插拔技术来做比方。热部署对生产系统来说具有很大吸引力不用停机就能完成部署效率啊。OSGI是Java模块化标准OSGI实现模块热部署的关键是它自定义的类加载器每一个模块都有一个自己定义的类加载器当需要更换一个Bundle时则把Bundle连同类加载器一同替换以实现热替换。在OSGI环境下类加载器不再是树型结构的双亲委派模型而是网状结构当收到类加载请求时OSGI是按照下面顺序进行类搜索的以“java.*”开头的类委派给父类加载器加载。将委派列表名单内的类委派给父类加载器加载。将Import列表中的类委派给Export这个类的Boundle的类加载器加载。查找当前Boundle的ClassPath使用自己的类加载器加载。查找类是否在Fragment Boundle中如果在则委派给Fragment Boundle类加载器加载。查找Dynamic Import列表的Boundle委派给对应的Boundle类加载器加载。如果以上都未查询到则查找失败。上面的搜索顺序除了1,2两点和双亲委派类似其余都是平级类加载过程。总结OSGI Boundle类加载器提供了类加载的另一种机制加载器结构不一定非得是树型结构也可以是网状结构。全文总结本文较全面的分析了jvm的类加载机制分析了类加载的5个阶段包含加载、验证、准备、解析、初始化最后总结了类加载器加载类的几种模型双亲委派模型、SPI的类加载模型、tomcat类加载模型以及OSGI类加载模型。The end.转载请注明来源否则严禁转载。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/88874.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!