JVM的组成
JVM 由类加载器子系统、运行时数据区、执行引擎以及本地方法接口组成。
JVM 的工作过程
类加载机制
关于类加载机制,前面已经做过介绍,这里就不再进行介绍了。
详情请见:类加载机制
Java内存模型
JVM内存划分:
- 方法区(线程共享):常量、静态变量、JIT(即时编译器)编译后的代码也在方法区存放。
- 堆内存(线程共享):垃圾回收的主要场地。
- 程序计数器:当前线程执行的字节码的位置提示器。
- Java虚拟机栈(栈内存):保存局部变量,基本数据类型以及堆内存中对象的引用变量。
- 本地方法栈(C栈):为JVM提供使用native方法的服务。
内存模型介绍:(功能、是否线程共享、生命周期、抛出的异常)
1、程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线性恢复等功能都需要依赖这个计数器来完成。另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
主要作用有两个:
- 1、字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 2、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪而了。
注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
2、Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型。
Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。(实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
局部变量表:
主要存放了编译器可知的各种数据类型(boolean\byte\char\short\int\float\long\double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
Java虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
- StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就会抛出 StackOverFlowError 异常。
- OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。
Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
3、本地方法栈
和虚拟机栈所发挥的作用非常相似。
区别是:Java虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间。也会出现 StackOverFloeError 和 OutOfMemoryError 两种异常。
4、堆
Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap),从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代;再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好的回收内存,或者更快的分配内存。
永久代说明:
- jdk1.6及之前:常量池分配在永久代。
- jdk1.7:有,但是已经逐步失去作用。
- jdk1.8及之后:无(java.lang.OutOfMemoryError:PermGen space,这种错误将不会出现在jdk1.8中)。
- 在jdk1.8中移除整个永久代,取而代之的是一个叫“元空间(Metaspace)”的区域
- (永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受本机的物理内存限制)
5、方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与Java堆区分开来。
HotSpot 虚拟机中的方法区也常被称为“永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理Java堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
JVM 启动参数设置
我们学习JavaGC机制的目的是为了实用,也就是为了在JVM出现问题的时候分析原因并解决之,JVM监控与调优主要的着眼点在于如何配置、如何监控、如何优化3点上。
1、标准参数(-),所有的 JVM 实现都必须实现这些参数的功能,而且向后兼容;
2、非标准参数(-X),默认 JVM 实现这些参数的功能,但是并不保证所有 JVM 实现都满足,且不保证向后兼容;
3、非Stable参数(-XX),此类参数各个 JVM 实现会有所不同,将来可能会随时取消,需要慎重使用(但是,这些参数往往是非常有用的);