免费建立网站步骤,建网站免费咨询,制作公司网页价钱,广东如何做网站设计类加载阶段
1. 加载
加载#xff1a;将类的字节码载入方法区中#xff0c;内部采用C的instanceKlass描述java类。如果这个类的父类还没加载#xff0c;则先加载父类加载和链接可能是交替运行的 通过全限定名获取字节码 从文件系统#xff08;.class 文件#xff09;、JA…类加载阶段
1. 加载
加载将类的字节码载入方法区中内部采用C的instanceKlass描述java类。如果这个类的父类还没加载则先加载父类加载和链接可能是交替运行的 通过全限定名获取字节码 从文件系统.class 文件、JAR 包、网络、动态代理生成等途径读取二进制数据。 将字节码解析为方法区的运行时数据结构 在方法区元空间存储类的静态结构如类名、字段、方法、父类、接口等。 在堆中生成 Class 对象 创建一个 java.lang.Class 实例作为方法区数据的访问入口。
2. 链接
验证验证类是否符合JVM规范安全性检查准备为static变量分配空间设置默认值 static变量分配空间和赋值是两个步骤分配空间在准备阶段完成赋值在初始化阶段完成。 如果static变量是final基本类型以及字符串常量编译阶段就确定了赋值在准备阶段完成如果static变量是final的但是属于引用类型赋值也会在初始化阶段完成 解析将常量池中的符号引用解析为直接引用。用符号描述目标转变为用他们在内存中的地址描述他们
3. 初始化
cint()V方法
初始化即调用 cint()V方法虚拟机会保证这个类的构造方法的线程安全
发生的时机
类的初始化是懒惰的。
main方法所在的类优先被初始化首次访问这个类的静态变量或静态方法子类初始化时如果父类还没初始化会先初始化父类子类访问父类的静态变量只会触发父类的初始化。执行Class.forNamenew会导致初始化
不会导致初始化
访问类的static final静态常量基本类型和字符串不会触发初始化类对象.class不会触发初始化创建该类的数组不会触发初始化类加载器的loadClass方法不会触发初始化Class.forName的参数2为false时不会触发初始化
public class Load01 {public static void main(String[] args) {System.out.println(E.a); // 不会被初始化(基本类型)System.out.println(E.b); // 不会被初始化(字符串)System.out.println(E.c); // 会被初始化(包装类型)}
}
class E {public static final int a 10;public static final String b hello;public static final Integer c 20;static {System.out.println(init E);}
}懒惰初始化单例模式
public class Load02 {public static void main(String[] args) {Singleton.test();System.out.println(Singleton.getInstance()); // 懒汉式只有调用getInstance()方法时才会加载内部的LazyHolder}
}
class Singleton {// 私有构造方法private Singleton(){}public static void test() {System.out.println(test);}private static class LazyHolder {private static Singleton SINGLETON new Singleton();static {System.out.println(LazyHolder init);}}public static Singleton getInstance() {return LazyHolder.SINGLETON;}
}类加载器
名称加载哪的类说明Bootstrap ClassLoaderJAVA_HOME/jre/lib无法直接访问Extension ClassLoaderJAVA_HOME/jre/lib/ext上级为BootstrapApplication ClassLoaderclasspath上级为Extension自定义类加载器自定义上级为Applicaiton
启动类加载器
启动类加载器是由C程序编写的不能直接通过java代码访问如果打印出来的是null说明是启动类加载器。
public class Load03 {public static void main(String[] args) throws ClassNotFoundException {Class? aClass Class.forName(pers.xiaolin.jvm.load.F);System.out.println(aClass.getClassLoader()); // null}
}
public class F {static {System.out.println(bootstarp F init);}
}使用java -Xbootclasspath/a:. pers.xiaolin.jvm.load.Load03将这个类加入bootclasspath之后输出null说明是启动类加载器加载的这个类 java -Xbootclasspath:new bootclasspathjava -Xbootclasspath/a:追加路径java -Xbootclasspath/p:追加路径 应用程序类加载器
public class Load04 {public static void main(String[] args) throws ClassNotFoundException {Class? aClass Class.forName(pers.xiaolin.jvm.load.G);System.out.println(aClass.getClassLoader()); // sun.misc.Launcher$AppClassLoader18b4aac2应用程序类加载器}
}public class G {static {System.out.println(G init);}
}双亲委派模式
双亲委派调用类加载器loadClass方法时查找类的规则。 每次都去上级类加载器中找如果找到了就加载如果上级没找到才由本级的类加载器进行加载。 执行流程
protected Class? loadClass(String name, boolean resolve) {synchronized (getClassLoadingLock(name)) {// 1. 检查类是否已加载Class? c findLoadedClass(name);if (c null) {try {// 2. 委托父加载器加载if (parent ! null) {c parent.loadClass(name, false);} else { // parent null说明到了启动类加载器c findBootstrapClassOrNull(name); // 父加载器是 Bootstrap}} catch (ClassNotFoundException e) {}// 3. 父加载器未找到则自行加载if (c null) {c findClass(name);}}return c;}
}核心作用
避免类重复加载确保一个类在JVM中只存在一份由最顶层的类加载器优先加载如果用户自己定义了一个java.lang.String那么这个类并不会被加载而是由最顶层的Bootstrap加载核心的String类保证安全性防止核心类被篡改通过优先委托父类加载器确保核心类由可信源加载分工明确Bootstrap加载JVM核心类、Extension加载扩展功能、Application加载用户代码
破坏双亲委派场景
双亲委派并非强制约束有些情况也会破坏它否则有些类他是找不到的。
核心库JDBC需要调用用户实现的驱动mysql-connector-java 通过Thread.currentThread().getContextClassLoader()获取线程上下文加载器通常是Application ClassLoader直接加载用户类。 不同模块可能需要隔离或共享类 自定义类加载器按照需要选择是否委派父加载器 热部署动态替换已经加载的类 自定义类加载器直接重新加载类不委派父类加载器 自定义类加载器
使用场景
需要加载非classpath路径中的类文件框架设计都是通过接口来实现希望解耦tomcat容器这些类有多种版本不同版本的类希望能隔离。
步骤
继承ClassLoader父类要遵守双亲委派机制重写findClass方法注意不是重写loadClass方法否则不会走双亲委派读取类文件中的字节码调用父类的defineClass方法来加载类使用者调用该类加载器的loadClass方法
public class Load05 {public static void main(String[] args) throws ClassNotFoundException {MyClassLoader classLoader new MyClassLoader();// 5. 使用者调用该类加载器的loadClass方法Class? c1 classLoader.loadClass(MapImpl1);Class? c2 classLoader.loadClass(MapImpl1);System.out.println(c1 c2); // trueMyClassLoader classLoader2 new MyClassLoader();Class? c3 classLoader2.loadClass(MapImpl1);System.out.println(c1 c3); // false }
}// 1. 继承ClassLoader父类
class MyClassLoader extends ClassLoader {// 2. 重写findClass方法Overrideprotected Class? findClass(String name) throws ClassNotFoundException { // name就是类名称String path d:\\myclasspath name .class;try {ByteArrayOutputStream os new ByteArrayOutputStream();Files.copy(Paths.get(path), os);// 3. 读取类文件中的字节码byte[] bytes os.toByteArray();// 4. 调用父类的defineClass方法来加载类return defineClass(name, bytes, 0, bytes.length); // byte[] - *.class} catch (IOException e) {e.printStackTrace();throw new ClassNotFoundException(类文件未找到, e);}}
}唯一确定类的方式应该是包名、类名、类加载器相同 运行期优化
逃逸分析
【现象】循环内创建了1000个Object对象但未被外部引用。 【JIT优化】JIT编译器尤其是C2编译器会通过逃逸分析Escape Analysis发现这些对象是方法局部作用域且未逃逸即不会被其他线程或方法访问因此会直接优化掉对象分配。实际运行时这些对象可能根本不会在堆上分配内存而是被替换为标量或直接在寄存器中处理。
public class JIT01 {public static void main(String[] args) {for(int i 0; i 200; i) {long start System.nanoTime();for(int j 0; j 1000; j) {new Object();}long end System.nanoTime();System.out.printf(%d\t%d\n, i, (end - start));}}
}在运行期间虚拟机会对这段代码进行优化。 JVM将执行状态分为5个层次
0层解释执行1层使用C1即时编译器编译执行不带profiling2层使用C1即时编译器编译执行带基本的profiling3层使用C1即时编译器编译执行带完全的profiling4层使用C2即时编译器编译执行 profiling是在运行过程中收集一些程序执行状态的数据方法的调用次数、循环次数… 解释器将字节码解释成机器码下次遇到相同的字节码仍然会执行重复的解释 即时编译器JIT就是把反复执行的代码编译成机器码存储在Code Cache下次再遇到相同的代码直接执行无需编译。 解释器是将字节码解释为争对所有平台都通用的机器码JIT会根据平台类型生成平台特定的机器码。 对于占据大部分不常用的代码无需耗费时间将其编译成机器码直接采取解释执行的方式对于仅占用小部分的热点代码 可以将其编译成机器码。运行效率Iterpreter C1 C2 方法内联
例子1
private static int square(final int i) {return i * i;
}
System.out.println(square(9));如果发现square是热点方法并且长度不会太长时就会进行内联把方法内的代码拷贝到调用位置
System.out.println(9 * 9);例子2
public class JIT02 {int[] elements randomInts(1_000);int sum 0;void doSum(int x) {sum x;}public void test() {for(int i 0; i elements.length(); i) {doSum(elements[i]);}}
}方法内联也会导致成员变量读取时的优化操作。 上边的test()方法会被优化成
public void test() {// elements.length首次读取会缓存起来 int[] localfor(int i 0; i elements.length(); i) { // 后续999次求长度不需要访问成员变量直接从loca中取sum elements; // 后续1000次取下标不需要访问成员变量直接从loca中取}
}反射优化
1. 初始阶段解释执行未优化
前几次调用约0~5次 Method.invoke 会走完整的 Java反射逻辑包括 方法权限检查AccessibleObject。参数解包Object[] 转原始类型。动态方法解析通过JNI调用底层方法。 性能极差单次调用耗时可能是直接调用的 20~100倍微秒级 vs 纳秒级。 2. 中间阶段JIT初步优化方法内联膨胀阈值
调用次数达到阈值约5~15次 JIT编译器C2开始介入优化 方法内联Inlining 如果 foo() 是简单方法如本例的 System.out.printlnJIT会尝试内联它。但 Method.invoke 本身 无法直接内联因反射调用是动态的。 膨胀阈值Inflation Threshold JVM默认设置 -XX:InflationThresholdN通常N15当反射调用超过此阈值时JVM会生成 动态字节码存根Native Method Accessor替代原始反射逻辑。优化效果 调用从JNI方式转为直接调用生成的存根代码性能提升约 5~10倍。 3. 最终阶段动态字节码生成最高效
超过膨胀阈值如15次后 JVM为 foo.invoke() 生成专用的 字节码访问器GeneratedMethodAccessor // 伪代码生成的动态类class GeneratedMethodAccessor1 extends MethodAccessor {public Object invoke(Object obj, Object[] args) {Reflect01.foo(); // 直接调用目标方法绕过反射检查return null;}}优化点 完全跳过权限检查 和 参数解包因JVM确认方法签名固定。通过字节码直接调用 foo()性能接近 直接方法调用纳秒级。