写在前面
当我们通过javac命令将java源代码编译为Java字节码后,必须通过类加载器将其加载到jvm中才能运行,所以类加载器是jvm中非常重要的一个组成部分,本文我们就一起来看下吧!
1:类的生命周期
类的生命周期如下图:
注意以上包括卸载在内的所有过程都是通过类加载器完成的。
1.1:加载
将class字节码文件加载到jvm中,具体就是通过类加载器来完成的。class字节码本身的二进制信息会被加载到jvm的方法区中,并在堆中创建一个对应的java.lang.Class对象实例(java中万物皆对象,class字节码也不例外!)
,参考下图:
1.2:验证
首先通过魔法数字验证这是否是一个class字节码文件,java的class字节码使用前4个字节来进行标记cafebabe
,如下使用submit打开查看:
可以类比pdf文件的魔数是"%PDF",则其十六进制表示就是2550 4446
,如下图:
除了验证魔数之外,还会验证版本号,即class字节码文件可运行的jdk版本,如下就是版本53,即jdk1.9(52就是1.8,51就是1.7,50就是1.6,以此类推)
:
除了对于魔数和版本号的验证之外,还包括对字节码文件本身格式的验证等。
1.3:准备
为类的静态变量在方法区中分配内存,并给零值,即基础数据类型的默认值,如整型为0,布尔为false,具体如下:
假定有如下类:
class A {
static int num1 = 90;
staic boolean isOk = true;
...
}
则经过准备阶段后如下图:
注意这个阶段并不会将num1赋值为程序中定义的90,isOK也不会赋值为程序中定义的true,而只是会给默认值,因为本步骤的主要是为静态变量们找到容身之所
,具体的赋值完成要等在初始化阶段完成。
1.4:解析
将常量池中的符号引用解析为实际引用,即将将使用到的类名称,如com.xx.A,解析为A类在方法区中的内存地址,这样当需要A类时就可以直接找到。所以解析就是将一个代表了某个信息的文本字符串转换为方法区中的某个内存地址。
1.5:初始化
执行构造器代码,执行静态代码块,为类静态变量赋值(还记得在准备阶段我们已经将类静态变量分配到方法去中内存并给默认值了吗?这里会给程序设置的实际值)
。如下代码就会在该阶段被执行:
class A {
static {
sout("静态代码块逻辑");
}
public A() {
sout("构造函数逻辑");
}
static {
sout("静态代码块逻辑");
}
}
注意{}代码块不会被执行,因为代码块和普通类成员变量一样是属于类实例的,因此只有在创建类实例对象的时候才会执行。
2:类何时加载并初始化
2.1:运行main所在类
当我们通过main函数启动程序时,该main函数所在的会被加载并初始化,即如下操作:
2.2:new指令的目标类
当我们通过new关键字创建一个类实例对象时,该类对应的class字节码会被加载并初始化,如下:
2.3:invokestatic指令访问类静态方法
当调用某静态方法时,该静态方法所在的类会被初始化,如下:
2.4:getstatic指令访问静态类成员变量
当访问某静态变量时,该静态变量所在的类会被加载并初始化,如下:
2.5:子类的初始化会触发父类初始化
通过extends关键字继承的父类会随着本类的初始化而初始化,如下:
2.6:有default方法的接口子类初始化
当一个接口有default方法时,如果有其子类被初始化,则该接口也会被初始化,因为default方法是提供了具体实现的方法,是可以直接被子类使用的如下:
2.7:通过反射API创建某类实例时
这种情况通new指令,因为都是要创建对象实例,所以肯定是需要初始化的。
2.8:MethodHandle调用方法时
MethodHandle调用方法时,方法所在的类会被加载并初始化如下:
有些时候类会被加载但不会被初始化,接着来看下。
3:类何时加载但不初始化
3.1:通过子类引用父类的静态字段
因为此时只要能够访问到父类的静态就行了,并不需要子类初始化,如下操作:
- 定义类A,并定义静态变量
package yudaosourcecode.csdn.huohuo;
public class A {
static {
System.out.println("A 初始化了");
}
static int num1 = 90;
}
- 定义A类的子类ASon
package yudaosourcecode.csdn.huohuo;
public class ASon extends A {
static {
System.out.println("ASon 初始化了");
}
}
- 定义B类通过ASon访问A的静态变量
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) {
System.out.println(ASon.num1);
}
}
- 运行B输出如下
可以看到A类初始化了,ASon并没有初始化。
3.2:定义对象数组不会触发该类的初始化
因为此时只需要保证jvm中有该类的class即可,还没有使用,所以不会初始化,如下测试:
- 定义类A
package yudaosourcecode.csdn.huohuo;
public class A {
static {
System.out.println("A 初始化了");
}
static int num1 = 90;
}
- 定义类B,在其中定义A数组
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) {
A[] as = {};
}
}
运行,不会看到A 初始化了
说明A并没有初始化:
3.3:应用某类的常量不会初始化该类
因为这种方式在编译时就是将常量值存入到常量池中,所以并不需要常量所在类初始化,如下:
- 定义类A并定义常量
package yudaosourcecode.csdn.huohuo;
public class A {
final static String name = "jack";
static {
System.out.println("A 初始化了");
}
static int num1 = 90;
}
- 定义类B并访问A的常量
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) {
System.out.println(A.name);
}
}
运行,A并没有初始化,如下:
因为常量是不能在运行期被更改的,所以可以在编译器直接存入方法区中的常量池中,而不需要动态获取,但是普通静态变量不同,静态变量是可以程序运行的过程中被修改的,所以访问静态变量会导致静态变量所在类的初始化。
3.4:通过.class获取类的Class对象不会导致初始化
因为class字节码的Class对象在加载
阶段就已经创建完毕并分配到堆上了,所以并不会进行初始化。如下:
- 定义A类
package yudaosourcecode.csdn.huohuo;
public class A {
final static String name = "jack";
static {
System.out.println("A 初始化了");
}
static int num1 = 90;
}
- 定义B类,访问A类的class对象
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) {
System.out.println(A.class);
}
}
访问测试,A并没有初始化:
3.5:Class.forName设置initialize false不会初始化
initialize参数使用来告诉虚拟机是否要初始化指定类,false不初始化,true初始化,如下:
- 定义A类
package yudaosourcecode.csdn.huohuo;
public class A {
static {
System.out.println("A 初始化了");
}
static int num1 = 90;
}
- 定义B类,initialize设置为false
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) throws Exception {
Class.forName("yudaosourcecode.csdn.huohuo.A", false, B.class.getClassLoader());
}
}
运行:
可以看到A类并没有初始化。
如果使用 Class.forName("yudaosourcecode.csdn.huohuo.A");
,此时initialize默认为true,则会初始化A类,如下:
3.6:通过ClassLoader的loadClass
通过ClassLoader的loadClass也是只会加载而不会初始化。如下:
- 定义A类
package yudaosourcecode.csdn.huohuo;
public class A {
static {
System.out.println("A 初始化了");
}
static int num1 = 90;
}
- 定义B类使用类加载器加载A类
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) throws Exception {
B.class.getClassLoader().loadClass("yudaosourcecode.csdn.huohuo.A");
}
}
运行:
4:3种类加载器
4.1:3种类加载器以及其特点
三种类加载器分别是启动类加载器BootstrapClassLoader,扩展类加载器ExtClassLoader,应用类加载器AppCLassLoader,其职责如下:
启动类加载器BootstrapClassLoader:负责加载jre核心类库,如jre/lib/rt.jar
扩展类加载器ExtClassLoader:负责加载lib/ext,或者java.ext.dirs指定目录的jar和class
应用类加载器AppClassLoader:负责加载-classpath,-cp,java.class.path属性指定的类路径
特点如下:
双亲委派:当需要根据类全限定名称加载某个class时,并不会自己直接加载,而是先找其父类加载器加载,当最终也没有加载类时则会报出ClassNotFoundException
负责依赖:当加载的类,依赖于其它类时,也会加载这些依赖的类
加载缓存:已经加载的类会进行缓存,避免重复加载
其中还有第四种类加载器,就是自定义的类加载器,如下图:
4.2:自定义类加载器
假定我们现在有这样的一个需求,有一个类机密程度非常高,其.class字节码文件不能暴露出去,因为一旦暴露出去就可以很容易的通过反编译工具,甚至是通过javap来读指令,获取源码信息,这个时候普通的类加载器就无法满足需求了,我们就需要来自定义类加载器,该类加载器可以加载一个.class加密后的字符串来加载类,简单起见,我们就用base64来模拟加密后的.class字节码,则假定我们有如下的类:
package yudaosourcecode.csdn.huohuo;
public class A {
static {
System.out.println("A 初始化了");
}
}
接着我们执行如下操作,获取类对应字节码的加密结果这里是base64
:
bogon:huohuo xb$ javac -d . A.java
bogon:huohuo xb$ java yudaosourcecode/csdn/huohuo/A
A 初始化了
bogon:huohuo xb$ base64 yudaosourcecode/csdn/huohuo/A.class
yv66vgAAADUAHgoABgAQCQARABIIABMKABQAFQcAFgcAFwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEABkEuamF2YQwABwAIBwAYDAAZABoBAA5BIOWIneWni+WMluS6hgcAGwwAHAAdAQAdeXVkYW9zb3VyY2Vjb2RlL2NzZG4vaHVvaHVvL0EBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAwAJAAsADAABAAkAAAAZAAAAAQAAAAGxAAAAAQAKAAAABgABAAAACQAIAA0ACAABAAkAAAAlAAIAAAAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAFAAgABgABAA4AAAACAA8=
这里的base64后的结果我们就可以在自定义类加载器中使用了,自定义类加载器如下:
package yudaosourcecode.csdn.huohuo;
import java.util.Base64;
public class ACLassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String helloBase64 = "yv66vgAAADUAHgoABgAQCQARABIIABMKABQAFQcAFgcAFwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEABkEuamF2YQwABwAIBwAYDAAZABoBAA5BIOWIneWni+WMluS6hgcAGwwAHAAdAQAdeXVkYW9zb3VyY2Vjb2RlL2NzZG4vaHVvaHVvL0EBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAwAJAAsADAABAAkAAAAZAAAAAQAAAAGxAAAAAQAKAAAABgABAAAACQAIAA0ACAABAAkAAAAlAAIAAAAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAFAAgABgABAA4AAAACAA8=";
byte[] bytes = decode(helloBase64);
return defineClass(name,bytes,0,bytes.length);
}
public byte[] decode(String base64){
return Base64.getDecoder().decode(base64);
}
}
然后使用自定义类加载器加载我们的加密类
:
package yudaosourcecode.csdn.huohuo;
public class B {
public static void main(String[] args) throws Exception {
new ACLassLoader().loadClass("yudaosourcecode.csdn.huohuo.A").newInstance();
}
}
运行:
5:利用类加载器帮我们排查问题
5.1:找不到jar包
在工作中经常遇到这样问题,明明jar包已经加到classpath中,但就是找不到,这个时候我们能不能明确的获取各种类加载器加载了哪些jar包和class,从而定位问题呢?是可以的,定义如下类:
package yudaosourcecode.csdn.huohuo;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
public class A {
public static void main(String[] args) {
// 启动类加载器
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
System.out.println("启动类加载器");
for (URL url : urls) {
System.out.println(" ==> " + url.toExternalForm());
}
// 扩展类加载器
printClassLoader("扩展类加载器", A.class.getClassLoader().getParent());
// 应用类加载器
printClassLoader("应用类加载器", A.class.getClassLoader());
}
public static void printClassLoader(String name, ClassLoader CL) {
if (CL != null) {
System.out.println(name + " ClassLoader ‐> " + CL.toString());
printURLForClassLoader(CL);
} else {
System.out.println(name + " ClassLoader ‐> null");
}
}
public static void printURLForClassLoader(ClassLoader CL) {
Object ucp = insightField(CL, "ucp");
Object path = insightField(ucp, "path");
ArrayList ps = (ArrayList) path;
for (Object p : ps) {
System.out.println(" ==> " + p.toString());
}
}
private static Object insightField(Object obj, String fName) {
try {
Field f = null;
if (obj instanceof URLClassLoader) {
f = URLClassLoader.class.getDeclaredField(fName);
} else {
f = obj.getClass().getDeclaredField(fName);
}
f.setAccessible(true);
return f.get(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
运行,注意使用jdk8:
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/javac -d . A.java
A.java:11: 警告: Launcher是内部专用 API, 可能会在未来发行版中删除
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
^
1 个警告
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/java yudaosourcecode/csdn/huohuo/A
启动类加载器
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/resources.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/sunrsasign.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jsse.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jce.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/charsets.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jfr.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/classes
扩展类加载器 ClassLoader ‐> sun.misc.Launcher$ExtClassLoader@7852e922
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunec.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/nashorn.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/dnsns.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/localedata.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/jaccess.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/zipfs.jar
==> file:/System/Library/Java/Extensions/MRJToolkit.jar
应用类加载器 ClassLoader ‐> sun.misc.Launcher$AppClassLoader@2a139a55
==> file:/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/src/main/java/yudaosourcecode/csdn/huohuo/
可以很清晰的看到启动类加载器,扩展类加载器,应用类加载器都加载了哪些jar和class。
5.2:NoSuchMethodError
如果是通过5.1:找不到jar包
确定jar包肯定加载了,则就要看下是不是加载了不同版本的jar,即jar冲突了,一般是这个原因。
5.3:如何看类加载顺序
5.2:NoSuchMethodError
如果是因为冲突造成的,则可以通过这里的方法来进一步定位问题,需要在java命令中增加‐XX:+TraceClassLoading 或者 ‐verbose 即可,如下:
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/java -XX:+TraceClassLoading yudaosourcecode/csdn/huohuo/A
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.String from /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar]
...
5.4:怎么修改启动类加载器和扩展类加载器加载内容
正常的,我们只需要rt.jar就行了,但默认的启动类加载器会加载很多其它的jar,此时可以通过‐Dsun.boot.class.path
来指定要加载的jar,如下:
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/java -Dsun.boot.class.path="/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar" yudaosourcecode/csdn/huohuo/A
启动类加载器
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar
扩展类加载器 ClassLoader ‐> sun.misc.Launcher$ExtClassLoader@7852e922
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/ext/sunec.jar
...
可以看到此时启动类加载器只加载了rt.jar,对于扩展类加载器,正常其默认加载的jar包我们都是使用不到的,所以可以通过‐Djava.ext.dirs将其设置为空,即不加载,如下:
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/java -Djava.ext.dirs="" yudaosourcecode/csdn/huohuo/A
启动类加载器
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/resources.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/rt.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/sunrsasign.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jsse.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jce.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/charsets.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/jfr.jar
==> file:/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/classes
扩展类加载器 ClassLoader ‐> sun.misc.Launcher$ExtClassLoader@15db9742
应用类加载器 ClassLoader ‐> sun.misc.Launcher$AppClassLoader@73d16e93
==> file:/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/src/main/java/yudaosourcecode/csdn/huohuo/
此时扩展类加载器就什么都不加载了。
5.5:运行时动态加载类
有两种方式,第一种是通过自定义类加载器,第二种是通过URLClassLoader的addURL方法动态添加应用类加载器加载类时的扫描路径,分别看下。
5.5.1:自定义类加载器
- 定义要动态加载的类A
package yudaosourcecode.csdn.huohuo;
public class A {
static {
System.out.println("A初始化了!");
}
public static void main(String[] args) {
}
}
- 定义自定义类类加载器
package yudaosourcecode.csdn.huohuo;
import sun.misc.BASE64Encoder;
import java.io.File;
import java.io.FileInputStream;
import java.util.Base64;
public class ACLassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 这里的路径注意修改为自己的!!!
String path = "/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/src/main/java/yudaosourcecode/csdn/huohuo/yudaosourcecode/csdn/huohuo/A.class";
File file = new File(path);
byte[] byte1 = new byte[0];
try (FileInputStream fis = new FileInputStream(file)) {
byte1 = new byte[fis.available()];
fis.read(byte1);
} catch (Exception e) {}
BASE64Encoder encoder = new BASE64Encoder();
String helloBase64 = encoder.encode(byte1);
helloBase64 = helloBase64.replaceAll("\n", "");
byte[] bytes = decode(helloBase64);
return defineClass(name,bytes,0,bytes.length);
}
public byte[] decode(String base64){
return Base64.getDecoder().decode(base64);
}
}
- 定义测试类
package yudaosourcecode.csdn.huohuo;
public class LoadDynamic {
public static void main(String[] args) throws Exception {
new ACLassLoader().loadClass("yudaosourcecode.csdn.huohuo.A").newInstance();
}
}
- 运行
注意用java8
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/javac -d . A.java
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/javac -d . ACLassLoader.java
ACLassLoader.java:3: 警告: BASE64Encoder是内部专用 API, 可能会在未来发行版中删除
import sun.misc.BASE64Encoder;
^
ACLassLoader.java:19: 警告: BASE64Encoder是内部专用 API, 可能会在未来发行版中删除
BASE64Encoder encoder = new BASE64Encoder();
^
ACLassLoader.java:19: 警告: BASE64Encoder是内部专用 API, 可能会在未来发行版中删除
BASE64Encoder encoder = new BASE64Encoder();
^
3 个警告
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/javac -d . LoadDynamic.java
bogon:huohuo xb$ /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/bin/java yudaosourcecode/csdn/huohuo/LoadDynamic
A初始化了!
可以看到A类初始化了。
5.5.2:URLClassLoader的addUrl方法
定义如下类:
package yudaosourcecode.csdn.huohuo;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class B {
public static void main(String[] args) throws Exception {
// 注意修改为自己本地的路径
String appPath = "file:/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/src/main/java/yudaosourcecode/csdn/huohuo/yudaosourcecode/csdn/huohuo";
URLClassLoader urlClassLoader = (URLClassLoader) B.class.getClassLoader();
try {
Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
// 暴力访问
addURL.setAccessible(true);
URL url = new URL(appPath);
addURL.invoke(urlClassLoader, url);
// 动态加载A类
Class.forName("yudaosourcecode.csdn.huohuo.A");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行:
...
[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ java ---
A初始化了!
[INFO]
...
写在后面
详解java类的生命周期 。
文件类型识别----魔数 。
一个类的生命周期 。