JVM 概述
Class 字节码格式总体结构ClassFile { u4 magic; // 魔数0xCAFEBABE4字节 u2 minor_version; // 次版本2字节 u2 major_version; // 主版本2字节 u2 constant_pool_count; // 常量池长度2字节 cp_info constant_pool[]; // 常量池变长 u2 access_flags; // 访问标志public/final等2字节 u2 this_class; // 当前类索引2字节 u2 super_class; // 父类索引2字节 u2 interfaces_count;// 接口数2字节 u2 interfaces[]; // 接口索引数组 u2 fields_count; // 字段数2字节 field_info fields[]; // 字段表变长 u2 methods_count; // 方法数2字节 method_info methods[]; // 方法表变长含字节码 u2 attributes_count; // 属性数2字节 attribute_info attributes[]; // 属性表变长 }magic number每个 Class 文件开头的 4 字节整数 - 0xcafebabe版本记录编译器版本, 判断当前的 jvm 是否能够兼容该 class 文件常量池计数器记录常量池中有多少常量从 1 开始计数字节码指令程序实际要运行的指令类加载机制加载过程装载将 class 文件加载到内存中并创建一个 Class 对象链接验证确认加载的字节码是否符合规范准备为静态变量分配内存并赋予默认值解析将类、接口、方法的符号引用转变为直接引用为什么 class 文件里必须用符号引用编译的时候类还没加载进内存地址还没分配编译器不知道这个类将来会被加载到内存哪个地址初始化为静态变量赋予正确的初始值static final 是例外常量值是在链接的准备阶段进行赋值类加载器启动类加载器没有父加载器加载 Java 核心库JAVA_HOME/jre/lib/rt.jar扩展类加载器继承自 ClassLoader 类父加载器为启动类加载器加载 jre/lib/ext 目录下的类库应用程序类加载器继承自 ClassLoader 类父加载器为扩展类加载器加载环境变量 classpath 下的类库双亲委派模型当需要加载某些类时类加载器会把请求先转给父类加载器即最顶层的类加载器先加载加载不到就转给子类加载器如果最终到达最底层类加载器依旧没有找到跑出 ClassNotFoundException如何破坏双亲委派模型重写自定义类加载器的 loadClass 方法使用线程上下文类加载器通过Thread.currentThread().getContextClassLoader() 拿到应用类加载器反向加载类绕过委派规则SPI 机制运行时数据区程序计数器记录下一条要执行的字节码指令在哪每个线程独有本地方法栈每个线程独有执行 native 方法虚拟机方法栈方法执行的地方存局部变量、方法出口、对象引用每个线程独有栈太深会报错 StackOverflowError通过 -Xss 指令可指定栈大小栈帧栈帧是方法运行的独立内存模型每调用一个 Java 方法就会在虚拟机栈中创建一个栈帧方法执行结束栈帧出栈销毁组成局部变量表存放方法参数、方法内局部变量、对象引用操作数栈JVM 执行字节码指令的临时数据缓冲区做计算、传参动态链接把栈帧中的符号引用在运行时解析为直接引用因为有些多态只有到实际运行后才能确定类型方法返回地址记录方法执行完后回到调用方的字节码指令位置附加信息存放异常处理表、调试信息、锁记录等堆存所有对象实例、数组所有线程共享内存空间不足会报错 OutOfMemoryError-Xms 设置堆的初始大小-Xmx 设置堆的最大大小方法区方法区只是逻辑上属于堆实际上是独立的内存空间存类信息、常量、静态变量所有线程共享JDK8 以前叫永久代JDK8 以后叫元空间MetaspaceJava 对象内存布局GC年轻代分为 Eden 和 S0 S1默认比例 8:1:1为什么年轻代区分为 eden 和 survivor用 “复制算法” 高效回收垃圾同时避免内存碎片还能筛选出真正存活的长寿命对象老年代对象什么时候会进入老年代对象年龄达到 15Survivor 中 相同年龄所有对象大小总和 Survivor 空间的一半年龄 该年龄的对象直接进入老年代大对象直接进老年代对象大小超过阈值-XX:PretenureSizeThresholdYGC 后存活对象太多Survivor 装不下老年代空间分配担保失败触发 Full GC 后所有存活对象强制进入老年代分配担保