1.概述:
- 内存分区:JVM会把自己所管理的所有内存区域进行分区。
- 各个区域的服务对象
- 各个区域中分别存放了什么内容
- 存放的数据是如何创建的
- 这些数据在各个区域中存放,存储的布局是什么样的
- 如何访问存放在不同内存区域的数据
- 各个区域的创建和销毁时间
- 随着进程的启动和结束而创建和销毁
- 随着线程的启动和结束而创建和销毁
- 各个区域服务过程中可能产生的问题(异常)
- 各个区域中可能产生的异常
- 如何解决上述各种异常
2.JVM内存分区
3.JVM内存各个区域的比较
|
JVM内存分区(JVM运行时数据区) |
||||
所有线程共享的数据区 (有效范围:整个进程) |
线程隔离的数据区(线程私有内存) (有效范围:单个独立线程) |
||||
|
方法区 Method area |
堆(又称”GC堆”) Heap |
程序计数器 Program Counter Register |
虚拟机栈 VM Stack |
本地方法栈 Native Method Stack |
概述 |
|
|
类似于虚拟机栈
|
||
占用内存空间大小 |
|
|
较小内存空间 |
和具体的java成员方法代码有关; 和该线程包含的成员方法个数有关。 |
和具体的Native方法有关 Native方法可以是其他语言(如Python、shell) |
服务对象 |
一个进程中的所有线程 (所有线程共享方法区) |
一个进程中的所有线程 所有线程共享Heap区域内存 |
当前线程 (每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储) |
当前线程 每个线程都有自己独有的该部分内存 |
当前线程 每个线程都有自己独有的该部分内存 一个本地方法栈中有多个栈帧 Native method stack=栈帧1+栈帧2+... 每个栈帧服务于单个的Native方法 |
生命周期 |
Jvm启动时创建 |
其生命周期与当前线程相同 |
其生命周期与当前线程相同 |
其生命周期与当前线程相同 |
|
存放内容 + 功能 |
v 存放内容: 概述: 方法区用于存储已经被JVM加载的类信息+常量+静态变量+即时编译器编译后的代码 详细解释:
v 功能: |
v 存放内容: 用于存放对象实例 用于存放所有数组 |
v 存放内容: 指示当前线程将要执行的下一条字节码指令的行号 v 功能: 作为当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令 |
v 存放内容: 虚拟机栈=栈帧1+栈帧2+.... 线程中的每个java成员方法对应于一个栈帧 栈帧=局部变量表+操作数栈+动态链接+方法出口 局部变量表=8种基本数据类型+对象引用(地址)+returnAddress(一条字节码指令的地址) 局部变量表是在编译期间生成的 v 功能: 每个java成员方法执行过程中都会创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个java方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 一个虚拟机栈中有多个栈帧(stack frame), VM Stack=栈帧1+栈帧2+... 每个栈帧服务于单个的java方法。 |
v 存放内容: v 功能: 每个Native成员方法执行过程中都会创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个Native方法从调用到执行完成的过程,就对应着一个栈帧在本地方法栈栈中入栈到出栈的过程。 |
各个区域存放的数据是如何创建的? |
Step1,JVM遇到一个new指令时, Step2,先去“方法区”的运行时“常量池”中定位到一个类的符号引用, Step3,检查step2中找到的该类的符号引用所代表的实际的类是否已被加载、解析和初始化过,如果没有则执行相应的“类的加载过程” Step4,该类的加载检查通过之后,JVM才开始为该类的新生对象分配内存(在heap内存中分配出一部分给新生对象) 至于具体的内存分配过程,则要看JVM所管理的堆内存空间的连续性是怎样的:如果堆内存是规整的,则通过“指针碰撞”方法为新建对象分配内存;如果堆内存是不规整的,只能通过“空闲列表”分配堆内存空间,并且更新“空闲列表” Step5,由step4可知,从JVM堆内存空间中分配一部分给new出的对象,其分配方法和堆内存空间是否规整有关,而堆内存空间是否规整又和垃圾回收机制有关。如果JVM中的垃圾回收期带有压缩整理功能,则堆内存空间是规整的,可以使用“指针碰撞”方法分配堆内存空间给new出的对象,否则就只能使用维护“空闲列表”的方法分配堆内存。 Step6,给new出的对象分配好堆内存空间之后,还要对该对象进行必要的设置(即为对象添加“对象头”),如该对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄信息等。这些信息存放在对象的对象头中。 Step7,执行init方法,初始化新建的对象。 Step8,将新生的对象的引用入栈(虚拟机栈)
方法一,为分配堆内存的操作加上同步处理 方法二,为单个线程分配独立的堆内存空间TLAB(本地线程分配缓冲),然后执行每个线程时,在TLAB上分配内存给new出的对象 |
u |
u |
||
数据在这些区域存储的布局是怎样的? |
对象头(Header)+实例数据(Instance Data)+对齐填充(padding)
ü 对象自身的运行时数据=哈希码+GC分代年龄+锁状态标志+线程持有的锁+偏向线程ID+偏向时间戳 ü 类型指针:这是一个指针,其中存储了一个地址。这个地址指向JVM内存的“方法区”中的某个部分,“方法区”该部分内容存放的是该对象实例对应的类信息 ü (数组长度)
|
u |
u |
u |
|
如何使用存放在这些区域的数据? |
u |
使用句柄:堆内存-->句柄池;句柄=对象数据+指向类信息的指针 直接指针:没有句柄池 |
u |
u |
u |
服务过程中可能产生的异常 |
OutOfMemoryError:如果方法区中没有足够的内存存放相应的类信息或+常量+静态变量+即时编译器编译数据,并且方法区再也无法扩展时,就会抛出该异常 |
OutOfMemoryError:如果堆中没有足够的剩余内存来存放新的实例对象,并且堆也无法再扩展时,就会抛出该异常 |
不会产生异常 (这是JVM中唯一一个不会产生OutofMemoryError的区域) |
u StackOverflowError:如果线程请求的栈深度大于JVM所允许的深度,就抛出该异常 u OutOfMemoryError:如果虚拟机栈可以动态扩展,但是在扩展时无法申请到足够的内存,就会抛出该异常 |
u StackOverflowError:如果线程请求的栈深度大于JVM所允许的深度,就抛出该异常 u OutOfMemoryError:如果虚拟机栈可以动态扩展,但是在扩展时无法申请到足够的内存,就会抛出该异常 |