类加载子系统:负责从文件系统或网络中加载class信息
方法区:类加载子系统加载的class信息(包括类的字段,方法,常量池)会被保存在方法区中;还有可能存放运行时常量池信息(包括 字符串常量池 和 数字常量 )。在jdk1.6和jdk1.7 方法区也可以叫做永久区,永久区也是有大小的,当方法和字段过多的时候,就会发生内存溢出OOM错误。在jdk1.8、jdk1.9和jdk1.10中,元数据区meta space 取代 永久区 perm Gen,元数据区是一块堆外内存,元数据区相比永久区最大的变化就是在不指定大小时,默认的内存大小是系统的可用内存,当系统的可用内存被耗尽时才会抛出内存溢出OOM错误。
java堆:这块空间是java程序最重要的内存工作区域,是在java虚拟机启动时才建立的。几乎所有的对象实例都会被存储在这块区域里面;
java堆会被分成两个区域。一个叫年轻代,年轻代又被分为两块区域,分别是eden区、survivor区,survivor区又被分为两块区域,分别是from区 和 to 区;一个叫老年代(也可以叫年老代)。为什么要将java堆分成这两块呢。这就和java虚拟机的垃圾回收系统有关系了。
这个对象被实例化之后会先进去eden区,当eden区的大小无法在存放一个对象时,会将eden区中一些对象移动到from区中,这个移动并不属于垃圾回收。当又一次有一个对象被实例,此时eden区和from区都已经无法在存储这个对象,此时将会触发第一次垃圾回收。将依然被引用的对象复制到to区,然后回收eden区和from区中所有对象,此时eden区和from区就是空的,同时原本的to区现在变成了from区,from区变成了to区(简单地说就是每次进行完minor GC后,to区一定是空的),对象每到一次to区,年龄就会增加1,当到达一定的年龄后就会被移动到老年代
直接内存:一块在java堆外的区域,简单来说就是系统的内存(所以大小是受系统内存限制的)。直接内存的读取速度是比java堆要快的,所以对于一些频繁进行读写操作的场景来说处理的效率是要高很多的
垃圾回收系统:垃圾回收系统是java虚拟机的重要组成部分,负责对方法区、java堆、直接内存进行回收,主要的工作重点是在java堆,java的垃圾回收自动完成的。并不能指定某一个对象进行回收,这是和c/c++语言最大的区别。
java栈:每一个java虚拟机线程(java虚拟机线程指的就是类中的方法)都拥有一个私有的java栈,用于java方法的调用,java栈中保存着帧信息,帧信息中就包括了 局部变量表、操作数栈、帧数据区
栈是和线程相关的,线程就是函数,一个函数就对应着一个栈帧。每调用一个函数,函数对应的栈帧就会被压进栈中。java栈和数据结构中的栈类似,也是先进后出,当一个函数返回时,对应的栈帧就会被弹出。这里的返回有两种方法,1.正常返回,使用return;2.抛出异常。
java栈同样是有大小的,当栈空间不足的时候,函数调用就会失败。同时抛出栈溢出错误
栈帧和局部变量、操作数栈、帧数据区的关系
从上面的图可以看出来,局部变量是存在于栈帧中的,如果一个函数返回了则栈帧也会被销毁,局部变量表中的局部变量也会消失。而且局部变量的内存空间是可以被重复利用的,怎么理解这句话。在一个线程中,每个局部变量可能都有自己的作用域,在一个作用域中声明一个局部变量,然在这个作用域之后在声明一个局部变量,则后者将复用前者使用的内存空间。从而达到节省资源的效果
//伪代码,这是一个线程
public void test(){
//成员变量a的作用域
{
int a = 10;
}
//成员变量b声明,将复用a的内存空间
int b = 12;
}
操作数栈也是一种先进后出的结构,主要是用于保存计算过程中的中间结果
帧数据区中保存着访问常量池的指针,方便程序访问常量池
本地方法栈:这个对于刚了解java虚拟机的来说很容易和java栈混淆。本地方法栈简单地讲就是用于本地方法的调用,这是java虚拟机可以扩展的一个表现,这个指的本地方法通常是用c语言编写的
PC寄存器:java虚拟机会为每一个java线程创建pc寄存器,一个java线程总是在执行一个方法,我们就把这个方法叫做当前方法。如果当前方法不是本地方法,pc寄存器就会指向正在被执行的指令;如果当前方法是本地方法,pc寄存器的值就是undefined
jvm执行引擎:执行引擎是java虚拟机最核心组件之一,负责执行虚拟机的字节码