一、运行时数据区域
1.程序计数器
2.java虚拟机栈
a.线程私有
b.他的生命周期与线程相同,它描述的是java方法执行的内存模型:方法在执行时会创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息
c.局部变量表中存放的是:编译期可知的基本数据类型、对象引用和returnAddress类型
d.如果线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError异常;如果扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常
3.本地方法栈
4.java堆
a.线程共享
b.所有对象实例和数据都要在堆上分配空间
c.根据分代回收还分为:新生代和老年代,新生代又可分为:Eden空间、From Survivor空间、ToSurvivor空间。
d.堆若无空间时会抛出OutOfMemoryError异常
5.方法区
a.线程共享
b.存放已经被虚拟机加载的类信息、常量、静态变量、即时编译编译的代码等
c.
6.运行时常量池
a.
常量池(Constant Pool):常量池数据编译期被确定,是Class文件中的一部分。存储了类、方法、接口等中的常量,当然也包括字符串常量。
字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存储编译期类中产生的字符串类型数据。(jdk1.7移至堆中)
运行时常量池(Runtime Constant Pool):方法区的一部分,所有线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。
b.在无法申请内存空间时会抛出OutOfMemoryError异常
7.直接内存
a.java引入nio,引入了基于通到(Channel)与缓冲区(Buffer)的I/O方式,使用native直接分配堆外内存
二、 虚拟机对象
1.对象的创建
a.首先检查这个指令的参数是否能在常量池中定位一个类的符号引用,并检查这个符号引用代表的类是否被加载、解析和初始化。若无,,则执行
b.接下来就是分配内存,所需大小在加载完毕就确定。堆中内存不规整,需要维护一个内存记录表,内存是否规整由回收期决定
2.对象的内存布局
a.可分为三块:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
b.对象头包括两部分:第一部分,存储对象自身的运行时数据,如hash、GC分代年龄、锁状态标志、偏向线程ID、偏向时间戳;另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
3,.对象的访问定位
a.使用句柄和直接指针
b.使用句柄 reference中存放的是句柄地址,不会改变,指针则访问比较快
4.可能存在OOM的地方
a.堆
只要不断创建对象(调小最大堆的值-Xmx)就能跑出OOM异常(分为内存泄漏Memory leak和内存溢出Memory Overflow)
b.虚拟机栈和本地方法栈
栈容量由-Xss参数设定,两种异常:如果线程请求的栈深度大于虚拟机所允许的深度抛出StackOverflowError;如果虚拟机扩展时无法申请到足够多的内存空间,抛出OOM
使用递归的方法可以抛出异常
c.方法区和运行时常量池
使用-XX:PermSize和-XX:MaxPermSize限制大小
使用String.intern():如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个String对象,否则将此String对象的字符串加到常量池,并返回对象的引用。
可能会出现OOM:PermGen space
不过随着JDK8的更新,”永久代”消失了,OOM:PermGen space在JDK8以及之后的版本不会再出现了。
5.本机直接内存
使用-XX:MaxDirectMemorySize
通过反射获取Unsafe实例