1.Java虚拟机内存模型
JVM 规范,JVM 内存共分为虚拟机栈、本地方法栈、程序计数器、方法区、堆五大部分
- 1.方法区
- 2.堆
- 3.Java虚拟机栈
- 4.本地方法栈
- 5.程序计数器
其中 方法区及堆 是所有线程共享的数据区
虚拟机栈及本地方法栈,程序计数器 全都是线程隔离的, 为什么这么区分,根本原因还是 每一块地址存放的东西不一样导致的
1.1 进程和线程区别
为什么要解释 进程和线程 ?
因为 JVM的内存区域 就是提供给进程和线程的
图中的方法区和堆是分配给进程的, 同一个进程的所有也线程共享区域, 而剩余部分的栈和程序计数器则是分配给每个独立线程
- 进程是资源分配的最小单位
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配一个地址空间、建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵
- 一个进程至少包含一个线程
- 同一个进程中可以包括多个线程, 多个线程共享整个进程的资源(寄存器、堆栈、上下文)
- 进程之间的通信则需要以通信的方式(IPC)进行
- 线程是程序执行的最小单位。
- 线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费要比进程小很多,同时创建一个线程的开销也要比进程小很多
- 线程之间的通信更加方便,同一进程下的线程共享全局变量、静态变量等数据
1.2 程序计数器
学过计算机组成原理的,肯定听过这个东西,程序计数器(Program Counter Register)
其实它是一块较小的内存空间,它可以看成一个寄存器,用来存放当前正在被执行的指令,也可以放下一个被执行的指令
Java虚拟机的多线程是通过线程轮询切换并分配处理器执行时间的方式来实现的,任何时刻,一个处理器CPU的一个核 只能处理一个线程中的一条指令
- 比如你是4核CPU, 那就是你能够处理4个线程
- 如果要处理多于4个线程的业务,那么cpu就会通过时间片轮转来并行处理多个线程
- 第一个线程处理30%,切换到第二个处理30%,再切换到第三个,这样轮转
- cpu调度线程的时间足够快,就造成了多线程的 同时执行的假象
- 如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源
- 每个线程被调度的次数会降低,线程的执行效率降低
线程切换之后为了恢复到正确的执行位置,每条线程都需要拥有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,便于CPU进行切换的时候 记着你上次执行到哪里了,你的下一个一个任务是什么
所以程序计数器是线程私有的内存,也就是它属于线程隔离区,此区域是唯一一个在Java虚拟机规范中没有任何OutOfMemoryError情况的区域。。
1.3 Java虚拟机栈
Java虚拟机栈 也是线程私有的,即他的生命周期和线程相同。
每个线程拥有一个虚拟机栈,在某个线程的运行过程中,如果有新的方法调用,那么该线程对应的栈就会增加一个存储单元,即栈针(Stack Frame)。
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈针, 用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法从调用至完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用信息等
方法运行结束时,该方法对应的帧将被删除,参数和局部变量所占据的空间也随之释放。
线程回到原方法,继续执行。当所有的栈都清空时,程序也随之运行结束。
1.4 本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈类似,但是本地方法栈为Native方法服务的,我们都知道 java 底层很多方法都是C++代码运行的, 我们如何调用这部分代码方法, 其实就是native方法。
1.5 方法区
方法区(Method Area)又叫非堆,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译之后的代码等数据。
方法区主要存放类的信息、常量池、方法数据、方法代码等;JDK8之后,取消了永久代,提出了元空间,并且常量池、静态成员变量等迁移到了堆中;
1.5 Java堆-重点
Java堆(Java Heap)是Java虚拟机管理的内存中最大的一块。也是我们打交道最多的一块,也是最容易出问题的一块
- Java堆是被所有线程共享的一块数据区域,它的存在就是为了存放对象实例
- 在虚拟机启动时创建,几乎所有的对象实例都在这里分配内存。
- 还有一些是即时编译JIT的,所有的对象都分配在堆上也逐渐变得不是那么“绝对”
- JIT编译器(just in time 即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为(Hot Spot Code 热点代码,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化
- Java堆可以处于物理上不连续的内存中,
- 可以固定大小或者实现可扩展 通过-Xmx(最大堆内存)和-Xms(最小堆内存)来控制 堆大小。
- 如果在堆中没有内存可分配,并且堆也无法继续扩展时,将会抛出OutOfMemortError异
至此 我们大致介绍了 Java虚拟内存模型及管理,下面我们会针对每个区域 进行详细解释