运行时数据区,对象的内存分布及内存溢出场景的简单列举

一,java的运行时数据区分为本地方法栈,虚拟机栈,程序计数器,堆和方法区

①程序计数器,线程私有的,每个线程自己单独维护一个程序计数器,它主要是用于记录当前线程执行的下一条指令的地址

②虚拟机栈,线程私有,每个线程自己单独维护一个虚拟机栈,主要用于线程执行到方法时压入栈帧

③本地方法栈,和虚拟机栈基本 一样,只是执行的是本地方法时压入的栈帧

④堆,线程共享的,主要是用于为为大部分对象的创建提供开辟内存的空间

⑤方法区,线程共享的,主要是用于存放类元数据信息,其中包括运行时常量池,class文件中运行时常量池的数据及符号引用等信息就是存于此

二,对象在内存中的布局

①创建

首先,检查下当前要创建的类元数据信息是否加载到方法区中,如果未加载,则先启用类加载启对该类的class文件进行加载

接着,根据类元数据的信息,在堆内存中开辟对应大小的内存空间,这里开辟的方式有两种,一种是指针碰撞,就是规整内存中,指针的左边为已使用内存,右边为未使用内存,那么当分配内存的时候,只需要将改指针向右移动相应大小的内存空间即可,

一种是空闲列表法,是针对不规整的内存,那么虚拟机会维护一份列表,哪些空间是空闲,哪些空间是已使用的,那么从空闲列表中找出适合大小的内存给予分配,另外,在分配过程中 有可能涉及到并发问题,

也是两种解决方式,一种是cas+失败重试,另一种是tlab,thread local allote buffer,即为每个线程单独分配一份当前专门用于分配内存的空间,那么就不会涉及到竞争问题了,只有在分配给当前线程的空间不够 用了,才会涉及到同步问题

然后,初始化空间,赋"零值"

紧接着,设置对象头

最后,执行构造函数

②内存布局

一个对象在堆中的布局包括对象头,实例数据,填充对齐

对象头 又包括 MarkWord,类型指针及如果是数组的话还有数组的长度

③访问定位 

对对象的访问主要有两种方法,一种是句柄,一种是直接指针

三,溢出场景列举

①堆溢出,主要是在堆中的对象创建过多,又因为不恰当的引用得不到释放导致

②栈溢出,那么有可能就是栈中的栈帧过多或者局部变量表定义的变量过多

③方法区溢出,主要是加载到方法区中的类元数据过多,且卸载不了导致

④直接内存溢出,主要是调用了开辟堆外内存的方法过多导致

 

本人倡导的讲解方式:代码示例[学以致用,不仅要知道理论,还要知道理论怎么付诸实践], 文字讲解[不仅知道要怎么用,还要知道是怎么回事], 画图讲解[有图有真相,用图的形式将代码嵌入到理论中,整体理解]