初始化:
虚拟机对类的何时加载没有约束,但对类的初始化约定了5个条件(加载,验证,准备需在其之前开始);
- new,读写非final(final的已被放入常量池)的static字段,调用静态方法;
- java.lang.reflect
- 初始化一个类时,其父类未初始化,初始化其父类;
- 程序入口(main())所在的类;
- Jdk1.7动态语言的支持,对java.lang.invoke.MethodInvokeHandle的解析是REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,这个句柄对应的类没有被初始化,则初始化该类;
被动使用不会导致类的初始化:
- Sub extends Super,Super有static field value,main中调用Sub.Value,只会导致Super的初始化;
- 通过数组定义来引用类,不会导致类的初始化(jvm会自动生成一个类,提供数组的length和clone方法(可见的));
- 引用类的static final field不会导致初始化;
接口的初始化:
- 接口不能包含static{},子接口的初始化不会触发父接口的初始化;
初始化阶段是执行类构造器方法的过程;
- 类构造器方法:<clinit>()方法是编译器自动收集类中的索引类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的(按代码顺序);静态语句块只能访问定义在其之前的变量,但是能够对定义在之后的变量进行赋值;
- <clinit>()与类构造函数不同,不需要显示调用父类构造器,jvm会保证在执行子类的<clinit>之前父类的<clinit>已经执行完毕;
- <clinit>不是必须的,jvm可以不生成<clinit>;
- 接口中不能使用static{},但仍有变量初始化,所以接口也会有<clinit>,但是接口<clinit>执行之前不需要执行父接口的<clinit>,只有当父接口的变量被使用时,父接口的<clinit>才会被执行,接口的实现类也一样;
- 虚拟机会保证类的<clinit>方法在多线程环境下被正确加锁,可能会导致线程阻塞,在实际情况下这种阻塞很隐蔽;
类加载器:
ClassLoader的重要方法:
- public Class<?> loadClass(String name) throws ClassNotFoundException
-
- 载入并返回一个Class
- protected final Class<?> defineClass(byte[] b, intoff, intlen)
-
- 定义一个类,不公开调用
- protected Class<?> findClass(String name) throws ClassNotFoundException
-
- loadClass回调该方法,自定义ClassLoader的推荐做法
- protected final Class<?> findLoadedClass(String name)
-
- 寻找已经加载的类
双亲委派模型:
- Bootstrap classLoader:只加载<java_home>/lib下的jar包或-Xbootclasspath参数指定的目录,无法被java程序获得,使用null表示;
- Extension classLoader:由sun.misc.Launcher$ExtClassLoader实现,加载<java_home>/lib/ext下的jar包或java.ext.dirs指定路径下的jar包;
- Application classLoader:由sun.misc.Launcher$AppClassLoader实现,加载classPath下的jar包,ClassLoader.getSystemClassLoader()的返回值就是它,如果不适用自定义加载器,默认使用该加载器加载应用程序;
破坏双亲委派模型:
- Jdk版本兼容问题,用户应当实现ClassLoader的findClass方法,而不要去overwrite loadClass();
- 基础类对实现类有依赖,典型的:JNDI,JDBC,JCE,JAXB,JBI,实现的方式:线程上下文加载器(Thread Context loader),Thread拥有一个setContextClassloader(ClassLoader cl),直接从父类继承,如果没有一个全局的设置,则默认是系统加载器,这样的话,JNDI服务就可以使用线程上下文加载器加载SPI代码;
- 对程序的动态性追求,OSGI,实现代码热替换(hot swap)和热部署(hot deployment),OSGI实现模块化热部署的关键在于自定义的类加载机制的实现,每一个程序模块(OSGI中成为bundle)都有一个自己的类加载器,当需要更换一个bundle,将bundle和类加载器一起替换以实现代码的热替换;
- OSGI中,ClassLoader不再是双亲结构,而是更复杂的网状结构;
- 通过反射强制指定类加载器加载指定 Class:
-
-
public static void main(String args[]) throws Exception {ClassLoadercl=FindClassOrder2.class.getClassLoader();byte[] bHelloLoader=loadClassBytes("geym.jvm.ch6.findorder.HelloLoader");Method md_defineClass=ClassLoader.class.getDeclaredMethod("defineClass", byte[].class,int.class,int.class);md_defineClass.setAccessible(true);md_defineClass.invoke(cl, bHelloLoader,0,bHelloLoader.length);md_defineClass.setAccessible(false);
HelloLoaderloader = new HelloLoader();System.out.println(loader.getClass().getClassLoader());loader.print();}
-
- Classloader SPI(service provider interface)
-
- Thread. setContextClassLoader()
- 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
- 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
-
// 代码来自于 javax.xml.parsers.FactoryFinder 展示如何在启动类加载器加载AppLoader的类// 上下文ClassLoader可以突破双亲模式的局限性
static private Class getProviderClass(String className, ClassLoader cl,boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException{try {if (cl == null) {if (useBSClsLoader) {return Class.forName(className, true, FactoryFinder.class.getClassLoader());} else {cl = ss.getContextClassLoader();if (cl == null) {throw new ClassNotFoundException();}else {return cl.loadClass(className); //使用上下文ClassLoader}}}else {return cl.loadClass(className);}}catch (ClassNotFoundException e1) {if (doFallback) {// Use current class loader - should always be bootstrap CLreturn Class.forName(className, true, FactoryFinder.class.getClassLoader());}…..
tomcat 正统的类加载器例子:
- 主流的web服务器都实现了自定义的ClassLoader(不止一个),解决如下问题:
-
- 实现不同应有的library的隔离(相同类库的版本不一样)
- 实现类库共享,减少内存占用(防止方法区过度膨胀)
- Server尽可能保证自身安全不受部署的web application的影响(许多server也是使用java实现)
- 支持JSP的server大多要支持hotSwap功能;
- Tomcat中有3组存放lib的目录:common/,server/,shared/,和application自身的WEB-INFO/:
-
- Common/:被所有application和tomcat server公用
- Server/:lib仅可以被tomcat server使用
- Shared/:可以被所有application公用,但是tomcat server不可见
- WEB-INFO/:仅能被此web application使用
- 使用双亲委派模式实现
-
- WebAPPClassLoader和JasperLoader通常有多个实例,每个web application对应一个WebAPPClassLoader instance,每一个jsp文件对应一个JasperLoader instance,
-
- JasperLoader instance出现的目的是为了被丢弃,当tomcat发现jsp文件被修改,通过建立一个新的JasperLoader instance来替代旧的JasperLoader instance实现HotSwap