1、总体机制中不重要的部分
1.1 本地接口 Native Interface
本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序。因为 Java 诞生的时候是 C/C++ 横行的时候,要想立足,必须有能力调用 C/C++。于是就在内存中专门开辟了一块区域处理标记为 native 的代码,它的具体做法是 Native Method Stack 中登记 native 方法,在Execution Engine 执行时加载 native libraies。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过 Java 程序驱动打印机或者 Java 系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用 Socket 通信,也可以使用 Web Service 等等,不多做介绍。
1.2 本地方法栈 Native Method Stack
专门负责在本地方法运行时,提供栈空间,存放本地方法每一次执行时创建的栈帧。它的具体做法是在 Native Method Stack 中登记 native 方法,在 Execution Engine 执行时加载本地方法库。
native方法举例:
public static native void yield();
栈:stack
堆:heap
如果抛开JVM内存结构,单独来看栈和堆其实是没有区别的,都是只先进后出的数据结构。
1.3 程序计数器
也叫PC寄存器(Program Counter Register)。用于保存程序执行过程,下一条即将执行的指令的地址。也就是说能够保存程序当前已经执行到的位置。这个位置由执行引擎读取下一条指令,是一个非常小的内存空间,从内存空间使用优化这个角度来看:几乎可以忽略不记。
1.4 执行引擎 Execution Engine
作用:用于执行字节码文件中的指令。
执行指令的具体技术:
解释执行:第一代JVM
即时编译:JIT,第二代JVM。
自适应优化:目前Sun的Hotspot JVM采用这种技术。吸取了第一代JVM和第二代JVM的经验,在一开始的时候对代码进行解释执行, 同时使用一个后台线程监控代码的执行。如果一段代码经常被调用,那么就对这段代码进行编译,编译为本地代码,并进行执行优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。
芯片级直接执行:内嵌在芯片上,用本地方法执行Java字节码。
1.5 直接内存
1、作用
在特定场景提高性能
2、应用场景
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 本机直接内存的分配不会受到 Java 堆大小的限制,受到本机总内存大小限制。 配置虚拟机参数时,不要忽略直接内存防止出现 OutOfMemoryError 异常。
3、直接内存和堆内存比较
直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显。直接内存I/O读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显。
2、方法区
2.1 概念
- 标准层面:方法区(Method Area)
- 具体实现层面:
- <=1.6 永久代(PermGen)
- =1.7永久代依然存在,但是已经开始提出:去永久代。
- =1.8元空间(Meta Space)。
2.2 概念的辨析
- 从方法区角度来说
- 方法区的具体实现:JDK<=1.7时,使用永久代作为方法区
- 方法区的具体实现:JDK>=1.8时,使用元空间作为方法区。
- 从堆空间角度来说
- 新生代:从标准和实现层面都确定属于堆
- 老年代:从标准和实现层面都确定属于堆。
- 永久代
- 名义上属于堆
- 实现上不属于堆,它另外有一个名字:非堆(Non-heap)
2.3 方法区存放内容
类信息:类中定义的构造器、接口的定义
静态变量(类变量):private static String some;
常量:public static final String SYS_MEDDAGE=“hello”
运行时常量池:“a”、“abc”
类中静态方法的代码
2.4 类加载机制和方法区的关系
类加载到JVM内存后,类的静态储存结构转化为方法区的运行时数据结构
类加载后,会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口。
类的字节码文件中有存放静态数据的常量池,类加载到内存后,字节码文件中的常量池会映射到方法区中的常量池。
说明:即使多个线程执行的是同一个方法,也并不是都拥挤到方法区去找方法代码。而是取得一份方法中代码指令的拷贝。
3、虚拟机栈
3.1 总体结构
3.2 栈帧
方法的每一次调用都会产生一个栈帧,线程中每一次调用一个方法,就会在虚拟机栈中开辟一块空间保存栈帧。
方法本次执行完成,本次执行对应的栈帧就会被释放。
整个线程要调用的所以方法都执行完成,则整个线程空间释放。
1、栈帧储存的数据
方法在本次执行过程中所用到的局部变量、动态链接、方法出口等信息。栈帧中主要保存三类数据:
本地变量(Local Variables):也叫局部变量,输入参数和输出参数以及方法内的变量。
栈操作(Operate Stack):记录出栈、入栈操作
栈帧数据(Frame Data):包括类文件、方法等等
2、栈帧的结构
局部变量表:方法执行时的参数、方法体内声明的局部变量
操作数栈:存储中间运算结果,是一个临时储存空间。
栈数据区:保存访问常量池指针,异常处理表
3、栈帧工作机制
当一个方法 A 被调用时就产生了一个栈帧 F1,并被压入到栈中,
A 方法又调用了 B 方法,于是产生栈帧 F2 也被压入栈,
B 方法又调用了 C 方法,于是产生栈帧 F3 也被压入栈,
……
C 方法执行完毕后,弹出 F3 栈帧;
B 方法执行完毕后,弹出 F2 栈帧;
A 方法执行完毕后,弹出 F1栈帧;
……
遵循“先进后出”的原则
图示在一个栈中有两个栈帧:
栈帧 2 是最先被调用的方法,先入栈,
然后方法 2 又调用了方法 1,栈帧 1 处于栈顶的位置,
栈帧 2 处于栈底,执行完毕后,依次弹出栈帧 1 和栈帧 2,
线程结束,栈释放。
每执行一个方法都会产生一个栈帧,保存到栈的顶部,顶部栈就是当前方法,该方法执行完毕后会自动将此栈帧出栈。
两组配套概念:
①串行,同步操作,按顺序执行,前一个操作不执行完后面操作就要等待
②并行,异步操作,由操作系统和CPU调度,不同操作之间不需要互相等待