Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
1 方法区
简单说方法区用来存储类型的元数据信息,一个.class文件是类被java虚拟机使用之前的表现形式,一旦这个类要被使用,java虚拟机就会对其进行装载、连接(验证、准备、解析)和初始化,而装载后的结果就是由.class文件转变为方法区中的一段特定的数据结构。
(1)方法区存储的信息
类型信息:全限定名、直接超类的全限定名、类的类型还是接口类型、访问修饰符、直接超接口的全限定名的有序列表
字段信息:字段名、字段类型、字段的修饰符
方法信息:方法名、方法返回类型、方法参数的数量和类型(按照顺序)、方法的修饰符
其他信息:除了常量以外的所有类(静态)变量、一个指向ClassLoader的指针、一个指向Class对象的指针、常量池(常量数据以及对其他类型的符号引用)
(2)永久代、元空间
方法区是java中的接⼝,永久代、元空间是具体实现类。
永久代,以前的方法区的实现,jdk8以后元空间代替了存在堆区。
元空间,1.8及以后方法区的实现,直接内存、OS内存。
(3)常量池
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
2 本地方法栈
本地方法栈和java虚拟机栈差不多,唯一的区别是,java虚拟机栈为java虚拟机执行java服务,本地方法栈为虚拟机执行本地方法服务,也可以理解为 java调⽤c、c++的动态链接库,运⾏⾥⾯的函数需要的栈。
3 程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
4 虚拟机栈
Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
4.1 动态连接
虚拟机运行的时候,运行时常量池会保存大量的符号引用,这些符号引用可以看成是每个方法的间接引用。如果代表栈帧A的方法想调用代表栈帧B的方法,那么这个虚拟机的方法调用指令就会以B方法的符号引用作为参数,但是因为符号引用并不是直接指向代表B方法的内存位置,所以在调用之前还必须要将符号引用转换为直接引用,然后通过直接引用才可以访问到真正的方法。
如果符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为静态解析。如果是在运行期间转换为直接引用,那么这种转换就成为动态连接。
4.2 局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
4.3 操作数栈
操作数栈 和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作——压栈和出栈—来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。
4.4 返回地址
方法的返回分为两种情况,不过无论是那种方式的方法结束,在退出当前方法时都会跳转到当前方法被调用的位置。
(1)一种是正常退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者。
(2)一种是异常导致的方法结束,这种情况是不会传返回值给上层的调用方法。
5 堆
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
新生代如何进入老年代
(1) 大对象直接进入年老代
对象大小超过Eden区的一半直接进入年老代。
(2) 长期存活的对象将进入年老代
虚拟机给每个对象定义了一个对象年龄计数器,在对象在Eden创建并经过第一次Minor GC后仍然存活,并能被Suivivor容纳的话,将会被移动到Survivor空间,并对象年龄设置为1。每经历过Minor GC,年龄就增加1岁,当到一定程度(默认15岁,可以通过参数-XXMaxTenuringThreshold设置),就将会晋升年老代。
(3) 动态对象年龄判定
了更好地适应不同程序内存状况,虚拟机并不硬性要求对象年龄达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入年老代。
(4)空间分配担保
当Eden和一个Survivor区中依然存活的对象无法放入到Survivor中,则通过分配担保机制提前转移到老年代中。