目录:
1.类的装载
1.1步骤
1.2类加载器classloader
1.2.1自定义classloader
1.2.2 UrlClassloader
1.3出现类名但不会初始化的情况(会load+准备)
2.类的卸载
1.虚拟机类的生命周期:装载(Load),链接(Link),初始化(Initializ),卸载(Unload)。
2.其中链接(Link)又可分为校验(Verify),准备(Prepare),解析(Resolve)三步。
1.类的装载
1.1步骤
- 通过类的全限定类名来获取该类的class文件字节流。
- 将此字节流所代表的静态存储结构转换为方法区中的运行时数据结构(class对象),作为该类元数据的访问入口。
- 准备阶段:为static变量在方法区中分配内存,并赋初始值(0," ",引用地址初始值X0000等)。
- 初始化阶段:执行编译后的static方法块(static方法块会在编译时按顺序收集static变量,所以static方法块只能访问其之前定义的static变量)。
static int value = 123;//此语句在编译后会被放进static方法块中
(static) final int value1 = 123;//常量值会在第3步时即在方法区中分配内存,并赋初始值123,并且不会编译进static方法块中
tips:
- 虚拟机会保证在子类的static方法执行之前,父类的static方法已执行完毕。
- 接口中不能使用static方法块,但是可以定义static变量,所以编译器也会为接口构造static方法,只是其执行前不需要先执行父接口的static方法,只有当父接口中定义的变量使用时,父接口才会初始化。接口的实现类同样也不会先执行父接口的static方法块。
- JVM会保证一个类或接口的cinit(类初始化)方法是同步的,加锁的,当其他线程同时init本类时会阻塞,直到执行cinit的线程退出cinit方法后,其他线程被唤醒,但是不会再次进入cinit方法。
- 同一个类加载器下,一个类只会初始化一次;若是多个类加载器加载同一个类,则会初始化多次。
- Bootstrap Classloader:C++编写,Java看不到,所以永远是null,用以加载核心类库lib/目录下的类。
- Extension Classloader:Bootstrap Classloader的子加载器,用以加载lib/ext/目录下的类。
- System Classloader:又叫App Classloader,用以加载Classpath(包括Classes目录)下的所有类,是Extension的子加载器。
- 使用委托机制,即委托其父加载器加载。
package com.baidu;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{//自定义classloader继承自ClassLoader类,然后自动挂载到AppClassLoader下
public MyClassLoader(){
super();
}
public MyClassLoader(ClassLoader parent){
//显示指定该类加载器的父加载器
super(parent);
}
//当调用cl.loadClass方法时,其内部会首先调用findLoadedClass(name)方法检查是否已经被加载过,若没有则会调用parent.loadClass方法(loadClass内部会调用findClass方法),如果parent.loadClass方法返回null,就会调用本classLoader类的findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
FileInputStream fi = null;
try {
fi = new FileInputStream("F:\\workspace\\TestJava\\bin\\com\\baidu\\"+name.replace("com.baidu.","")+".class");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int b = -1;
try {
while((b=fi.read())!=-1){
bos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] bs = bos.toByteArray();
return defineClass(name, bs, 0, bs.length);
}
public static void main(String[] args) throws Exception{
ClassLoader cl = new MyClassLoader();
cl.loadClass("com.baidu.MyClassLoader");//委托AppClassLoader来加载
ClassLoader cl1 = new MyClassLoader(null);//指定父类加载器为Bootstrap Classlaoder
cl1.loadClass("com.baidu.Sample").newInstance();//Bootstrap ClassLoader找不到,所以由MyClassLoader加载
//程序输出如下:
//Sample is loaded by com.baidu.MyClassLoader@15db9742
//A is loaded by com.baidu.MyClassLoader@15db9742
//sun.misc.Launcher$AppClassLoader@73d16e93
System.out.println(Thread.currentThread().getContextClassLoader());//AppClassLoader
}
}
package com.baidu;
public class Sample {
public Sample(){
System.out.println("Sample is loaded by " + this.getClass().getClassLoader());
new A();
}
}
package com.baidu;
public class A {
public A(){
System.out.println("A is loaded by " + this.getClass().getClassLoader());
}
}
-
从上面的例子可以看出类A调用了类B,则类B的ClassLoader为类A的Classloader。
-
一些web服务器如Tomcat会自定义classloader以实现不同web application之间的类相互隔离(为每一个新的请求new一个新的线程,并设置当前线程的Classloader为所访问的Web application的ClassLoader)。
-
Tomcat可能会重写loadClass方法以消除委托机制。
1.2.2 UrlClassloader
<pre name="code" class="java">URL[] urls = new URL[] {new URL("file:libs/jar1.jar")};
URLClassLoader loader = new URLClassLoader(urls, parentLoader);
class clazz = loader.loadClass("全限定类名");
1.通过数组引用类:A[] a = new A[5];。
2.由于静态字段也会被继承,通过子类引用父类的静态字段,只会触发父类的初始化。
3.调用classloader.loadClass("com.baidu.A")方法不会触发类初始化。
4.引用类的静态常量不会触发类初始化。
2.类的卸载
- 双向关联:Classloader类的内部实现中,用一个Java集合保存其所加载的所有Class对象;class对象有getClassLoader方法,所以其内部包含classloader对象的引用;类的实例对象有getClass方法,并且类有一个静态属性class,二者都指向此类的Class对象。
- JVM的类加载器所加载的类,在整个JVM运行期间始终不会被卸载(因为JVM本身始终会引用这3个Classloader,而这些classloader又会始终引用其所加载的class对象)。
- 用户自定义的classloader所加载的类是可以被卸载的。
Classloader cl = new MyClassloader(null);
Class<?> clazz = cl.loadClass("com.baidu.Test");
Object obj = clazz.newInstance();
obj = null;
clazz = null;
cl = null;
System.gc();
//若将此三个对象统统设为null,则会GC他们所指向的对象,方法区中的Test类元数据信息会随之卸载
结语:本季内容已全部结束,主要向大家介绍了JVM内核,包括JVM内存模型,JVM垃圾回收,JVM调优以及JVM类加载机制。从今以后,将正式进入大数据开发相关内容的学习和交流。
更多大数据相关技术和教程,请扫描以上二维码