Java虚拟机在执行Java程序过程当中把其所管理的内存划分红若干个不一样的数据区域。程序员
当前线程所执行的字节码的指示器。经过改变这个计数器的值来选取下一个须要执行的字节码指令,分支、循环、跳转、异常、线程恢复等都须要这个计数器完成。算法
每一个线程一个独立的程序计数器,各线程之间互不影响,独立存储。安全
执行Java方法时:正在执行虚拟字节码的指令地址
执行Native方法:值为空(Undefined)
复制代码
惟一一个Java虚拟机规范中没有规定OutOfMemoryError的区域并发
线程私有,生命周期和线程一致。函数
描述的是Java方法执行的内存模型:方法执行同时建立一个栈帧,方法从调用到执行完成,就是一个栈帧在虚拟机栈中入栈到出栈的过程。布局
其中栈帧用于存储:局部变量表、操做数栈、动态连接、方法出口等信息。spa
局部变量表中存放了编译期间可知的基本数据类型和对象引用。线程
64位的long和double类型的数据会占2个局部空间变量,其他类型只占1个。因局部变量表中所需内存是在编译期间完成的,因此这个方法在帧中须要分配多少局部变量空间是肯定的。指针
这部分区域异常:StackOverFlowError和OutOfMemoryError 、code
与虚拟机栈的做用相似。本地方法栈是为Native方法服务。抛出异常与虚拟机栈一致。
堆是Java虚拟机中内存最大的一块。线程共享,虚拟机启动时建立。存放对象实例。
几乎全部的对象都在这里分配内存。【随着JIT编译器的发展和逃逸分析技术的成熟,栈上分配和标量替换,对象不必定在堆中分配】
堆是垃圾收集管理器的主要区域,也叫GC堆。细分为:新生代、老年代。eden->from survivor->to survivor
经过-Xmx和-Xms控制扩展,没法扩展时抛出OutOfMemoryError异常
线程共享。存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码。也叫非堆,也称永久代。
-XX:MaxPermSize
内存回收目标:常量池的回收和类的卸载
OutOfMemoryError
方法区的一部分
OutOfMemoryError
不是虚拟机运行时区的一部分。
JDK1.4新加入的NIO类,引入Channel和Buffer的I/O方式,使用Native函数库直接分配堆外内存,使用DirectByteBuffer做为这块内存的引用进行操做。
受本机内存大小和处理器寻址空间的限制
OutOfMemoryError
当遇到new指令时
首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用。【并检查该符号引用表明的类是否已被加载、解析、初始化过,若没有则执行类加载】
类加载经过以后,为新生对象分配内存
内存分配完成后,需将分配到的内存空间都初始化为零值,不包括对象头
接下来,虚拟机要对对象进行必要的设置,设置对象头。如这个对象是那个类的实例。如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
复制代码
分配内存方式:
指针碰撞
空闲列表
复制代码
当使用Serial、ParNew等带compact过程的收集器时:分配算法是指针碰撞
使用CMS基于Mark-Sweep算法的收集器时:分配算法采用空闲列表
分配对象内存空间并发下线程安全问题:
采用CAS加上失败重试保证更新的原子性
把内存分配动做按线程划分在不一样的空间进行,即每一个线程在Java堆中预先分配一块小内存,成为本地线程分配缓冲区【TLAB】 -XX:+、-UseTLAB
复制代码
对象建立完成以后还有进行init,按照程序员的意愿进行初始化。
对象内存布局分为三块
对象头、实例数据、对齐填充
复制代码
对象头包含两部分:
存储对象自身的运行数据
类型指针
复制代码
包含哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等。也称Mrak Word
对象指向它的类元数据的指针。虚拟机经过这个指针来肯定对象是属于哪一个类的实例
对象真正存储的有效信息,各类类型的字段内容。
不是必然存在的。起着占位符的做用。
对象起始地址必须是8字节的整数倍。当对象实例数据部分没有对齐时,就须要对齐填充了。
对象的访问定位目前有两种流行的方式:
使用句柄
直接指针
复制代码
使用句柄好处:栈中引用存储的是稳定的句柄地址,对象被移动时只会改变句柄中的实例数据指针,引用自己不需变化
使用直接引用好处:速度快,节省一次指针定位的时间开销