JVM内存模型

3/8/2017来源:ASP.NET技巧人气:1678

JVM内存模型

程序计数器

线程私有,当前线程锁执行的字节码的行号指示器 无OutOfMemory错误

虚拟机栈

线程私有,java方法执行时会创建栈帧,存储 局部变量表,操作数栈,动态链接和方法出口等信息 局部变量表存放各种编译器可知的基本数据类型boolean、byte、char、short、int、float、long、double、对象引用(地址)和returnAddress(下一条字节码指令地址) 局部变量表的内存空间在编译器分配完成,方法运行期间不会改变其大小。

本地方法栈

虚拟机使用本地Native方法服务 有的虚拟机把这部分与虚拟机栈合并

Java堆

线程共享,存放对象实例,几乎所有的对象实例以及数组都要在堆上分配 垃圾回收的主要区域,GC堆 可以处在物理不连续的空间上,但是逻辑上要连续

方法区(也叫堆-永久代)

线程共享,存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码

运行时常量池:存放编译器生成的各种字面量和符号引用

Java对象创建

常量池检查

遇到一条New指令后,先去常量池检查这个类是否已经被加载,如果没有,则执行类加载

类加载

内存分配

对象所需内存的大小在类加载后就可以完全确定(通过类元数据信息),然后将同等大小的内存从堆中划分给对象。

分配方法:

规整内存:指针碰撞 不规整内存:空闲列表

分配同步:

CAS+失败重试 本地线程分配缓冲

设置对象头

对象内存 = 对象头 + 实例数据 + 对齐填充

对象头一般包括两部分信息:

存储对象自身的运行时数据(hashCode,GC Age, 锁状态等) 一个字长大小,也叫“MarkWord”,非固定结构,根据对象的不同状态,存储不同含义的数据 类型指针,对象指向类元数据的指针。如果是数组,还需要记录数组长度

实例数据:类中所定义的各种类型的字段内容

对齐填充:HotSpot要求对象起始地址必须是8字节的整数倍,对象的大小必须是8字节的整数倍。

访问对象

通过操作栈上的Reference数据来操作堆上的对象,如何实现Reference:

句柄:二层索引,稳定 指针:一层索引,快速高效

垃圾收集

程序计数器和虚拟机栈、本地方法栈随线程生存消亡,内存分配回收相对确定,但是堆上内存就复杂的多,需要设计垃圾回收算法进行回收。

如何判断哪些对象需要被回收

引用计数:无法解决循环引用 可达性分析:GCRoot无法到达该对象,该对象就标为死亡 可以作为GCRoots的结点:栈上引用对象(局部变量),永久代上引用对象(静态变量和常量) 对象在可达性分析中不可达后,不代表一定会消亡,在标记过程中,会触发finalize方法(只会触发一次,没有实现该方法则不触发),如果对象重新与引用链关联上,就不会被回收。这里的触发只是JVM会调用该方法,但不一定等待该方法执行完

方法区回收

废弃常量 没有其他地方引用该常量 无用的类 该类所有实例都已被回收 加载该类的ClassLoader被回收 该类对应的java.lang.class对象没有在任何地方被引用

垃圾回收算法

标记-清除算法

标记所有需要清除的对象,然后再统一回收 存在效率问题和空间问题(内存碎片)

复制算法

内存对半分,用完一半,回收时,将存活对象复制到另外一半,顺序排列 简单高效,但是内存容量缩小 一个大的Eden空间和两个小的survivor空间,每次使用Eden和一个survivor(from),在回收时,将其中还存活的对象一次性复制到另外一个survivor(to)中,当survivor(to)空间不够时,依赖其他内存进行分配担保

标记-整理算法

标记需要清理的对象,然后存货对象向内存一端移动

分代收集算法

Java堆划分为新生代和老年代,新生代存储新分配的对象,老年代存放存活周期大和内存空间大的对象 新生代对象代谢频繁,使用复制算法,老年代存活率高,使用另外两种算法。

内存分配回收策略

对象优先分配在Eden 对象将尝试在Eden中分配,如果空间不够,则调用一次MinorGC MinorGC 发生在新生代,很快; MajorGC FullGC 发生在老年代,比较慢 大对象直接进入老年代 eg.长字符串和数组 长期存活的对象将进入老年代 对象年龄AGE:每在Survivor区中熬过一次MinorGC就长一岁,默认15岁就要去老年代 动态年龄对象判定:如果在survivor空间中相同年龄的对象大小的总和大于survivor空间的一半,则年龄大于或等于该年龄的就可以直接进入老年代 空间分配担保 老年代为新生代MinorGC提供担保 在复制算法中,如果survivor(to)空间不足,则将剩余的存活对象装入老年代中。