目录
2.5.1 永久代(PermGen)和元空间(MetaSpace)
图来自腾讯课堂
java文件被编译为字节码文件,由类加载系统将字节码加载到JVM内存,执行引擎负责执行class文件中包含的字节码指令,运行时数据区即程序运行时的内存区域,其又分堆栈方法区PC寄存器,有的区域随虚拟机启动而存在,有的区域依赖用户线程的启动和结束而建立和销毁。
1. 类加载系统
1.1 类生命周期
指一个class从被加载到卸载的全过程。
1)加载,加载一个.class文件到jvm,实例化一个class对象的过程
链接,加载后对class的验证和初始化工作,一般分为三个部分,验证、准备、解析
2)验证:验证字节码文件格式是否符合jvm规范、变量方法是否重复、继承是否合标准,保证jvm可以正确运行。
3)准备:为静态变量分配内存设置初始值,比如int,long等初值设置为0
4)解析:把常量池中的符号引用转换为直接引用,根据符号引用所作的描述,在内存中找到符合描述的目标并把目标指针指针返回。
5)初始化,执行静态变量赋值和静态代码块,从父类往下执行
6)使用,被动引用和主动引用
被动引用:
- 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
- 定义类数组,不会引起类的初始化。
- 引用类的常量,不会引起类的初始化,已经放到常量池了
主动引用:1.2 类加载时机
7)卸载,满足下面的情况,类就会被卸载:
- 该类所有的实例都已经被回收
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有任何地方被引用
1.2 类加载时机
在运行过程中遇到如下字节码指令时,如果类尚未加载,那就要进行加载:new、getstatic、putstatic、invokestatic。这几个指令对应的场景:
1)通过new创建对象;
2)读取、设置一个类的静态成员变量(不包括final修饰的静态变量,final修饰已在编译阶段把结果放入常量池);
3)调用一个类的静态方法。
4)执行main,初始化主类
还有Class.forName(“”)时触发加载;
1.3 类加载器
java中的类加载器ClassLoader分为四种
- 启动类加载器(BootStrap ClassLoader),程序访问不到,C++实现,是最顶层的加载器,用来加载jre/lib目录下的jar包
- 扩展类加载器(ExtClassLoader),用来加载jre/lib/ext目录下的jar包
- 应用类加载器(AppClassLoader),用来加载classpath中指定的jar包及目录中class,父类加载器为ExtClassLoader
- 自定义类加载器,前三种是jvm预定义的类加载器,我们可以自定义classLoader来加载我们指定地方的class文件
类加载机制:全盘负责、双亲委派
全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
双亲委派,试图加载某个class时,首先查询缓存是否之前已加载过,加载过则直接返回class。若没有加载过,则先交给父类加载器加载class,直到顶层的BootStrap ClassLoader,若父类没有加载到class,再调用本加载器的loadClass方法记载class。
1.3.1 ClassLoader#loadClass
重要的三个方法
1)loadClass加载类的主方法
2)findLoadedClass查看本classLoader是否已经加载过这个类
3)defineClass 将字节码文件解析为class对象
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 {
// 如果存在父类加载器,交给父类加载器加载class
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 没有父类加载器,则调用顶层bootstrapclassLoader加载,native方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果父类没加载到class
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 调用本类的加载方法加载class
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;
}
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 从完整包路径,加载文件,在调用defineClass将二进制字节码文件解析为class对象
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
一个类被加载到JVM中,用其类加载器和全限定名作为这个类在jvm中的唯一标识,不同的加载器实例加载的相同class文件,互不兼容(cast强转报错),下节代码示例。
1.3.2 自定义类加载器
测试不同的加载器加载的类与原类不兼容
public class TestClassLoader extends ClassLoader{
String classpath = "D:/00000000000000/target/test-classes/";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
name = name.replace(".", "//");
String path = classpath + name + ".class";
File file = new File(path);
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
is.close();
byte[] bytes = baos.toByteArray();
return this.defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public class TestObject {
public void testPrint() {
System.out.println("ddd");
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
System.out.println("默认加载器:" + TestObject.class.getClassLoader().toString());
TestClassLoader loader = new TestClassLoader();
Class<?> testObject = loader.findClass("TestObject");
Object o1 = testObject.newInstance();
System.out.println("自定义加载器加载的对象:" + o1.getClass().toString());
System.out.println("自定义加载器加载的对象classLoader:" + o1.getClass().getClassLoader().toString());
// 强转报错,因为加载器不同,两个Class也是不同的,不能强转
TestObject cast = (TestObject) o1;
cast.testPrint();
}
}
但是如果改改代码,为TestObject加个接口,强转为接口类型则可以。