目录
1. 类初始化时机
Java类的初始化--虚拟机会保证这个类的 构造方法 线程安全,类其实就是懒加载。
以下是类的初始化时机:
1. main 方法所在的类,总会被优先初始化
2. 首次访问这个类的静态变量或静态方法时
3. 子类初始化,如果父类还没初始化,会引发父类初始化
4. 子类访问父类的静态变量,会触发父类的初始化
5. new 会导致初始化不会导致类初始化的情况如下:
1. 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
2. 类对象.class 不会触发初始化
3. 创建该类的数组不会触发初始化
4. 类加载器的 loadClass 方法
2. 类加载器
2.1 类加载器的分类:
中文名称 | 名称 | 加载哪的类 | 说明 |
启动类加载器 | Bootstrap ClassLoader | JAVA_HOME/jre/lib | 无法直接访问 |
扩展类加载器 | Extension ClassLoader | JAVA_HOME/jre/lib/ext | 上级为 Bootstrap,显示为 null |
应用类加载器 | Application ClassLoader | classpath | 上级为 Extension |
自定义类加载器 | 自定义类加载器 | 自定义 | 上级为 Application |
启动类加载器:主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等包。
扩展类加载器:主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
应用类加载器:主要负责加载当前应用的classpath下的所有类。
自定义类加载器:用户自定义的类加载器,可加载指定路径的class文件。
2.2 双亲委派模型
要加载一个类.class,从低层级到高层级一级一级委派,先由应用层加载器委派给扩展类加载器,再由扩展类委派给启动类加载器;启动类加载器无法加载,再由扩展类加载器载入,扩展类加载器无法加载,最后由应用类加载器载入,如果应用类加载器也找不到,就会触发findclass,抛出classNotFoundException。
源码中的执行的过程如下:
1.首先判断该类是否已经被加载过。
2.该类未被加载,如果父类不为空,交给父类加载。
3.如果父类为空,交给bootstrap classloader 加载。
4.如果类还是无法被加载到,向下层依次加载。
2.3 双亲委派模型的意义
1.通过委派的方式,可以避免类的重复加载,父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
2.保证程序的安全性,层级关系代表优先级,也就是所有类的加载,优先给启动类加载器,这样就保证了核心类库类。
3. 类的运行期优化
3.1 即时优化
JVM 将执行状态分成了 5 个层次:
0 层,解释执行(Interpreter)
1 层,使用 C1 即时编译器编译执行(不带 profiling)
2 层,使用 C1 即时编译器编译执行(带基本的 profiling)3 层,使用 C1 即时编译器编译执行(带完全的 profiling)
4 层,使用 C2 即时编译器编译执行
即时编译器(JIT)与解释器的区别:
解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释
JIT 是将一些字节码编译为机器码,并存入 Code Cache,下次遇到相同的代码,直接执行,无需再编译
解释器是将字节码解释为针对所有平台都通用的机器码
JIT 会根据平台类型,生成平台特定的机器码
【逃逸分析】多次for循环同样的代码
【方法内联】多次基本计算
3.2 反射优化
反射默认调用16次就会生成一个类进行类方法调用。
sun.reflect.noInflation 可以用来禁用膨胀(直接生成 GeneratedMethodAccessor1,但首次生成比较耗时,如果仅反射调用一次,不划算)
sun.reflect.inflationThreshold 可以修改膨胀阈值