首先一个class文件在硬盘里面
然后JVM去对它进行以下行为:
一般我们只要记住,静态变量的初始化分为两步,Linking时是默认值,Initializing后才是初始化的值
成员变量的初始化其实也是分两步,第一步申请内存空间时是默认值,第二步调用构造方法时才是初始化的值
一个class文件的Loading,load出两个东西:
1.class文件的二进制编码加载到内存
2.生成一个与之对应的Class对象,改对象指向内存中的class编码文件
如果打印一下String的ClassLoader.会发现结果为null:
System.out.println(String.class.getClassLoader());
这是因为:
最顶层的加载器Bootstrap是用C++来实现的,在JAVA中没有与之对应的类.
所以Bootstrap加载出来的类,比如String,获取到的ClassLoader是null.
类加载器的分层关系:
注意
1.这个上层加载器,即父加载器,是逻辑上的关系,其实就是一个成员变量
2.不是类的继承关系,那是另一种维度的关系
3.加载器也是一个对象,也要由另一个加载器加载,但并不一定是由他的parent加载,是谁不一定.最终都是由Bootstrap加载的
设一个加载器a的上层加载器是b,那么 a不一定是被b加载的
其实AppClassLoader和ExtClassLoader都是sun.misc.Launcher的内部类
(JDK11把Launcher换成了ClassLoaders)
Launcher里面可以看到这三个内部类,和他们各自加载的path
BootClassPathHolder:System.getProperty(“sun.boot.class.path”)
AppClassLoader:java.class.path
ExtClassLoader:java.ext.dirs
我们写个方法打印一下这些东西就看出来了:
public static void main(String[] args) {
System.out.println("boot-------------------");
System.out.println(System.getProperty("sun.boot.class.path").replaceAll(":", System.lineSeparator()));
System.out.println("ext-------------------");
System.out.println(System.getProperty("java.ext.dirs").replaceAll(":", System.lineSeparator()));
System.out.println("classpath-------------------");
System.out.println(System.getProperty("java.class.path").replaceAll(":", System.lineSeparator()));
}
双亲委派并不是指父母双方,而是指"查找类时从子到父,加载类时从父到子"的这么一个机制.
具体含义:
众所周知,ClassLoader加载完一个类后,会放入一个ClassCache,下次再用时就不需重复加载了.每个ClassLoader有自己的ClassCache
为啥不直接放到一个ClassCache里面,这样就不用层层查找了啊?
这里主要是出于安全考虑.
假设黑客小明自定义了一个java.lang.String对象,里面做了些非法操作;如果不分层查找的话,用户就会用到他自定义的String,阴谋得逞;
双亲委任机制下,使用String时,先看看父加载器是否已加载,直到找到Bootstrap后直接返回String类.
可以,自定义一个classLoader,重写loadClass方法就可以打破.
热加载/热部署的时候,可能会重写loadClass(),打破双亲委派机制
别的方法ClassLoader类已经写好啦(模板设计模式),
关键点就是下面的findClass方法,该方法直接抛异常,是个必须被子类重写的方法.(模板方法,钩子函数)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 这个查找调用了native方法,具体可能要看HotSpot或者其他JVM源码了,可以理解为一个"缓存"
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 然后调用parent的loadClass,parent也是先检查下是否已加载
// 然后调用parent.parent.loadClass或者findBootstrapClassOrNull
// 这里体现了双亲委派的第一步,查找类时从子到父
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();
// 这里需要各ClassLoader实现,需要去实现双亲委派的第二步,加载类时从父到子
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
CompilerAPI 动态编译,直接在内存中完成编译源码和加载Class,不需要经过硬盘了
我这里com.example.springboot2.test.Hello是另一个项目空间的类,test.Hi是自己的项目空间的类
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("MyClassLoader.findClass");
String parent = "/Users/liweizhi/IdeaProjects/wzdemo/springboot2/target/classes/";
// String parent = "/Users/liweizhi/IdeaProjects/wzdemo/demo1/target/classes";
File f = new File(parent, name.replace(".", "/").concat(".class"));
FileInputStream fis = null;
ByteArrayOutputStream bao = null;
try {
fis = new FileInputStream(f);
bao = new ByteArrayOutputStream();
/*int b = 0;
while ((b = fis.read()) != -1) {
bao.write(b);
}*/
byte[] buffer = new byte[1024];
for (int len = 0; (len = fis.read(buffer)) != -1; ) {
bao.write(buffer, 0, len);
}
byte[] bytes = bao.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bao != null) {
try {
bao.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return super.findClass(name);
}
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader classLoader = new MyClassLoader();
// String className = "com.example.springboot2.test.Hello";
String className = "test.Hi";
Class<?> aClass = classLoader.loadClass(className);
System.out.println(aClass.getClassLoader());
}
}
这里涉及到一个加载顺序的问题
public class InitializingTest {
public static void main(String[] args) throws ClassNotFoundException {
// System.out.println(Parent.a); // 先加载Parent,然后打印a
// System.out.println(Parent.s);
// System.out.println(Child.q); // 先加载Parent, 后加载Child, 然后打印q
// System.out.println(Child.w);
// Class.forName("com.example.demo.jvm.InitializingTest$Parent");
// 这个和打印Child.q时一样, 其实就是当第一次打印Child.q,去加载了
// Class.forName("com.example.demo.jvm.InitializingTest$Child");
Child child = new Child();
}
static class Parent {
static int a = 1;
static final int s = 2;
static {
System.out.println("Parent static block");
}
{
System.out.println("Patent block");
}
public Parent() {
System.out.println("Patent constructor");
}
}
static class Child extends Parent {
static int q = 3;
static final int w = 4;
static {
System.out.println("Child static block");
}
{
System.out.println("Child block");
}
public Child() {
System.out.println("Child constructor");
}
}
}
答:默认是混合模式,解释器+JIT,当某个方法调用很频繁时就走JIT
也可以指定为单纯的解释性/编译型
搞个测试类,求证一下:
我的电脑上,默认大概五六秒完成;
加了-Xmixed后还是五六秒;
加了-Xint后,过了很久还没完事,不等了,反正时间很长了
加了-Xcomp后,四五秒完事,比混合模式稍微快一些;但是前面编译时间略久,会产生运行比混合模式还慢的感觉.
public class T009_WayToRun {
static int count = 10_0000;
public static void main(String[] args) {
for (int i = 0; i < count; i++) {
m();
}
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
m();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void m() {
for (long i = 0; i < count; i++) {
long j = i % 3;
}
}
}