main方法是如何执行的?
这里有一个简单的java方法,以这个代码说明一下;
public class Demo1 {
public static void main(String[] args) {
System.err.println("Demo1");
}
}
1.编译成.class文件;
2.使用java命令调用这个calss文件;
3.java命令会启动c++程序,c++程序会启动jvm进程,实例化bootstartp类加载器再调用java的 sun.misc.Launcher.getLauncher() 方法实例化ExtClassLoader 和AppClassLoader;
4.完成类加载的流程
5.调用main方法的入口,执行方法;
类加载流程
1.加载;从磁盘,网络 可以获取的任何二进制输入流
2.验证:验证字节码文件的格式
3.准备:将字节码文件中的static变量设置默认的初始值,如果是final 则在这一步就完成了真实赋值;
4.解析:将字节码文件中的static方法的符号引用转为直接引用; 在字节码文件中方法的描述都是指向常量池中的字面量,真实调用时应该是指向某个内存地址; 这个转换过程就是符号引用转直接引用;
5.初始化:将字节码文件中的static变量赋值,执行static静态代码块;
字节码文件被加载到jvm之后,会在方法区存储。主要存储运行时常量池,接口信息,字段信息,方法信息,类加载器信息,对应的Class实例信息;
类加载器的信息:保留被哪个类加载器加载的信息;
对应的Class实例信息:字节码文件被加载到JVM后,会在堆区创建一个对应的Class实例,作为开发人员访问方法区类元信息的入口和切入点;
SUN.MISC.LAUNCHER
JVM启动时,创建BootStrap类加载器,调用sun.misc.launcher.getLauncher()方法;
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
//1.类加载时,在初始化阶段完成赋值,调用类的默认构造方法
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
//3.jvm调用这个方法时,此类已经实例化完成,返回对应的单例实例;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
//2.默认构造方法
Launcher.ExtClassLoader var1;
try {
//2.1创建extClassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//2.2将extClassLoader作为AppClassLoader的parent属性传入,创建AppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
注意:java中的类是懒加载的,只有使用时才会加载;如遇到new,getstatic,putstatic指令的时候;对应创建一个新对象,调用static方法,获得static变量;
public class LazyLoadDemo {
static {
System.err.println("LazyLoadDemo Class init");
}
public static void main(String[] args) {
A a=new A();
B b=null;
}
static public class A{
static {
System.err.println("A Class init");
}
}
static public class B{
public static String bb="bb";
static {
System.err.println("B Class init");
}
public B(){
System.err.println("B exam init");
}
}
}
main方法中模拟3种情况:
1.A a=new A();
B b=null;
输出
LazyLoadDemo Class init
A Class init 说明B类并未被加载;
2.A a=new A();
String s=B.bb;
输出
LazyLoadDemo Class init
A Class init
B Class init 说明B类被加载,但是并没有被实例化,static 属性的使用不会触发对象的实例化;
3.A a=new A();
B b=new B();
输出
LazyLoadDemo Class init
A Class init
B Class init
B exam init
各类加载器负责加载的内容
public class ClassLoaderDuty {
public static void main(String[] args) {
//rt.jar
System.out.println(Launcher.class.getClassLoader());
//ext/*
System.out.println(DESKeyFactory.class.getClassLoader());
//classpath
System.out.println(ClassLoaderDuty.class.getClassLoader());
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
URL[] urLs = bootstrapClassPath.getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
运行上述程序可以在控制台输出各ClassLoader加载的文件,AppClassLoader虽然输出很多,但是由于双亲委派机制的存在导致它的加载范围是有限的。
双亲委派机制
双亲委派机制指需要加载一个类时,先到当前的类加载器中寻找如果没有就委托父类去加载一直到BootStrap如果还没有找到,就再往下委托;
双亲委派机制保证了
沙箱安全:因为类加载是使用类的全限定名查找的,如果没有双亲委派,那我们自己开发一个java.lang.String 就可能把jdk的给替换掉;
避免类重复加载: 父加载器加载过的类避免子类重复加载;
双亲委派的实现代码主要是在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 {
//如果查找不到,委托给父类
//如果没有父类,委托给bootStrap
//例如: app 加载一个User.class --->委托给Ext --->ext 没有父类,只能委托给bootStrap
//bootStrap加载不到,返回C=null,继续往下执行Ext再自己去加载,Ext加载不到再返回给APP, App再自己去加载
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);
// 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实现了双亲委派的逻辑实现,真正加载类是调用了findClass方法;
下面我们自定义一个类加载器,分别实现这两个方法观察一下区别;
public class CustomClassLoader extends ClassLoader{
private String path;
public CustomClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data=new byte[0];
try {
data=loadByte(name);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name,data,0,data.length);
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.", "/");
String classPath=path+"/"+name+".class";
FileInputStream inputStream=new FileInputStream(classPath);
int available = inputStream.available();
byte[] data=new byte[available];
inputStream.read(data);
inputStream.close();;
return data;
}
}
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
CustomClassLoader classLoader=new CustomClassLoader("d:/Demo");
Class<?> demo1 = classLoader.loadClass("com.jtfu.jvm.Demo1");
System.err.println(demo1.getClassLoader());
}
}
可以看到输出的类加载器是 sun.misc.Launcher$AppClassLoader@18b4aac2, 为什么还是APP呢? 因为Demo1 这个Class文件还存在于ClassPath下,由于双亲委派机制会由APP来加载, 把ClassPath下的Demo1.class删除后,就会输出:com.jtfu.jvm.custom.CustomClassLoader@1540e19d
打破双亲委派
怎么打破双亲委派呢? 上面提到双亲委派的逻辑在loadClass方法中,那么我们重写这个方法;
public class CustomClassLoader1 extends ClassLoader{
private String path;
public CustomClassLoader1(String path) {
this.path = path;
}
@Override
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();
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;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data=new byte[0];
try {
data=loadByte(name);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name,data,0,data.length);
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.", "/");
String classPath=path+"/"+name+".class";
FileInputStream inputStream=new FileInputStream(classPath);
int available = inputStream.available();
byte[] data=new byte[available];
inputStream.read(data);
inputStream.close();;
return data;
}
}
public static void main(String[] args) throws ClassNotFoundException {
CustomClassLoader1 classLoader=new CustomClassLoader1("d:/Demo");
Class<?> demo1 = classLoader.loadClass("com.jtfu.jvm.Demo1");
System.err.println(demo1.getClassLoader());
}
注意,这里已经把上面测试删除的Demo1.class恢复了;
运行会提示错误, d:\Demo\java\lang\Object.class (系统找不到指定的文件。) 当然,java所有的类都继承Object,那该咋办呢?既然提示找不到那就在对应目录下创建一个Object.class好了
再次运行,发现还是报错提示 Prohibited package name: java.lang; 由于jvm的安全机制,不允许以这个包名命名;
再去重新修改一下代码,当只加载com.jtfu.jvm 的文件时,打破双亲委派机制;
if (c == null && name.startsWith("com.jtfu.jvm")) {
// 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();
}else {
c=this.getParent().loadClass(name);
}
再次运行成功输出自义定类加载器:com.jtfu.jvm.custom.CustomClassLoader1@1540e19d
注意
一个jvm中可以存在多个包名,类名相同的Class实例,这是因为他们的ClassLoader不同所以判断两个Class是否相同,还需要判断它们的类加载器是否相同! 下面的代码使用自定义类加载器加载的Demo1 与 ClassPath下的Demo1 包名,类名完全相同; 但是它们的类加载器不同;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
CustomClassLoader1 classLoader=new CustomClassLoader1("d:/Demo");
Class<?> demo1 = classLoader.loadClass("com.jtfu.jvm.Demo1");
Object d1 = demo1.newInstance();
System.err.println(d1);
Demo1 dd1=new Demo1();
System.err.println(dd1);
System.err.println(dd1.getClass().equals(d1.getClass()));
Demo1 dd2=new Demo1();
System.err.println(dd1.getClass().equals(dd2.getClass()));
}
}
com.jtfu.jvm.Demo1@14ae5a5
com.jtfu.jvm.Demo1@7f31245a
false
true
项目开发中实际应用到打破双亲委派机制的地方
1.数据库连接
2.tomcat自定义类加载器