向上委托,向下加载
收到加载任务后,先交给父类加载器,只有当父类加载器无法完成,才会执行加载
保证只有一个类加载器加载,避免重复加载
破坏:JDK 1.2后才使用,JDK 1.1的核心类没有通过双亲委派定义
如何判断两个Class对象是否相同
class字节码相同
classLoader相同
JVM运行数据区按线程使用情况分类
线程独享区域
虚拟机栈、本地方法栈、程序计数器
不需要垃圾回收
线程共享区域
堆和方法区
垃圾回收、类的静态数据和对象
HotSpot
JDK 1.6:字符串常量池在永久代
JDK 1.7:依然有永久代,但字符串常量池在堆中
JDK 1.8:不存在永久代
字符串常量池
节省内存空间,所有类共享一个字符串常量池
如何存数据
对象存储
快速搜索:StringTable(hashtable)
StringTableSize:决定搜索效率
堆
JVM中最大的一块内存
GC最重要区域
堆的内存分配通过垃圾收集器实现
JVM启动时实现
存什么
JDK 1.6:对象、数组
JDK 1.7+:对象、数组、字符串常量池、静态变量
参数
-Xms:初始大小,单位m,g
-Xmx:最大大小
-XMn:新生代(年轻代)
-XX:SurvivorRatio=8:年轻代中Eden与Servivor比例,默认为8:1
-XX:PermSize:非堆内存初始大小,一般默认200m
-XX:MaxPermSize:菲堆最大大小
-XX:NewSize(-Xns):年轻代内存初始大小
-XX:MaxNewSize(-Xmn):年轻代最大大小
分配原则
优先在Eden分配,如果Eden不足,进行一次MinorGC
大对象直接进入老年代(超过Eden一半)
长期存活对象进入老年代(15次GC未被回收)
年轻代没有空间存放的对象进入老年代
分配方式
指针碰撞:内存地址连续(年轻代)
空闲列表:内存地址不连续(老年代)
分配安全
虚拟机给A线程分配内存过程中,指针未修改,B线程可能同时使用了同一块内存
CAS(乐观锁):不加锁,假设没有冲突,如果冲突就重试,直到成功
TLAB(本地线程分配缓冲):每个线程预先分配一块内存
分配担保
当在新生代中无法分配内存时,把新生代对象转移到老年代,然后把新对象放入腾空的新生代
对象内存布局
对象头:锁信息、对象年龄、类型指针
实例数据:成员变量
对齐填充
程序计数器
当前线程执行下一个字节码的行号
用于线程切换后,能恢复到正确执行的位置
Java虚拟机栈
基于栈和基于寄存器
基于栈(操作数栈):一个程序调用需要10个基于栈的指令,JVM一次编译,到处运行
基于寄存器:同样的一个程序调用只需要两三个基于寄存器的调用,和操作系统有关
栈帧
支持虚拟机进行方法执行的数据结构
存储方法的局部变量表、操作数栈、动态链接、方法返回地址
局部变量表--冰箱;操作栈--盘子
方法调用时创建
操作数栈的每个元素可以是任意Java数据类型,32位数据类型占一个栈容量
每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,可在类加载阶段或第一次使用时转换为直接引用(静态解析),或每次运行时转换为直接引用(动态连接)
静态解析:直接引用,即对应方法的内存地址