JVM虚拟机规范详情参见官网
ClassFile {
u4 magic; // 魔数值,确认class文件,值固定
u2 minor_version; // 副版本号
u2 major_version; // 主版本号
u2 constant_pool_count; // 常量池计数器
cp_info constant_pool[constant_pool_count-1]; // 常量池
u2 access_flags; // 访问标志
u2 this_class; // 类索引
u2 super_class; // 父类索引
u2 interfaces_count; // 接口计数器
u2 interfaces[interfaces_count]; // 接口表
u2 fields_count; // 字段计数器
field_info fields[fields_count]; // 字段表
u2 methods_count; // 方法计数器
method_info methods[methods_count]; // 方法表
u2 attributes_count; // 属性计数器
attribute_info attributes[attributes_count]; // 属性表
}
javap
-help --help -? 输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
javap生成的非正式“虚拟机汇编语言”,格式如下:
ASM是一个Java字节码操纵框架,能够用来动态生成或增强既有类的功能
提供基于事件形式编程模型。不需要一次性将整个类结构读取到内存,运行更快、占用内存少,但是编程方式难度较大
ASM Core API中操纵字节码的功能基于ClassVisitor接口,这个接口中的每个方法对应class文件中每一项
ASM提供ASMifier工具,可用来生成ASM结构来对比
提供基于树形的编程模型。需要一次性将整个类结构读取到内存,占用更多内存但是编程方式简单。
Java虚拟机自带加载器包括以下几种:
用户自定义加载器,是java.lang.ClassLoader的子类,用户可以定制类的加载方式,自定义加载器加载顺序在所有系统类加载器之后
JVM中的ClassLoader通常采用双亲委派模型,要求除启动类加载器外,其余的类加载器都应该有自己的父加载器。加载器间是组合关系而非继承。工作过程如下:
双亲委派模型说明:
自定义类加载器:
public class MyClassLoader extends ClassLoader {
private String loaderName;
public MyClassLoader(String loaderName) {
this.loaderName = loaderName;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String name) {
byte[] data = null;
name = name.replace(".", "/");
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); InputStream in = new FileInputStream(new File(
"target/" + name + ".class"))){
byte[] buffer = new byte[1024];
int size = 0;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
data = out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
}
public class MyClass {
public MyClass() {
}
}
public class ClassCloaderMain {
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader classLoader = new MyClassLoader("myClassLoader1");
Class cls = classLoader.loadClass("classloader.MyClass");
System.out.println("cls class loader == " + cls.getClassLoader());
System.out.println("cls parent class loader == " + cls.getClassLoader().getParent());
}
}
/*
控制台打印:
cls class loader == classloader.MyClassLoader@3caeaf62
cls parent class loader == sun.misc.Launcher$AppClassLoader@18b4aac2
*/
破坏双亲委派模型:
双亲委派模型问题: 父加载器无法向下识别子加载器加载的资源
为了解决这个问题,引入线程上下文类加载器,可以通过Thread的setContextClassLoader()进行设置,例如数据库连接驱动加载
另一种典型情况是实现热替换,比如OSGI的模块热部署,它的类加载器不再是严格按照双亲委派模型,很多在平级的类加载器中执行
将已经读入内存的类二进制数据合并到JVM运行环境中去,包含以下几个步骤:
为类的静态变量赋初始值,或者说执行类的构造器
Java程序对类的使用分成: 主动使用和被动使用。JVM必须在每个类或接口“首次主动使用”时才会初始化它们,被动使用的类不会导致类的初始化。
主动使用的情况:
当代表类的Class对象不再被引用,那么Class对象生命周期就结束了,对应方法区的数据也会被卸载
JVM自带的类加载器装载的类不会卸载,由用户自定义的类加载器加载的类可以被卸载
深入浅出JVM(Ⅰ):JVM规范&类从加载、连接、初始化到卸载
原文:https://www.cnblogs.com/haif/p/13643085.html