- 首先看一段源码
public class Math {
private static final int initData = 666;
private static UserService user = new UserService();
public int compute(){ //一个方法对应一块栈针内存区域
int a =1;
int b = 2;
int c = a+ b;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
我们在程序中运行上面的一段代码,运行main方法,具体做了什么?
java命令执行代码的大体流程
Math.class --------->windows环境下,java.exe调用底层的jvm.dll文件创建java虚拟机(c++实现)------->创建一个启动类加载器实例(c++现)----------> c++调用java代码创建jvm启动器 实例sun.misc.Launer,该类由启动类加载器负责加载创建其他类加载器 -------.> sun.misc.Launer.getLauncher() ---------- 获取运行类自己的加载器ClassLoader,是AppClassLoader
的实例-------------》launcher.getClassLoader()---------调用loadClass加载要运行的类Math-----------------------------------------
– >ClassLoader.loadClass(“Math”)----------加载完成时Jvm会执行Math类的main方法--------》Math.main()-------程序运行结束-
-----------》jvm销毁
loadClass的类加载过程有下面几步
加载 在磁盘上查找并通过IO读入字节码文件,使用到类时才会加载(懒加载),例如调用类的main()方法,new对象等,在加载阶段在内存中会生成代码这个类的java,lang.Class对象,最为方法区这个类的各种数据的访问入口
可以通过java -p -v Math.class 命令看到汇编语言编写的字节码 执行顺序 (对应 .clsss文件)
验证 检验字节码文件的正确性
准备 给类的静态变量分配内存,并赋予默认值
解析 将符号引用转为直接引用(内存地址)
初始化 对类的静态变量初始化指定的值,并执行静态代码块
类加载到方法区中后主要包含 运行时常量池, 类型信息, 字段信息, 方法信息,类加载器的引用,对应Class实例的引用等
使用
卸载
类的执行顺序:
父类静态代码块 —》子类的静态代码块----》父类非静态代码快 -------》父类 构造方法 -------》子类的非静态代码块-------》子类的构造方法
类加载器以及双亲委派模型
启动类加载器 负责加载jJVM运行的位于JRE目录的核心类库,比如rt.jar,charset.jar等
扩展程序类加载器 负责加载JVM位于JRE目录下的ext扩展目录的jar包
应用程序类加载器 负责加载ClassPath路径下的类包,主要加载自己写的哪些类。
自定义加载器 加载用户自定义路径下的类包
双亲委派原理参考java面试高级篇所写
双亲委派模型优点:
沙箱保护机制以及避免类的重复加载
可以通过代码来打破双亲委派模型,只需要自己写的类 extends ClassLoader , 重写loadClass 方法
package com.tuling.jvm;
import lombok.SneakyThrows;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader{
private String classPath;
public MyClassLoader(String classPath){
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[]data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@SneakyThrows
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadByte(name);
return defineClass(name,data,0,data.length);
}
/**
* 重写父加载器,实现自己的加载逻辑,不委派双亲委派
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//如果类名不是以com.test.Math 开头,调用AppconfigClassLoader进行加载
if(! name.startWith("com.test.Math")){
c = this.getParent().loadClass(name);
}else{
//调用自己的加载器去加载
c = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
public static void main(String[] args) throws Exception {
//初始化自定义类加载器
MyClassLoader myClassLoader = new MyClassLoader("C:\\test");
//依次在 C盘 test目录下分层建立目录com/test/Math.class
Class clazz = myClassLoader.loadClass("com.test.Math");
Object obj = clazz.newInstance();
//调用类中方法compute
Method method = clazz.getDeclaredMethod("compute", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass());
}
}
}
当没有loadClass 方法时,自己项目路径包com.test有个Math.java类,可以看出类加载器为Application ClassLoader, 当删除掉之后,加载我们自己的类加载器。可以加入loadClass 方法,来打破双亲委派模型。这种测试很好的验证双亲委派模型的执行流程。