执行main方法的主要流程
上代码:
public class Test {
private User user = new User();
public int add() {
int a = 10;
int b = 10;
int c = a + b;
return c;
}
public static void main(String[] args) {
Test test = new Test();
int sum = test.add();
System.out.println(sum);
}
}
通过java命令执行Test字节码
在执行main方法之前,jvm首先通过类加载把主类加载到jvm内存中,主要步骤:
类加载过程(classLoader.loader("xx.xx.xx"))
当我们运行某个java类的main函数时,首先需要加载这个类,加载类的主要流程为:
加载、验证、准备、解析、初始化、使用、卸载。
加载:从磁盘文件找到需要加载的class字节码文件,将它以Class对象的方式加载到jvm内存中,当需要使用它时,比如创建对象等操作,只需知道它所在对应的地址就可以使用。
验证:验证字节码是否符合jvm字节码的规则,比如:魔数。
准备:给类的静态变量分配内存,并赋默认值,比如,int类型,赋值0。
解析:将符号引用替换为直接引用,该阶段会把静态方法替换为指向数据所在内存的指针或者句柄,这个过程就是静态链接。动态链接是程序在运行期间才能确定数据所在内存的地址进行动态替换。
初始化:对类的静态变量进行赋值,初始化指定的值,执行静态代码块
类加载器
类加载器在java中的实现:
引导类加载器:加载JRE中lib文件下类库jar包
拓展类加载器:加载JRE中lib文件中ext文件下的jar包
应用类加载器:加载classpath路径下的class文件
自定义类加载器:加载用户自定义文件路径的类
类加载器测试类:
@Slf4j
public class TestClassLoader {
public static void main(String[] args) {
//String加载器为BootstrapClassLoader,但是打印为null,是因为BootstrapClassLoader是由c++(jvm)实现的
log.debug("String加载类:{}", String.class.getClassLoader());
log.debug("DESKeyFactory加载类:{}", DESKeyFactory.class.getClassLoader());
log.debug("当前类的加载类:{}", TestClassLoader.class.getClassLoader());
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = classLoader.getParent();
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
log.debug("ClassLoader默认加载器:{}", classLoader);
log.debug("appClassLoader父加载器:{}", extClassLoader);
log.debug("extClassLoader父加载器:{}", bootstrapClassLoader);
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
log.debug("bootstrapClassLoader加载的资源:");
for (URL url : urls) {
log.debug("{}", url);
}
log.debug("extClassLoader加载的jar包:{}", System.getProperty("java.ext.dirs"));
log.debug("appClassLoader加载的jar包:{}", System.getProperty("java.class.path"));
}
}
类加载过程:
在运行TestClassLoader类的main方法后,windows系统会创建一个jvm实例并创建一个引导类加载器(BootstrapClassLoader),它会通过C++代码调用java中sun.misc.Launcher#getLauncher()方法创建Launcher实例,此过程会先后创建extClassLoader和AppClassLoader,通过AppClassLoader就可以加载TestClassLoader类,加载完成后就可以调用TestClassLoader#main()方法。
双亲委派机制
加载器之间的层级关系
还是以TestClassLoader类为例,第一次加载TestClassLoader,jvm首先用AppClassLoader加载,会先查找是否已经加载,如果已经加载了直接返回;否则委托给父加载器ExtClassLoader去加载,也会先判断是否已经在ExtClassLoader中加载过,加载了就返回;否则委托给父加载器BootstrapClassLoader去加载,同样会判断是否已经加载过,加载了就返回;没有则会去jdk中jre的lib下查找是否存在TestClassLoader字节码文件,发现没有;则向下委托给ExtClassLoader去加载,ExtClassLoader会去查找lib中ext文件下是否存在TestClassLoader类字节码,发现不存在;则委托给AppClassLoader去加载,AppClassLoader在classpath路径下找了TestClassLoader类字节码,则进行加载;这就是双亲委派机制的过程。核心源码在ClassLoader#loadClass中:
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) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派的必要性:
沙箱安全机制:当自己写一个java.lang.String时,最终jvm会由引导类加载器加载jdk中的java.lang.String类,可以防止核心库被随意改动。
避免类重复加载。
测试:
public class String {
public static void main(String[] args) {
System.out.println("MyString");
}
}
自定义类加载器示例
自定义类加载器类加载Test类,将Test.class复制到D:/test中(注意包路径),重写ClassLoader#findClass()方法。
@Slf4j
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
public byte[] byte2Class(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream inputStream = new FileInputStream(path +"/"+ name + ".class");
byte[] b = new byte[inputStream.available()];
inputStream.read(b);
inputStream.close();
return b;
}
/**
* ClassLoader中默认findClass方法为空,这里需要重写。
* @param name
* @return
*/
protected Class<?> findClass(String name) {
try {
byte[] c = byte2Class(name);
//将byte[]转为class
return defineClass(name, c, 0, c.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("D:/test");
Class<?> aClass = myClassLoader.loadClass("com.example.demo.classloader.Test");
Object obj = aClass.newInstance();
Method method = aClass.getDeclaredMethod("print", null);
method.invoke(obj, null);
log.debug("加载器类型: {}", aClass.getClassLoader());
}
}
需要加载Test类
public class Test {
public void print() {
System.out.println("execute Test.class");
}
}
提醒:在运行MyClassLoader类之前一定要删除target下Test.class文件,否则输出结果将是AppClassLoader。
打破双亲委派机制
重写loadClass()方法,自定义类加载器类,不在向上委托,而是自己加载类
public 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) {
/**
* 直接执行findClass()...什么意思呢? 首先会使用自定义类加载器加载类, 不在向上委托, 直接由自己执行
* jvm自带的类还是需要由引导类加载器自动加载
*/
if (!name.startsWith("com.example.demo")) {
c = this.getParent().loadClass(name);
} else {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}