一、java程序如何运行的
当我们写好一段java代码之后,jvm是如何帮助我们执行这段程序代码的?底层的执行机制是什么?
带着以上两个问题,我们进行一下深入研究。
首先清楚一点,java是一个面向对象的语言,在java中一切皆对象,也就是java中的类,我们的任何代码都无法脱离类而独自存在,如果你想要执行java程序代码,那么必然就需要有一个主类,里面有一个程序启动的入口方法,也就是所谓的public static void main(String[] args){ }静态方法,而JVM就是通过这个入口开始运行程序代码的。
我们知道JVM底层是使用C++语言实现的,当我们启动程序时,相当于创建了一个JVM进程运行在操作系统中,JVM会把我们运行的程序代码编译成.class文件,所谓的字节码文件,保存到磁盘中,然后找到执行主类的字节码文件加载到jvm内存中(方法区),加载对应的类,调用启动方法main()。
jvm调用一些java类的方法时,是需要先进行类的加载的,只有被加载好的类才能被使用。
那么类加载,需要一个专门用来加载类的工具,我们称之为类加载器。
- 首先C++帮我们创建jvm
- C++创建一个引导类加载器,它是最顶层的类加载器
- 然后C++调用java的代码创建jvm的启动器Launcher的实例 Launcher由引导类加载器加载
- 创建Launcher实例过程中,会创建两个加载器:ExtClassLoad 和 AppClassLoad 加载器
- 然后使用AppClassLoad加载器按照双亲委派机制加载需要加载的类,之后调用main方法启动。
- java程序执行结束,jvm销毁。
二、类加载器
java类的加载是使用类加载器加载,那么类加载器有哪些?分别负责加载哪些类?如何工作的?
类加载器有哪些?分别负责加载哪些类?
- 引导类加载器(BootStrapClassLoader):负责加载支撑jvm运行的位于jre的lib目录下的rt.jar,charset.jar等核心类库中类
- 扩展类加载器(ExtClassLoader):负责加载支撑jvm运行的位于jre的lib目录下的ext扩展目下的核心类库中的类
- 应用类加载器(AppClassLoader):负责加载应用程序中自己创建类
- 自定义类加载器:负责加载自定义路径下的类
类加载过程分为:加载->验证->准备->解析->初始化
类加载器的创建
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
// 静态变量 该类在被加载的初始化阶段 被实例化 调用构造方法 单例模式
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
//jvm调用此方法 获取实例
public static Launcher getLauncher() {
return launcher;
}
//构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//创建ExClassLoader扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
/**
** 创建AppClassLoader应用类加载器 上一步创建的扩展类加载器被作为父加载器传入
** AppClassLoader的构造器中
** 创建完成后 赋值给loader变量
**/
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
public ClassLoader getClassLoader() {
//返回的是AppClassLoader加载器
return this.loader;
}
}
首先C++创建引导类加载器,
然后由引导类加载器加载Launcher,在加载Launcher的初始化阶段 给launcher静态变量赋值时,调用Launcher的构造方法(由此可见,类的静态代码块先于构造方法被调用)
在构造方法中创建两个构造器:ExtClassLoader,AppClassLoader。
调用launcher.getClassLoader()方法获取类加载器,获取到加载器后 进行类的加载工作。
分析源码可见:所有的加载器都继承ClassLoader加载器
类加载机制默认遵循双亲委派机制且是懒加载。
三、双亲委派机制
类加载器在加载类时默认遵循双亲委派机制
双亲委派机制:
一般一个类从AppClassLoader开始,在AppClassLoader中先找是否已经加载过,如果已经加载过则直接返回,
若未加载过,则丢给父加载器ExtClassLoader加载,ExtClassLoader先找自己是否加载过,
若未加载过则丢给父加载器BootStrapClassLoader引导类加载器,BootStrapClassLoader会在自己的加载路径中找,找不到
则再回丢给ExtClassLoader加载,ExtClassLoader在自己的加载路径找 找不到回丢给AppClassLoader加载,AppClassLoader在自己加载路径中找到了 则加载成功。
双亲委派机制的优点:
- 沙箱安全:加入你自定义创建一个类String 全类名java.lang.String。在加载的时候,会最终丢给最顶层的引导类加载器加载,而加载器发现自己已经加载过了(加载的是jre下lib目录下的rt.jar核心库的类,并不是你创建的String类),只要全类名相同,加载器就认为加载过了,直接返回。这就防止有人对java的核心类库的类进行篡改。
- 避免重复加载:在整个加载过程中,一个类被任何一个加载器加载过了,就不会在此被加载,避免了重复加载。
四、实现原理
类加载是由各个加载器加载的,每个加载类都有两个核心方法 loadClass() 和findClass();
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先 查看自己是否已经加载过当前类 已加载过 直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
//没有加载过
long t0 = System.nanoTime();
try {
if (parent != null) {
//委托父加载器进行加载
c = parent.loadClass(name, false);
} else {
//没有父加载器了 当前加载器为ExtClassLoader 找BootStrapClassLoader加载器加载
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);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
加载器会一直向上委托父加载器进行加载,最顶层的加载器加载失败,才会向下传递由子加载器加载,为双亲委派机制就体现在loadClass(String name, boolean resolve)方法中
五、如何打破双亲委派机制
上文已经说过双亲委派机制就是在loadClass()方法实现的,那么我们如何打破双亲委派机制呢?
打破双亲委派,我们只需要在类加载时,不在委托父加载器就可以了,就那么简单,如何实现?
自定义加载器,重写loadClass()就可以了。
/**
* @description: 自定义类加载器 父加载器默认AppClassLoader 应用类加载器
* @author: zhangbing
* @create: 2020-06-09 13:59
**/
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
/**
* 重写loadClass 打破双亲委派机制
*
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先,查找当前加载器是否已经加载过,已经加载过 则直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
//当前加载器没有加载过
long t0 = System.nanoTime();
if (!name.startsWith("main.jvm.classLoad")) {
//jdk的类 不允许打破双亲委派机制
c = getParent().loadClass(name);
} else {
//自定义的类 使用自定义加载器加载
c = findClass(name);
}
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadByte(name);
return defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadByte(String fullPathname) {
String path = fullPathname.replaceAll(".", "/");
byte[] bytes = null;
try {
FileInputStream fileInputStream = new FileInputStream(classPath + "/" + path + ".class");
int available = fileInputStream.available();
bytes = new byte[available];
fileInputStream.read(bytes);
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
public static void main(String[] args) {
//创建一个自定义加载器 传入自定义类路径
MyClassLoader myClassLoader = new MyClassLoader("D:/workspace/test/");
try {
Class<?> aClass = myClassLoader.loadClass("main.jvm.classLoad.User", false);
System.out.println("使用的类加载器:" + aClass.getClassLoader());
Object o = aClass.newInstance();
Method testMethod = aClass.getDeclaredMethod("testMethod", null);
testMethod.invoke(o, args);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码运行结果:
打破双亲委派机制成功!!!
我们熟知的tomcat内部就没有使用默认的双亲委派机制,内部打破了双亲委派机制。