
程序计数器:
- 线程私有,生命周期与线程同步;
- 一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器;
- 为了线程切换后恢复到执行位置,每个线程都有一个计数器,各线程之间互不影响、独立存储;
- 如果正在执行java方法,计数器记录的是虚拟机字节码指令的地址;如果是Native方法,计数器值将为空;
- 该区域是唯一一个在《java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域;
虚拟机栈:
- 线程私有,生命周期与线程同步;
- 每个方法被执行的时候,java虚拟机将会同步创建一个栈帧用于存储方法的局部变量表、操作数栈、动态连接、方法出口等
- 局部变量表:保存的数据类型在局部变量表中以局部变量槽(Slot,64为的long和double占2个连续的变量槽,其余的占一个)表示,局部变量槽的多少在编译期间完成分配
- 保存编译期可知的基本数据类型
- 保存对象引用(reference):并不是对象本身,可能是对象的指针或者是代表对象的句柄等;
- 保存returnAddress:字节码指令的地址;
- 动态连接:每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接;
- 静态链接:当字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变。这种情况下将调用方法的符号引用转换为直接引用的过程称为静态链接:
- 动态链接:如果被调用的方法在编译期无法被确定下来,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种转换过程具备动态性,因此被称为动态链接;
- 操作数栈:LIFO,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据
- 栈的最大深度在编译时写人到code属性的max_stacks中;
- 当一个方法刚开始执行时,该方法的操作数栈是空的。在方法执行过程中,会有各种字节码指令往操作数栈中入栈和出栈内容(只能通过标准的入栈push和出栈pop操作来完成一次数据访问,不是采用索引访问方式);
- 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令;
- 如果线程请求的栈深度大于虚拟机允许的深度,会抛出 StackOverflowError 异常。如果虚拟机栈可以动态扩容,当无法申请到足够空间时,会抛OOM;
- 方法出口:方法正常执行完成 和 出现未处理的异常,非正常退出;
- 局部变量表:保存的数据类型在局部变量表中以局部变量槽(Slot,64为的long和double占2个连续的变量槽,其余的占一个)表示,局部变量槽的多少在编译期间完成分配
- 一个方法被调用直至执行完毕的过程就是栈帧入栈到出栈的过程;
本地方法栈:
- 与虚拟机栈作用属性类似,其区别只是虚拟机栈为虚拟机执行java方法。而本地方法栈则为虚拟机使用到的 Native方法(Java通过 JNI 直接调用本地 C/C++ 库) 服务;
- 《java虚拟机规范》对本地方法栈中方法使用的语音、使用的方式与数据结构并没有任何强制规定。
- 本地方法栈也会在深度溢出和申请内存不足抛出 StackOverflowError 和 OOM ;
Java堆:
- 所有线程共享的。虚拟机管理的内存最大的一块,在虚拟机启动时创建,该区域的唯一目的是存放对象实例【new创建的对象和数组】(java实例都分配在堆上,并非绝对(逃逸分析、栈上分配、变量替换等));
- java堆是垃圾收集器管理的内存区域(Garbage Collected Heap);
- 通过-Xms和-Xmx设定堆扩展。没有内存分配时抛OOM;
方法区:
- 由 运行时常量池 和 Class文件常量池组成;
- 各个线程共享的,用于存储已被虚拟机加载的类型信息、常量、静态常量、即时编译器编译后的代码缓存等数据;
- 《java虚拟机规范》对方法区的约束是非常宽松的,除了和java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至可以选择不实现垃圾收集(但并非数据进入方法区就“永久”存在);
- 和堆、栈的关系:UserInfo【存在方法区】 info【存在虚拟机栈】=new UserInfo();【java堆】
- 方法区存储的数据:
- 类型信息:类型的全限定名、超类的全限定名、直接超接口的全限定名、类型标志、类的访问描述符;
- 类型的常量池:存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符号引用。常量池中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的字符引用,所以它也是动态连接的主要对象(在动态链接中起到核心作用)。
- 字段信息:字段修饰符、类型、名称;
- 方法信息:方法修饰符、类型、名称、参数(个数、类型、名称)、方法字节码、操作数栈和该方法在栈帧中的局部变量区大小、异常表;
- 类静态变量:
- 指向类加载器的引用:每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到;
- 指向Class实例的引用:类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过Class.forName(String className)来查找获得该实例的引用,然后创建该类的对象;
- 方法表:
运行时常量池:
- 存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放;
- 除了保存Class文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中;
- 具备动态性。java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。
直接内存(Direct Memory):
- 并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定意思的内存区域;
- JDK1.4中加入了NIO(New Input/Output)类,引入了一种基于 Channel 与 Buffer 的 I/O 形式,它可以使用Native函数直接分配堆外内存(会受本机总内存限制),然后通过一个存储在java堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在java堆和Native堆中来回复制数据;
【该文为个人学习总结,不对或者有缺失的地方,欢迎指正】
网友评论