一、java命令执行代码大致流程:
loadClass类加载过程有如下几步:
加载 => 验证 => 准备 => 解析 => 初始化 => 使用 => 卸载
加载:在硬盘上查找并通过IO读取字节码文件。使用到的类才会被加载,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为这个类在各种数据的访问入口(方法区的入口)
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:将符号引用替换为直接引用。该阶段会将一些静态方法替换为指向数据实际存储内存的指针或句柄(即将符号引用替换为直接引用)。这就是静态链接的过程(在加载期间完成的)。而动态链接是在程序运行期间完成的将符号引用替换为直接引用的过程。
初始化:对类的静态变量初始化为指定的值,执行静态代码块。
类被加载到方法区后,主要包含:运行时常量池,类型信息,字段信息,方法信息,类加载器的引用,对应class实例的引用等信息。
1)类加载器的引用:这个类到类加载器实例的引用
2)对应class实例的引用:类加载器在加载类信息放到方法区后,他创建一个对应的class类型的对象实例放到堆中,作为程序访问方法区中类定义的入口和切入点。
注意:JVM加载类的过程是按需加载,如果主类在运行过程中要使用其他的类,会在使用时逐步加载这些类。
public class ClassLoaderTest {
@Test
public void loadClass01(){
new A();
System.out.println("*************load test************");
B b = null;
}
}
class A{
static{
System.out.println("this is class A");
}
public A() {
System.out.println("initialize A");
}
}
class B{
static{
System.out.println("this is class B");
}
public B() {
System.out.println("initialize B");
}
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
执行结果如下:
this is class A
initialize A
*************load test************
虽然创建了B的变量,但是并没有实例化,只会创建一个类放到内存中。
二、类加载器和双亲委派机制
类加载的过程主要通过类加载器实现,JAVA中的类加载器:
1)引导类加载器(bootstrapClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库。如dt.jar
2)扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的Jar包
3)应用程序类加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要是加载项目中业务和技术代码。
4)自定义类加载器:负载加载用户指定的路径下的类包
public class ClassLoaderTest {
@Test
public void classLoader01(){
System.out.println("String:" + String.class.getClassLoader());
System.out.println("DESKeyFactory:" + DESKeyFactory.class.getClassLoader());
System.out.println("ClassLoaderTest:" + ClassLoaderTest.class.getClassLoader());
System.out.println("************************************************************************");
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("bootstrapLoader:" + bootstrapClassLoader);
System.out.println("extClassLoader" + extClassLoader);
System.out.println("appClassLoader:" + appClassLoader);
System.out.println("************************************************************************");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for(URL url : urLs){
System.out.println(url);
}
System.out.println("************************************************************************");
System.out.println("extClassLoader load file:" + System.getProperty("java.ext.dirs"));
System.out.println("************************************************************************");
System.out.println("appClassLoader load file:" + System.getProperty("java.class.path"));
}
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
执行结果:
String:null
DESKeyFactory:sun.misc.Launcher$ExtClassLoader@6d03e736
ClassLoaderTest:sun.misc.Launcher$AppClassLoader@18b4aac2
************************************************************************
bootstrapLoader:null
extClassLoadersun.misc.Launcher$ExtClassLoader@6d03e736
appClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
************************************************************************
file:/D:/a_install/jdk1.8.0_162/jre/lib/resources.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/rt.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/sunrsasign.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/jsse.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/jce.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/charsets.jar
file:/D:/a_install/jdk1.8.0_162/jre/lib/jfr.jar
file:/D:/a_install/jdk1.8.0_162/jre/classes
************************************************************************
extClassLoader load file:D:\a_install\jdk1.8.0_162\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
************************************************************************
appClassLoader load file:D:\a_install\idea\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar;D:\a_install\idea\IntelliJ IDEA 2019.3.3\plugins\junit\lib\junit5-rt.jar;D:\a_install\idea\IntelliJ IDEA 2019.3.3\plugins\junit\lib\junit-rt.jar;D:\a_install\jdk1.8.0_162\jre\lib\charsets.jar;D:\a_install\jdk1.8.0_162\jre\lib\deploy.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\access-bridge-64.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\cldrdata.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\dnsns.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\jaccess.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\jfxrt.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\localedata.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\nashorn.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunec.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunjce_provider.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunmscapi.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\sunpkcs11.jar;D:\a_install\jdk1.8.0_162\jre\lib\ext\zipfs.jar;D:\a_install\jdk1.8.0_162\jre\lib\javaws.jar;D:\a_install\jdk1.8.0_162\jre\lib\jce.jar;D:\a_install\jdk1.8.0_162\jre\lib\jfr.jar;D:\a_install\jdk1.8.0_162\jre\lib\jfxswt.jar;D:\a_install\jdk1.8.0_162\jre\lib\jsse.jar;D:\a_install\jdk1.8.0_162\jre\lib\management-agent.jar;D:\a_install\jdk1.8.0_162\jre\lib\plugin.jar;D:\a_install\jdk1.8.0_162\jre\lib\resources.jar;D:\a_install\jdk1.8.0_162\jre\lib\rt.jar;D:\projects\extraTools\FileProcessor\target\test-classes;D:\projects\extraTools\FileProcessor\target\classes;D:\a_install\maven\repository\com\alibaba\fastjson\1.2.47\fastjson-1.2.47.jar;D:\a_install\maven\repository\org\apache\poi\poi\4.0.1\poi-4.0.1.jar;D:\a_install\maven\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\a_install\maven\repository\org\apache\commons\commons-collections4\4.2\commons-collections4-4.2.jar;D:\a_install\maven\repository\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;D:\a_install\maven\repository\org\apache\poi\poi-ooxml\4.0.1\poi-ooxml-4.0.1.jar;D:\a_install\maven\repository\org\apache\poi\poi-ooxml-schemas\4.0.1\poi-ooxml-schemas-4.0.1.jar;D:\a_install\maven\repository\org\apache\xmlbeans\xmlbeans\3.0.2\xmlbeans-3.0.2.jar;D:\a_install\maven\repository\org\apache\commons\commons-compress\1.18\commons-compress-1.18.jar;D:\a_install\maven\repository\com\github\virtuald\curvesapi\1.05\curvesapi-1.05.jar;D:\a_install\maven\repository\dom4j\dom4j\1.6.1\dom4j-1.6.1.jar;D:\a_install\maven\repository\xml-apis\xml-apis\1.0.b2\xml-apis-1.0.b2.jar;D:\a_install\maven\repository\junit\junit\4.12\junit-4.12.jar;D:\a_install\maven\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\a_install\maven\repository\com\ibm\db2\jcc\db2-spring-framework\0.0.1\db2-spring-framework-0.0.1.jar;D:\a_install\maven\repository\com\ibm\db2\jcc\11.5.0.0\jcc-11.5.0.0.jar;D:\a_install\maven\repository\commons-io\commons-io\2.5\commons-io-2.5.jar;D:\a_install\maven\repository\org\freemarker\freemarker\2.3.31\freemarker-2.3.31.jar;D:\a_install\maven\repository\com\ibm\db2\jcc\db2jcc\db2jcc4\db2jcc-db2jcc4.jar;D:\a_install\maven\repository\org\springframework\spring-webmvc\5.2.11.RELEASE\spring-webmvc-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-aop\5.2.11.RELEASE\spring-aop-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-beans\5.2.11.RELEASE\spring-beans-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-context\5.2.11.RELEASE\spring-context-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-core\5.2.11.RELEASE\spring-core-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-jcl\5.2.11.RELEASE\spring-jcl-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-expression\5.2.11.RELEASE\spring-expression-5.2.11.RELEASE.jar;D:\a_install\maven\repository\org\springframework\spring-web\5.2.11.RELEASE\spring-web-5.2.11.RELEASE.jar;D:\a_install\idea\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar
关注点及说明:
1)bootstrapClassLoader是C++的类加载器,java中为null
2)bootstrapClassLoader加载的核心类库
3)extClassLoader加载的ext扩展目录的类库
4)appClassLoader加载的classpath目录下的jar包
类加载器初始化过程:
C++创建JVM启动器实例sun.misc.Launcher。sun.misc.Launcher初始化使用了单例模式,保证虚拟机只有sun.misc.Launcher实例。
Launcher类的构造方法内部创建了两个类加载器:ExtClassLoader和AppClassLoader,JVM默认使用Launcher类的getClassLoader()方法返回的类加载器(类型为应用类加载器)的实例来加载应用程序的类
双亲委派机制:
原理:当加载一个类时,当前类加载器(可能是应用类加载器,也可能是自定义类加载器)会先在自己已加载的类中查询是否已经加载该类,如果未加载,则委托父类加载器尝试加载这个类,父类加载器同逻辑处理,直至引导类加载器(bootstrapClassLoader),如果引导类加载器在已加载类中未查询到该类,则到引导类加载路径尝试加载类,然后返回加载结果给扩展类加载器,扩展类加载器判断类已被加载,则直接返回;否则扩展类加载器会到自己的加载路径尝试加载类,然后返回加载结果给应用类加载器,应用类加载器判断类已被加载就返回,如果未被加载就到应用类加载路径尝试加载类,如果加载成功就把该类,如果加载失败就报ClassNotFoundException(如果有自定义类加载器,应用类加载器会)。
双亲委派机制优点:
1)水箱安全机制:防止核心API类库被随意篡改。比如:java.lang.String类手写版也不会被加载,只会加载核心类库中的java.lang.String类
2)避免类的重复加载:保证被加载的类的唯一性。一个类被加载一次后,就不会再进行重复加载。
全盘负责委托机制:
全盘负责:当一个ClassLoader装载一个类时,除非显式的使用另一个ClassLoader,否则该类及该类所依赖的类和该类引用的类都由这个ClassLoader装载。
自定义类加载器:
自定义类加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法,loadClass(String,boolean),用来实现双亲委派机制;findClass(String),默认为空方法,需要自定义类加载器重写findClass(String)方法。
package com.jvm.tt;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
private byte[] loadByte(String name) throws Exception{
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(new File(classPath + "/" + name + ".class"));
int available = fis.available();
byte[] data = new byte[available];
fis.read(data);
fis.close();
return data;
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
Class<?> clazz = myClassLoader.loadClass("com.jvm.tt.Study");
Object obj = clazz.newInstance();
Method main = clazz.getMethod("doStudy");
main.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
class Study {
public Study() {
}
public void doStudy(){
System.out.println("studying");
}
}
+++++++++++++++++++++++++++
执行结果:
studying
sun.misc.Launcher$AppClassLoader
打破双亲委派机制:
package com.jvm.tt;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class NoParentClassLoader extends ClassLoader{
private String classPath;
public NoParentClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception{
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(new File(classPath + "/" + name + ".class"));
int available = fis.available();
byte[] data = new byte[available];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)){
// First, check if the class has already been loaded
Class<?> loadedClass = findLoadedClass(name);
if(loadedClass == null){
// If still not found, then invoke findClass in order to find the class
loadedClass = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(System.nanoTime());
sun.misc.PerfCounter.getFindClasses().increment();
}
if(resolve){
resolveClass(loadedClass);
}
return loadedClass;
}
}
public static void main(String[] args) throws Exception {
NoParentClassLoader classLoader = new NoParentClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
//尝试用自己改写类加载机制去加载自己写的java.lang.String.class
Class<?> clazz = classLoader.loadClass("java.lang.Integer");
// Object obj = ;
Method method = clazz.getMethod("sout");
System.out.println(method.invoke(clazz.newInstance()));
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
++++++++++++++++++++++++++++++++++++
输出结果:
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 com.jvm.tt.NoParentClassLoader.findClass(NoParentClassLoader.java:30)
at com.jvm.tt.NoParentClassLoader.loadClass(NoParentClassLoader.java:51)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.jvm.tt.NoParentClassLoader.main(NoParentClassLoader.java:66)
Exception in thread "main" java.lang.ClassNotFoundException
at com.jvm.tt.NoParentClassLoader.findClass(NoParentClassLoader.java:33)
at com.jvm.tt.NoParentClassLoader.loadClass(NoParentClassLoader.java:51)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.jvm.tt.NoParentClassLoader.main(NoParentClassLoader.java:66)
在调用自定义的findClass时,defineClass方法会报Prohibited package name: java.lang错误,然后throw ClassNotFoundException才打印的下面ClassNotFoundException的错。
Tomcat打破双亲委派机制:
原因:
1、Tomcat作为一个web容器,当需要部署两套不同的应用,且这两套应用会依赖同一个第三方库的不同版本,所以要求每个应用程序的类库都是相对独立的。
2、部署在同一个web容器中的相同类库的相同版本应该可以共享。
3、web容器有自己的类库,且不与应用程序混淆。
4、web容器要支持jsp文件的热加载,即应用程序运行后,可以随时修改jsp文件,且可以即时生效,不需要重启。
Tomcat自定义类加载器详解
tomcat的几个主要类加载器:
commonClassLoader: tomcat最基本的类加载器,加载路径中的class可以被tomcat容器本身以及各个webapp访问
catalinaClassLoader:tomcat容器私有的类加载器,加载路径中的class只对tomcat容器可见,对webapp不可见
sharedClassLoader:各个webapp共享的类加载器,加载路径中的class对于所有的webapp可见,但是对于tomcat不可见
WebappClassLoader:各个webapp私有的类加载器,加载路径中的class只对当前webapp可见。如加载war包中的类,每个war包有自己的WebappClassLoader,实现相互隔离。
如图所示:
CommonClassLoader能加载的类,可以被CatalinaClassLoader和SharedClassLoader使用,是共有类库。而CatalinaClassLoader和SharedClassLoader能加载的类则是相互隔离的。
WebappClassLoader可以使用SharedClasLoader加载到的类,但各个WebappClassLoader加载的类库之间是相互隔离的。
JasperClassLoader的加载范围仅是这个JSP文件所编译出来的那个.class文件。当web容器检测到JSP文件被修改,会替换掉当前的JasperClassLoader实例,然后新建一个JSP类加载器来实现JSP文件的热加载功能。
每个WebappClassLoader加载自己目录下的class文件,且不会传递给父类加载器,打破了双亲委派机制。
模拟tomcat的WebappClassLoader加载自己war包应用,实现应用包内不同版本的类库相互共存与隔离
package com.jvm.tt;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class BreakParentClassLoader extends ClassLoader {
private String classPath;
public BreakParentClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception{
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(new File(classPath + "/" + name + ".class"));
int available = fis.available();
byte[] data = new byte[available];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)){
// First, check if the class has already been loaded
Class<?> loadedClass = findLoadedClass(name);
if(loadedClass == null){
// If still not found, then invoke findClass in order to find the class
long nanoTime = System.nanoTime();
//if not self-define class, call parent class loader, else load class by self-define class loader
if(!name.startsWith("com.jvm.tt")){
loadedClass = getParent().loadClass(name);
}else{
loadedClass = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(System.nanoTime());
sun.misc.PerfCounter.getFindClasses().increment();
}
if(resolve){
resolveClass(loadedClass);
}
return loadedClass;
}
}
public static void main(String[] args) throws Exception {
BreakParentClassLoader classLoader = new BreakParentClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
Class<?> clazz = classLoader.loadClass("com.jvm.tt.entity.Student");
Object obj = clazz.newInstance();
Method method = clazz.getMethod("studying");
method.invoke(obj);
System.out.println(clazz.getClassLoader());
System.out.println("*******************************************");
BreakParentClassLoader selfClassLoader = new BreakParentClassLoader("D:\\extra\\keepLearning\\TT\\SpringMVC\\JVMTT\\target\\classes");
Class<?> selfClass = selfClassLoader.loadClass("com.jvm.tt.entity.Student");
Object selfObj = selfClass.newInstance();
Method selfMethod = selfClass.getMethod("studying");
selfMethod.invoke(selfObj);
System.out.println(selfClass.getClassLoader());
}
}
+++++++++++++++++++++++++++++++++++++++++++++++
运行结果:
=======自己的加载器加载类调用方法=======
com.jvm.tt.BreakParentClassLoader@14ae5a5
*******************************************
=======自己的加载器加载类调用方法=======
com.jvm.tt.BreakParentClassLoader@135fbaa4
注意:同一个JVM中,两个相同路径、相同类名的类对象可以共存,它们的类加载器不同,所以它们不是同一个类。