内存分配
对象优先在Eden分配
大对象直接进入老年代
大对象:需要大量连续空间的对象
长期存活对象进入老年代
出生时位于Eden,一次MinorGC存活后进入Survivor,每经历一次GC,年龄加一,年龄达到15进入老年代
当Survivor中相同年龄的对象大小总和大于Survivor的一半,则这些对象进入老年代
常用工具
jps:JVM Process Tool:显示指定系统内所有的虚拟机进程
jstat:JVM Statistics Monitoring Tool 收集HotSpot虚拟机各方面的运行数据
jinfo:Configuration Info for Java 显示虚拟机配置信息
jmap:Memory Map for java 内存转储快照
jhat:JVM Heap Dump Browser 用于分析heapdump文件,建立一个HTTP/HTML服务器让用户浏览
jstack:Stack Trace for Java 显示虚拟机的线程快照
运行时数据区
虚拟机栈VM stack
线程私有
生命周期与线程相同
每个方式执行会创建一个栈帧
局部变量表:编译期可知的各种基本数据结构、对象引用、返回地址
存放方法参数及方法内的局部变量
操作数栈
动态链接:指向运行时常量池中该栈帧所属方法的引用
方法出口:储存返回地址
退出方法的方式
正常完成出口
异常完成出口,不会返回值,<font color="#c41230">返回地址通过异常处理器表来确定</font>
退出过程
1)恢复上层方法的局部变量表和操作数栈
2)把返回值压入调用者的栈帧的操作数栈中
3)调整PC计数器指向下一条指令
附加信息
本地方法栈 Native Method Stack
线程私有
与虚拟机栈一样,只是服务的方法类型不一样
堆Heap
线程共享
占内存最大的一块
存放对象实例、数组
垃圾回收的主要区域
方法区Method Area
线程共享
储存已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
对象的创建
步骤
1)是否能在常量池中找到类符号引用,并检查是否已被加载、解析、初始化,如果没有则进行类加载
2)分配内存
内存规整:指针碰撞
内存不规整:空闲列表
线程安全:本地线程分配缓冲TLAB,每个线程一个空间,不干涉
3)对象设置
属于哪个类
元数据
哈希码
GC分代年龄
对象内存布局
对象头
运行时数据(Mark Word)
哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
实例数据
代码中定义的各种类型字段内容(包括从父类继承的)
类文件结构
class文件是一组8位字节为基础单位的二进制流,大端
数据类型
无符号数
u1、u2、u4、u8表示长度为1、2、4、8个字节(8位))
表示数字、索引引用、数量值、字符串值
表
组成
4~7字节:jdk本号
常量池
字面量:常量字符串、final常量值
符号引用
类和接口的fully Qualified Name
字段的方法和描述符
方法的名称和描述符
u2访问标志:类/接口、public、final、abstract
继承关系
u2类索引:类的全限定名
u2父索引:父类的全限定名
nu2+1接口索引:实现接口的全新定名
字段表集合:描述接口、变量
u2访问标志
u2 name_index
u2 descriptor_index
u2 attributes_count
u2 attributes
方法表集合:描述方法
属性表集合
code属性
exception属性
LineNumberTable属性
LocalVariableTable属性
sourceFile属性
constantvalue属性:通知虚拟机自动为静态变量赋值
innerClass属性
Deprecated和Synthetic属性
stackMapTable属性
Signature属性:记录泛型信息
BootstrapMethod属性
类加载机制
类型的加载、连接和初始化过程都是在程序运行期间完成的:增加开销,增加灵活性
运行时再指定接口的实现类
类可以从网络或其它地方加载
过程
加载
过程
通过类的全限定名来获取定义此类的二进制流
从jar、war包中获取
从网络中获取:Applet
运行时计算生成:动态代理技术
其他文件生成:JSP
将字节流代表的静态储存结构转化为方法区的运行时数据结构
在内存中生成一个Class对象,作为方法区该类各种数据的访问入口
加载与连接的部分内容是交叉进行的
验证
目的:确保Class文件包含的信息符合虚拟机要求
防止访问数组边界以外的数据
防止将对象转型为它并未实现的类型
防止跳转到不存在的代码行
检验动作
文件格式验证
是否以0xCAFEBABE开头
主、次版本号是否当前虚拟机支持
常量池中常量是否有不被支持的类型
索引值是否指向不存在的常量
元数据验证
类是否有父类
是否继承了不允许被继承的类
非抽象类是否实现了其父类或接口的方法
是否覆盖了父类的final字段
字节码验证
字节码操作数栈的数据类型与指令代码序列能正常工作,不会用long的命令操作int
跳转指令不会跳转到方法体之外的指令上
类型转换是有效的
符号引用验证(发生在解析阶段)
符号引用中通过全限定名是否能找到对应的类
在指定类中是否符合方法的字段描述符及简单名称所描述的方法和字段
符号引用中的类、字段、方法是否能被当前类访问
准备
正式为类变量分配内存并设置类变量零值的阶段(final的变量会赋实际值)
解析
将常量池内符号引用替换为直接引用的过程
符号引用:符号表示的,目标不一定已加载在内存中,与虚拟机内存布局无关
直接引用:指针、偏移量或句柄。目标一定加载在内存中,与虚拟机内存布局有关
分类
类/接口的解析
不是数组类型:当前类的类加载器得到目标类的全限定名
数组类型:先加载数组元素类型
符号引用验证
字段解析
1、类只包含简单名称和字段描述符与目标相匹配的字段,则返回这个字段的直接引用
2、否则,如果类实现了接口,按照继承关系从下往上递归搜索各个接口重复1
3、否则,如果不是java.lang.Object,按照继承关系从下往上递归搜索各个父类重复1
4、否则,查找失败
类方法解析
接口方法解析
初始化
执行类构造器<clinit>()方法
<clinit>()方法是由编译器自动收集类中所有变量的赋值动作、静态语句块中的语句合并产生的
子类的<clinit>()执行之前,父类<clinit>()一定执行完成
不会执行父接口的<clinit>(),除非要使用父接口中定义的变量
多线程同时初始化一个类,只有一个会去执行<clinit>(),其余挂起
初始化条件
初始化有且仅有:
遇到new、getstatic、putstatic、invokestatic字节码时
使用new实例化对象
调用一个类的静态方法
读取、设置一个类的静态字段
使用java.lang.reflect包时,类没有初始化,则初始化
初始化子类,而其父类没有被初始化时
主类
使用JDK1.7动态语言
被动引用不初始化
通过子类引用父类的静态字段,不会导致子类初始化
通过数组定义来引用类
引用类的常量,不会初始化该类,因为常量在编译阶段存入了常量池
使用
卸载
类加载器:外部实现的“通过类全限定名获取描述此类的二进制字节流”的代码
同一个类,被不同类加载器加载,这两个类不相等(equals、instanceof)
分类(从上到小,组合)
启动类加载器
扩展类加载器
应用程序类加载器
自定义类加载器
只有上层无法加载,子才会加载