JVM
- JVM虚拟机,可以看作软件模拟完整的硬件系统。
- 解释执行.class的字节码文件,在java中是通过javac命令来编译.java代码为字节码文件的
- javap xxx.class 可以查看字节码文件
- jvm上运行的应用程序都有一个特点,一次编写,到处运行,应用不在对底层操作系统,物理设置有依赖,而只依赖jvm,并且JVM还有自动内存管理,对多线程友好等特点。
- jdk源码:http://jdk.java.net/java-se-ri/7。
字节码文件
- 字节码的指令集有不超过256条指令,大多数和类型相关。
- 例如,invokevirtual指调用对象的实例方法,会根据对象的实际类型进行分派,在运行期间确定,也称为动态分派。
类加载
- 类加载是指JVM加载.class的字节码文件到内存(元数据区,方法区)的过程,在java程序中主要涉及Class对象的建立,静态变量分配内存空间和赋值,static代码块的执行。
- JVM对类加载是按需加载的,当执行new操作,static变量和方法使用,反射调用,Class.forName("…"),getClassLoader().loadClass()等操作时会触发类加载过程;
- 通过在启动参数中添加-verbose:class,-verbose:gc,-verbose:jni可以查看启动类加载,gc和本地方法调用的日志。
类加载的过程
- 类加载过程分为:加载,验证,准备,解析,初始化,使用,卸载几个阶段,阶段之间有一定的重叠运行。
- 加载:.class文件(或者动态生成的字节码)转换成二进制流,静态内容转换方法区数据,生成Class对象作为方法区该类的访问入口,这个阶段开发人员可控性较强。
- 验证:验证.class二进制流是否符号JVM要求,如果是反复使用,信任的java代码,可以通过-Xverify:none来关闭大部分验证。
- 准备:方法区分配内存给类变量,并赋初值(零值),static final修饰的常量也完成了内存分配。
- 解析:常量池的部分符号引用替换为直接引用,链接到方法区的其他类,解析时间不固定,可以在类加载就解析或者在使用时才解析。
- 符号引用是指对类,字段,方法的引用,其中对类的解析需要加载类,并判断访问权限,对字段的解析:在自身类,接口类,父类中寻找该字段,对类方法的解析:跟字段的解析类似,寻找该方法;
- 初始化:执行cinit(),由静态变量赋值和静态代码块组合而成,默认会调用父类的cinit();
- 因为类只会加载一次,所以静态变量,静态代码块只会执行一次,并且new SuperClass[10],并不会初始化SuperClass。
类加载器
- 负责类加载的加载过程,即获取字节码的二进制流,应用可控性强;
- 加载过程采用双亲委派的方式,即将类加载器分层级,加载某个类会逐层向上委派父类加载器来加载,只有当父类加载器无法加载时才会让子类加载器尝试加载。这种方式保证一个类只会被一个类加载加载;
- 采用双亲委派是因为类是由自身和其加载器共同确定其唯一性,因此要避免同一个类被不同加载器加载到;并且类之间也规定只能访问自身类加载器和上级类加载器加载的类,不能向下访问,这样做的好处是保证了类的安全性,父类加载器加载的类不会对子类加载器加载的类有依赖性;
- jvm对类加载器的分层,最上层是启动类加载器,负责加载JAVA_HOME/lib目录下指定名称的jar(rt.jar,charsets.jar);然后是扩展类加载器,负责加载JAVA_HOME/lib/ext目录下的jar;然后是系统类加载器,负责加载classPath路径下的jar和class文件。
获取系统类加载器:AppClassLoader:ClassLoader.getSystemClassLoader()
获取classpath:this.getClass().getClassLoader().getResource("").toString() ??
- 源码上类加载器ClassLoader是通过loadClass() --》 findClass() --》defineClass()实现的,入口是loadClass(),内部调用findClass(),findClass()内部再调用defineClass()(将流转成class实例)然后返回,其中loadClass()保证了双亲委派,通常构造一个类加载器只需要重写findClass即可。
实现自定义类加载器MyClassLoader:
D:/SimpleCustom.class:
public class SimpleCustom {
public int age = 30;
static {
System.out.println("SimpleCustom loaded");
System.out.println(SimpleCustom.class.getClassLoader());//输出类装载器的类型
}
}
MyClassLoader:
public class MyClassLoader extends ClassLoader {
public MyClassLoader(){ }
// 从文件中读取,形成字节码类
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//将class文件进行解密
FileInputStream fis = new FileInputStream("D:/SimpleCustom.class");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] byteArr = new byte[1024];
int len;
while((len = fis.read(byteArr)) != -1){
bos.write(byteArr, 0, len);
}
byte[] classByte = bos.toByteArray();
//将字节流变成一个class
Class<?> c = this.defineClass(null, classByte, 0, classByte.length);
return c;
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
public class SimpleApp {
static{
System.out.println("SimpleApp loaded");
System.out.println(SimpleApp.class.getClassLoader());//输出类装载器的类型
}
public static void main(String[] args) {
try{
ClassLoader loader = new MyClassLoader();
Class<?> c = loader.loadClass("SimpleCustom");
// 注意直接用sc.age是访问不到的,因为SimpleCustom是由自定义类加载器加载的,而SimpleApp是系统类加载器加载的,父级别类加载器加载的类是无法访问子类加载器加载的类,但可以通过反射访问
System.out.println(c.getDeclaredField("age").get(c.newInstance()));
}catch(Exception e){
}
}
}
SimpleApp loaded
sun.misc.Launcher$AppClassLoader@439a8942
SimpleCustom loaded
com.dengh.classLoader.MyClassLoader@1558473e
30
-
也存在双亲委派不适合的场景,如,jdbc中父加载器加载的代码需要使用各个厂商的实现,而厂商的实现只能由子类加载器来加载。另外还有OSGi,支持热插拔的模块化,已经形成业界标准;
-
类加载在热部署上的应用:通过每次创建新的类加载器实例来加载类,实现一个类修改后的重复加载,同时丢弃上次加载的类加载器实例,这样上次加载的类也被丢弃了,但注意丢弃的类加载器实例会被回收,但是生成的Class对象是存放在元数据区的,如果有大量热部署,可能导致内存溢出;
来源:CSDN
作者:qq_28128035
链接:https://blog.csdn.net/qq_28128035/article/details/103847265