运行main方法启动程序时,需要通过类加载器将这个类加载到JVM内存中。
加载过程
加载:在硬盘中查找并通过IO读入字节码文件,只加载使用到的类;
验证:是否符合JVM的要求规则;
准备:为静态变量分配内存,并且赋值为默认值;
public static int number = 66;
实际上变量 number 在准备阶段过后的初始值为 0 而不是66;
如果是final修饰的,那么在编译阶段会为 number 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 number赋值为 66。
解析:引用符号替换为直接符号,简单的说就是静态方法替换为指向数据所存内存的指针;
初始化:为静态变量初始化指定的值,执行静态代码块。
public class Test {
public static final int intNum = 100;
public static Student student = new Student();
static {
System.out.println("this is the static of Test");
}
public static void main(String[] args) {
Test test = new Test();
test.methodOne();
}
public void methodOne(){
System.out.println("this is the first method");
}
public static void methodTwo(){
System.out.println("this is the second method");
}
public void methodThree(){
System.out.println("this is the third method");
}
}
public class Student {
static {
System.out.println("this is a static of Student");
}
public Student(){
System.out.println("this is a Object of Student");
}
}
输出结果:
this is a static of Student
this is a Object of Student
this is the static of Test
this is the first method
从运行结果看出:首先初始化静态变量,然后再执行静态代码块,最后执行main方法,并且没有用到的方法都不会去加载。
代码执行流程图如下:
类加载器:
1.引导类加载器(BootstrapClassLoader):加载位于JRE的lib目录下的核心类库,如rt.jar、charsets.jar等。
2.扩展类加载器(ExtClassLoader):加载位于JRE的lib目录下的ext目录中的jar包。
3.应用类加载器(AppClassloader):加载ClassPath目录下的类,就是自己写的类。
示例代码:
public class JDKTest01 {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println("引导类加载器:"+String.class.getClassLoader());
System.out.println("扩展类加载器:"+CurveDB.class.getClassLoader().getClass().getName());
System.out.println("应用类加载器:"+JDKTest01.class.getClassLoader().getClass().getName());
System.out.println("");
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootsrapClassLoader = extClassLoader.getParent();
System.out.println("引导类加载器:"+ bootsrapClassLoader);
System.out.println("扩展类加载器:"+ extClassLoader);
System.out.println("应用类加载器:"+ appClassLoader);
System.out.println("");
System.out.println("引导类加载器加载的类文件如下:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0;i
运行结果:
Hello World!
引导类加载器:null
扩展类加载器:sun.misc.Launcher$ExtClassLoader
应用类加载器:sun.misc.Launcher$AppClassLoader
引导类加载器:null
扩展类加载器:sun.misc.Launcher$ExtClassLoader@29453f44
应用类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
引导类加载器加载的类文件如下:
file:/D:/Java/jdk1.8.0_131/jre/lib/resources.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/rt.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/sunrsasign.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/jsse.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/jce.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/charsets.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/jfr.jar
file:/D:/Java/jdk1.8.0_131/jre/classes
扩展类加载器加载的类文件如下:
D:\Java\jdk1.8.0_131\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
应用类加载器加载的类文件如下:
D:\Java\jdk1.8.0_131\jre\lib\charsets.jar
D:\Java\jdk1.8.0_131\jre\lib\deploy.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar
D:\Java\jdk1.8.0_131\jre\lib\javaws.jar
D:\Java\jdk1.8.0_131\jre\lib\jce.jar
D:\Java\jdk1.8.0_131\jre\lib\jfr.jar
D:\Java\jdk1.8.0_131\jre\lib\jfxswt.jar
D:\Java\jdk1.8.0_131\jre\lib\jsse.jar
D:\Java\jdk1.8.0_131\jre\lib\management-agent.jar
D:\Java\jdk1.8.0_131\jre\lib\plugin.jar
D:\Java\jdk1.8.0_131\jre\lib\resources.jar
D:\Java\jdk1.8.0_131\jre\lib\rt.jar
E:\ProjectsIdea\out\production\ProjectsIdea
D:\Program Files\JetBrains\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar
类加载器初始化过程:
C++调用java代码创建JVM启动器,即实例化sun.misc.Launcher(单例模式),源码如:
private static Launcher launcher = new Launcher();
...
public static Launcher getLauncher() {
return launcher;
}
在sun.misc.Launcher的构造方法中创建了两个类加载器:sun.misc.Launcher.AppClassLoader 和 sun.misc.Launcher.ExtClassloader。JVM默认使用Launcher的getLauncher()方法返回的类加载器AppClassLoader的实例加载我们自己的应用程序。Launcher的构造方法源码如下:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
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);
}
}
双亲委派机制:
JVM类加载器之间关系是亲子层级结构的,如下图
双亲委派机制,即加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器都在自己的加载类路径下都找不到目标类,那么就在自己的类加载路径中查到且载入目标类。 简而言之,先找父加载器加载,找不到则自己来加载。
下面请看加载器AppClassLoader加载类的双亲委派源码:
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.
//如果仍然没有找到,那么按顺序调用findClass去找那个类
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;
}
}
双亲委托机制设计的目的:(为什么要这么设计?)
1.沙箱安全机制:我们自己写的类的类名(包括路径)与核心API库中类名(包括路径)相同时,我们自己写这个类不会被加载,这样就可以防止核心类库被篡改。
2.避免重复加载:当父加载器已经加载了该类时,子加载器就没必要再加载一次,保证被加载类的唯一性。
下面请看一个示例:
package java.lang;
/**
* Created
*/
public class Math {
public static void main(String[] args) {
System.out.println("=============进入Math类===========");
}
}
为什么会报错?就是因为双亲委托机制,加载的类是核心库中的java.lang.Math,而这个类是没有main方法的。
全盘负责委托机制(也可称为当前类加载器负责机制):
自定义类加载器:
负责加载用户自定义路径下的类包。自定义加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法:loadClass(String,boolean),实现了双亲委派机制,还有个方法是findClass,默认实现是空方法。
下面是自定义加载器示例:
import java.io.FileInputStream;
import java.lang.reflect.Method;
/**
* Created
*/
public class MyClassLoader {
static class MyClassLoader1 extends ClassLoader{
private String classPath;
public MyClassLoader1(String classPath){
this.classPath = classPath;
}
private byte[] loadByte(String name)throws Exception{
name = name.replaceAll("\\.","/");
FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name,data,0,data.length);
}catch (Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String[] arg) throws Exception{
//初始化自定义类加载器,会先初始化父类ClassLoader,
// 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader1 classLoader1 = new MyClassLoader1("E:/jvm");
//E盘创建jvm/com/huigu/stu目录,将Student类的复制类Student1.class丢入该目录
Class clazz = classLoader1.loadClass("com.huigu.stu.Student1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("study",null);
method.invoke(obj,null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
public class Student {
private String name;
public void study(){
System.out.println("好好学习,天天向上");
}
}
运行结果:
好好学习,天天向上
test01.MyClassLoader$MyClassLoader1
打破双亲委派机制
技术实现:重写类加载方法,需要自定义类加载器加载的类不委派给双亲加载。代码示例如下:
import java.io.FileInputStream;
import java.lang.reflect.Method;
/**
* Created
*/
public class MyClassLoader {
static class MyClassLoader1 extends ClassLoader{
private String classPath;
public MyClassLoader1(String classPath){
this.classPath = classPath;
}
private byte[] loadByte(String name)throws Exception{
name = name.replaceAll("\\.","/");
FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name,data,0,data.length);
}catch (Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String[] arg) throws Exception{
//初始化自定义类加载器,会先初始化父类ClassLoader,
// 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader1 classLoader1 = new MyClassLoader1("E:/jvm");
Class clazz = classLoader1.loadClass("com.huigu.stu.Student");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("study",null);
method.invoke(obj,null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
//重写类加载方法,实现自己的加载逻辑,不为派给双亲加载
@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(!name.startsWith("com.huigu.stu")){
c = this.getParent().loadClass(name);
// c = super.loadClass(name,resolve);
}else{
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
}
return c;
}
}
}
}
public class Student {
private String name;
public void study(){
System.out.println("好好学习,天天向上");
}
}
运行结果:
好好学习,天天向上
test01.MyClassLoader$MyClassLoader1
那么,如果打破双亲委派机制,用自定义类加载器加载自己写的java.lang.Math.class 呢?请看下面代码:
import java.io.FileInputStream;
import java.lang.reflect.Method;
/**
* Created
*/
public class MyClassLoader {
static class MyClassLoader1 extends ClassLoader{
private String classPath;
public MyClassLoader1(String classPath){
this.classPath = classPath;
}
private byte[] loadByte(String name)throws Exception{
name = name.replaceAll("\\.","/");
FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name,data,0,data.length);
}catch (Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String[] arg) throws Exception{
//初始化自定义类加载器,会先初始化父类ClassLoader,
// 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader1 classLoader1 = new MyClassLoader1("E:/jvm");
Class clazz = classLoader1.loadClass("java.lang.Math");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("study",null);
method.invoke(obj,null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
//重写类加载方法,实现自己的加载逻辑,不为派给双亲加载
@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(!name.startsWith("java.lang.Math")){
c = this.getParent().loadClass(name);
// c = super.loadClass(name,resolve);
}else{
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
}
return c;
}
}
}
}
//自定义的Math类
package java.lang;
/**
* Created
*/
public class Math {
public static void main(String[] args) {
System.out.println("**************进入Math类**************");
}
}
运行结果:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at test01.MyClassLoader$MyClassLoader1.findClass(MyClassLoader.java:32)
at test01.MyClassLoader$MyClassLoader1.loadClass(MyClassLoader.java:69)
安全类型异常,禁止加载java.lang包中的类,出于安全性考虑,jvm是不允许自定义类加载器加载像java.lang这种核心包中的类的。
本篇博客就写到这里了,感谢您的观阅,如果对您有所帮助,希望给个关注,错误之处,欢迎指正,如有疑问,欢迎来询。下篇是tomcat打破双亲委派机制,期待再次相见。