Java通过latcher程序启动JVM,通过类加载器加载类,先加载类的元数据信息,包括类编译好的方法指令,这些信息先放在Method Area。
Method area中的数据就是运行时的数据(Runtime Data),而这些数据有一部分是指令,这些指令需要运行必须依靠线程。线程执行就需要stack和ProgramCounter,这两部分配合起来执行程序就需要使用heap(对象初始化需要heap),如果调用了JNI(java native interface)还需要Native memory
执行java的bytecode需要执行引擎,它包括JIT编译器(just in time)是一边编译一边执行,也就是一边将指令编译成机器码,一边执行机器码。编译过的程序不再编译第二次,也就是一次编译,到处运行。
GC是垃圾会收器和java内存分配的工具,即java堆的实际管理者。
本地库接口:任何语言都不可避免的要调用本地接口,比如Java的线程只是一个Object,它是没有办法执行程序的。只有操作系统可以执行,可以调用CPU,包括GC的内存分配也需要向操作系统要内存空间。本地库接口会调用本地方法来实现。
在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language表示Language类的地址。
loadClass与forName区别
Classloader.loadClass得到的class是还没有链接的。
Class.forName得到的class是已经初始化完成的
双亲名起的不准确,其实是父亲,父亲的父亲。
ClassLoader类型:
BootStrapClassLoader: 加载核心库java.*,由c++编写
ExtClassLoader:加载扩展库javax.*,由Java编写(jre/lib/ext)
AppClassLoader:加载程序所在目录,由Java编写(classpath)
自定义ClassLoader:定制化加载,由Java编写
优点:避免多个同样字节码对象(Class对象)的加载,比如以前A已经加载过一个类,会有一个该类的Class对象,B在使用时就不需要再new一个该类的Class对象,而是从当前的ClassLoader逐层向父类ClassLoader去查找该Class对象,如果有ClassLoader已经加载过该Class对象则不需要再创建该类的Class对象。
ClassLoader主要工作在Class装载的加载阶段,主要作用是从系统外部获得Class二进制数据流。
所有的Class都是由ClassLoader加载的,它负责将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
从ClassLoader源代码可知道,该类是一个抽象类。
Parent也是ClassLoader类型,说明ClassLoader是有多种类型的。
有一个重要方法是loadClass(String name),name是类名,这个方法返回一个Class类型的对象,如果没有找到则抛出ClassNotFoundException异常。
自定义类加载器
openclass.classloader/Tom.java
package openclass.classloader;
public class Tom {
static {
//static在初始化时执行
System.out.println("hello tom");
}
}
openclass.classloader /MyClassLoader.java
package openclass.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
//读取文件(tom.class)
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
}
openclass.classloader/ClassLoaderChecker.java
package openclass.classloader;
public class ClassLoaderChecker {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//第一个参数就class文件的路径,第二个是自定义类加载器的名字,可以随意取。
MyClassLoader m = new MyClassLoader("D:/workspace/jvmquestion/openclass/classloader/", "myClassLoader");
Class c = m.loadClass("openclass.classloader.Tom");
c.newInstance();
}
}
打破双亲委派机制的场景有很多:JDBC、JNDI、Tomcat等
因为双亲委派模型是JDK1.2之后才被引入,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时就已经存在,所以在面对已存在的用户自定义类加载器的实现代码时,Java设计者引入双亲委派模型时不得不做出一些妥协。在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,这是源于虚拟机进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的loadClass()。
如果想自定义类加载器,就需要继承ClassLoader,并重写findClass,如果想不遵循双亲委派的类加载顺序,还需要重写loadClass。