程序计数器:
程序计数器属于线程的私有内存区域,记录当前线程的运行位置,以供多线程运行时上下文切换,字节码解释器通过程序计数器的增减来执行顺序分支循环等结构。独立于堆之外,因此程序计数器是唯一不会导致OutOfMemoryError的区域。
虚拟机栈:
JAVA中说得栈其实就是虚拟机栈,线程私有,每个java方法在调用时会创建一个栈帧,保存局部变量表 操作数栈 动态链接 返回地址 等信息。
局部变量表:基本类型以及引用类型,引用类型是指堆中对象的引用。
Stackoverflowerror指的是虚拟机栈不被允许动态扩展大小时,线程请求栈深度超过了虚拟机栈的最大深度
OutOfMemoryError是虚拟机栈允许动态扩展大小时,无法申请到内存空间。
本地方法栈:同虚拟机栈类似。在Hotspot中合二为一。
方法如何调用?
每一次方法调用,都会向虚拟机栈压入一个栈帧,而每次return或抛出异常,都会导致栈帧弹出。
堆:
线程共用,也就是线程不安全。是虚拟机最大的一块内存空间,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。是垃圾收集器管理的主要区域,因此也被称作GC堆。根据垃圾回收机制,将堆分为新生区、老生区。
最容易出现两类OutOfMemoryError,一类是垃圾回收代价太高、另一类是内存不够。
静态常量池:以class文件形式保存的常量,在类加载后,变为运行时常量池。
方法区(元空间、永久代):
线程共用,存放运行时常量池、静态变量、类信息等数据,在Hotspot中的实现之前为永久代,被元空间取代。元空间可以指定初始和最大空间。
元空间使用直接内存,若不指定最大空间,则元空间会尽可能使用物理内存,减少内存溢出
直接内存:
是在虚拟机之外的内存,由NIO类使用native方法分配,然后通过在堆中的一个引用直接内存的buffer访问
对象的创建:
虚拟机在遇到new的时候,会首先在方法区中寻找该对象的类信息是否加载,若没有加载,则首先要加载该类。
给对象分配内存,有两种方式。
指针碰撞:堆内存规整时,使用的内存和没使用的内存中间有个边界指针,把该指针往没使用过的内存上移动要分配内存的大小那么长的位置就行。
空闲列表:堆内存不规整时,虚拟机维护一个列表记录哪些区域是空闲的,分配的时候在列表上中找一块足够大的,然后更新记录表。
给分配的内存空间初始化为0。
执行初始化方法。
对象的内存布局:
对象的内存区域分为:对象头、实例数据、对齐填充。
对象头:一部分是对象的运行时数据,hashcode、分代年龄、锁状态等、一部分指向类原始数据。
实例数据:对象包含的真正的数据。
对齐填充:占位作用,使得对象占用空间为8字节的整数倍。
对象访问定位:
句柄:在堆中分配一块句柄池,reference指向这个句柄,句柄包含两个指针,一个指向对象实例数据、一个指向类型数据。
直接指针: reference直接指向对象实例。
前者:对象的内存空间被移动时只需要更改句柄而不需要更改reference
后者:节省了一次寻址。
字符串常量池:
对于编译期可确定值的字符串,也就是字符串常量,jvm会将其存入字符串常量池,在堆中。其他常量池在方法区
并且拼接得到的字符串也在编译器就存入了常量池.
常量折叠:将在编译期内可确定值的常量存入常量池:
基本数据类型以及字符串常量
final 修饰的基本数据类型和字符串变量
字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )
因此,要 尽量避免使用new在堆上创建字符串,而使用双引号,可以引入编译器的优化。
String s = new String("abc");这句话创建了几个字符串对象?
会创建 1 或 2 个字符串:
如果字符串常量池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。
如果字符串常量池中没有字符串常量“abc”,那么它将首先在字符串常量池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。