以前咱们探讨过一个.class文件是如何被加载到jvm中的。可是jvm内又是如何划份内存的呢?这个内被加载到了那一块内存中?jvm内存划分也是面试当中必被问到的一个面试题。java
其实这个问题很是简单,JVM在运行咱们写好的代码时,他是必须使用多块内存空间的,不一样的内存空间用来放不一样的数据,而后配合咱们写的代码流程,才能让咱们的系统运行起来。面试
举个最简单的例子,好比我们如今知道了JVM会加载类到内存里来供后续运行,那么我问问你们,这些类加载到内存之后,放到哪儿去了呢?想过这个问题吗?安全
因此JVM里就必须有一块内存区域,用来存放咱们写的那些类。数据结构
包括咱们定义的成员变量,类变量,方法,局部变量等等,都在jvm内存中对应着一块内存来记录存储。并发
在JDK1.8以前的版本里,表明JVM的一块区域。在1.8版本之后,这块区域的名字改了,叫作“Matespace”,能够认为是“元数据空间”这样的意思,固然这里主要存放的仍是咱们本身写的各类类的相关信息。jvm
举个栗子。有以下两个类,People类没有成员变量,而Student类有一个name的类变量。spa
public class Student{ private static String name = "lisi"; } public class People{ public static void main(){ Student student = new Student(); } }
这两个类被加载到JVM,就会存放在这个方法区里面(注意:若是读过我以前的章节,就会明白这里的加载表明的是:加载->验证->准备->解析->初始化,类的全部类变量都会被赋值)。以下图操作系统
咱们知道,被加载到jvm的类对象是咱们写的.java文件被编译以后的.class文件。线程
在编译事后会将咱们的代码编译成计算机能读懂的字节码。而这个.calss文件就是,就是咱们代码编译好的字节码了。code
加载到内存之后,字节码执行引擎就开始工做了。去执行咱们编译出来的代码指令,以下图
此时问题来了,咱们是否是须要一块内存空间来记录咱们字节码执行引擎目前执行到了哪行代码?这一块特殊的内存区域就是“程序计数器”
这个程序计数器就是用来记录当前执行的字节码指令的位置。
以下图:
到这里我相信会有人产生疑惑,就按照当前的代码顺序执行就好了,为何要记录执行到哪里了?
由于咱们写好的代码可能会开启多个线程并发的执行不一样的代码。可能当前线程这段代码尚未执行完毕,就上下文切换到另外一段代码中。
当线程再次上下文切换到以前的代码时,就须要一个专门记录当前线程执行到了哪一条字节码。因此,每个线程都有这本身的程序计数器。
以下图:
java代码在执行的时候,必定是某个线程来执行某个方法中的代码。
当线程执行到某个方法的时候,若是这个方法有局部变量,那么就须要一块区域来存放局部变量的数据信息。这个区域就叫作java虚拟机栈。
每个线程都有一个本身的java虚拟机栈,好比说当执行main方法的时候就会有一个main线程,用来存放main方法中定义的局部变量
public static void main(){ People people = new People(); int i = 9; }
好比上面的main()方法中,其实就有一个"people"的局部变量,他是引用一个People的实例对象的,这个对象咱们先无论他。而后有一个"i"的局部变量。
以下图:
我想你们应该都知道栈的数据结构,后进先出。当方法执行完毕之后,这个栈桢就会出栈,里面的局部变量信息就会从内存删除。因此局部变量是线程安全的。由于只有当前线程能获取到这个值。
为何要用后进先出的数据结构?
假设a方法当中同步调用b方法,此时a方法的栈桢先入栈,而后再是b方法的栈桢入栈。b方法执行完毕后,b方法的栈桢出栈,继续执行a方法。因此使用一个后进先出的栈结构是很是完美的。
此时jvm的内存模型图以下:
这一块内存是很是很是重要的。
咱们实例化的全部对象都是存放在这个内存中。这个实例化的对象里面会包含一些数据,咱们用上面的代码来作栗子。
public class Student{ private String name = "lisi"; public String getNmae(){ return name; } } public class People{ public static void main(){ Student student = new Student(); student.getName(); } }
仍是这个代码,当main线程执行main()方法的时候,首先在堆内存中实例化Student对象,而后在局部变量中建立student,student存的是实例化Student对象的内存地址。而后执行Student对象的getName()方法。
以下图:
由上图能够看出来,栈空间是封闭的,是线程安全的,而堆内存中是咱们主要发生线程不安全的地方,由于堆内存的空间全部的线程其实都是能共享的。
此时jvm的内存划分的最终模型为:
不少java程序猿对这一块区域的接触是很是少的。
其实在JDK的不少底层代码API中,好比NIO。
若是你去看源码会发现不少地方的代码不是java写的,而是走的native方法去调用本地操做系统里面的一些方法,可能调用的都是c语言写的方法。
好比说:public native int hashCode();
在调用这种native方法的时候,就会有线程对应的本地方法栈,这个其实相似于java虚拟机栈。也是存放各类native方法的局部变量表之类的信息。
还有一块区域,是否是jvm的,经过NIO中的allocateDirect这种API,能够在jva堆外分配内存空间,而后经过java虚拟机栈里的DirectByteBuffer来引用和操做堆外内存空间。
基本上jvm的核心内存区域的功能都解释清楚了,面试能回答到这一个地步应该也能顺利经过了。
咱们须要重点关注的是方法区,程序计数器,java虚拟机栈和java堆内存这些内存区域的做用。