1.程序计数机器
①程序计数器是一块较小的内存区域,它可以看做当前线程所执行字节码的行号指示器
②为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,称这类内存区域为线程私有的内存
③程序计数器是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
2.虚拟机栈
①Java虚拟机栈也是线程私有的,它的生命周期与线程相同
②虚拟机栈是描述Java方法执行的内存模型
③如果线程请求的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
④如果无法申请到足够的内存,就会抛出OutOfMemoryError异常
3.本地方法栈
①虚拟机栈是为执行Java方法服务,本地方法栈是为虚拟机使用到的native方法服务
4.Java堆
①Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
②此内存区域唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
③-Xmx和-Xms控制堆大小,如果堆中没有内存完成实例分配,并且也无法扩展时,将会抛出OutOfMemoryError异常
5.方法区
①方法区是各线程共享的内存区域,用来存储以被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
②方法区的内存回收目标主要是针对常量池的回收和对类的修改
6.运行时常量池
①运行时常量池是方法区的一部分
②用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
7.直接内存
①直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域
②通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用来操作,避免了在Java堆和Native堆中来回复制数据
8.对象的创建
①虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析、初始化过。如果没有,必须先执行相应的类的加载过程
②在类的加载检测通过后,虚拟机将会为新生对象分配内存。对象所需内存大小在类加载完成后便可完全确定。为对象分配内存空间的任务等同于把一块确定大小的内存从Java堆中划分出来
9.对象的内存布局
①对象内存中的布局可以分为,对象头、实例数据和对其填充三部分
②对象头包括两部分信息
第一部分:存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等 | 存储内容 | 标志位 | 状态 | | —- | —- | —- | | 对象哈希码、对象分代年龄 | 01 | 未锁定 | | 指向锁记录的指针 | 00 | 轻量级锁定 | | 指向重量级锁的指针 | 10 | 膨胀(重量级锁定) | | 空,不需要记录信息 | 11 | GC标记 | | 偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 |
第二部分:类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
10.对象的访问定位
①reference类型在Java虚拟机规范中只规定了一个指向对象的引用
②使用句柄访问,Java会在堆中划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息
③使用直接指针访问
④使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改
⑤使用直接指针访问最大好处是速度快,它节省了一次指针定位的时间开销
11.Java堆溢出
①对象数量达到最大堆的容量限制后会产生内存溢出异常
②堆的最小值-Xms,堆的最大值-Xmx,-XX:+HeapDumpOnOutOfMemoroyError可以让虚拟机在出现内存溢出时Dump出当前的内存堆转储快照以便时候分析
③内存泄漏,可通过工具查看泄漏对象到GC Roots的引用链
④内存溢出,内存中的对象确实必须活着,就应当检查虚拟机堆的参数(-Xmx和-Xms)
12.虚拟机栈和本地方法栈溢出
①栈容量有-Xss参数设定
②如果线程请求栈的深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
③如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
13.方法区和运行时常量池溢出
①通过-XX:PermSize和-XX:MaxPermSize限制方法区大小
14.本机直接内存溢出
①DirectMemory容量可以通过-XX:MaxDirectMemorySize指定