中间跳了几章,过些天(有可能是很多天)会补充回来
重温深入理解java虚拟机这本书,温故而知新。
书是基于java虚拟机规范而来,文章中会掺杂我的个人理解的描述,如有误,请指正。
1、类加载的时机
说明:类加载过程规范中并没有进行强制约束,具体实现自由把握。
初始化在有且只有此5种情况下必须被初始化。
A、遇到new、getstatic、putstatic或invokestatic字节码指令:使用new关键字、读取或设置类的静态字段(被final修饰,已在编译期把结果放入常量池的静态字段除外)、调用类的静态方法
B、java.lang.reflect包的方法进行反射
C、初始化类时候,发现父类没初始化,先初始化父类(接口除外)
D、虚拟机启动执行的主类
E、jdk1.7之后动态语言支持
2、类加载过程详解
(1)加载:拿到class文件,可以从网络、zip包、文件夹都可以。
(2)验证:文件格式、元数据、字节码、符号引用等的验证
(3)准备:正式为类变量(被static修饰的)分配内存并设置类变量初始值(被static修饰的基本类型 int为0)阶段,如果static final修饰,则直接为对应的值,比如public static final int value=321 这个阶段直接就是321,而不会是初始值0了。
(4)解析:a、类或接口的解析 b、字段解析 c、类方法解析 d、接口方法解析
(5)初始化(字节码开始内容开始登场):
A、编译为class文件后,会把静态代码块、静态变量生成<clinit>方法(类变量的赋值动作和static{}语句块),这里还有一个问题,就是静态代码块中的变量不能引用静态代码块后面声明变量,可以打印使用,不能赋值。详情见:https://blog.csdn.net/zhoumingsong123/article/details/82120362
B、虚拟机保证<clinit>先执行父类的,也就是一定先执行Object的
C、类或接口不是必须的,如果没有静态语句,也没有对变量进行的赋值操作
D、接口如果不存在静态语句块,则子类或者实现初始化时,不需要调用父接口的<clinit>方法。
F、虚拟机保证多线程加载的时候是线程安全的,只会有一个线程执行一次。
3、类加载器(上面是理论,这个是实现)
不同的类加载器加载的同一个class文件,在instanceof进行判断时候是不相同的。
双亲委派模型:不是强制行的,而是推荐的形式
(1)启动类加载器(Bootstrap ClassLoader):加载<JAVA_HOME>/lib目录中或者被-Xbootclasspath参数指定的路径,并且仅文件名识别rt.jar。
(2)扩展类加载器(Extension ClassLoader,sun.misc.Launcher$ExtClassLoader):加载<JAVA_HOME>/lib/ext或者java.ext.dirs指定的类库,开发者可以使用
(3)应用程序类加载器(Application ClassLoader,sun.misc.Launcher$AppClassLoader):加载类路径上的指定的类库
(4)自定义类加载器(虽然能随便加载类,但是加载以java.lang开头的类不会成功):
破坏双亲委派的情况:
(1)写自定义类加载的时候,可能会覆盖loadClass方法,这只是为了兼容1.2之前的逻辑,所以不建议自己覆盖此方法,应该覆盖findClass,这样loadClass加载不了的类再调用自定义的findClass方法进行寻找加载,实现完美的双亲委派模型。
(2)类似JNDI服务,需要父类用到自定义加载器加载的资源,使用线程上下文解决。
(3)程序的动态性,比如热部署、模块热部署等。例如OSGI模块化做的工作。