图
本地方法栈
Java虚拟机栈是线程私有的,生命周期跟线程相同。
虚拟机栈的栈元素是栈帧,当有一个方法被调用时,代表这个方法的栈帧入栈;当这个方法返回时,其栈帧出栈。(后入先出,相当于弹夹调用就是放入子弹,返回就是发射,下面是图解栈帧及内部结构)
局部变量表
局部变量表是一组变量值存储空间,用以存储方法参数与方法内部定义的局部变量。在Java程序被编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需的局部变量表的最大容量。
slot 是局部变量表中的空间单位,虚拟机规范中有规定,对于 32 位之内的数据,用一个 slot 来存放,如 int,short,float 等;对于 64 位的数据用连续的两个 slot 来存放,如 long,double 等。引用类型的变量 JVM 并没有规定其长度,它可能是 32 位,也有可能是 64 位的,所以既有可能占一个 slot,也有可能占两个 slot。
操作数栈
操作数栈也被称为操作栈,是一个后入先出的栈。同局部变量表一样,操作数栈的最大深度在编译的时候已经写入到Code属性的max_stacks数据项中。
当一个方法刚刚开始的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是入栈和出栈操作。
举个例子,整数加法的字节码指令iadd在运行的时候在操作数栈中最接近栈顶的两个元素已经存入了两个int类型的数据,当执行这个命令时,这会将两个int类型的数据出栈,相加以后再把结果入栈。
动态连接
每一个栈帧内部都包含一个指向运行时常量池的引用来支持当前方法的代码实现动态链接。在 Class 文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用。
方法返回地址
正常退出的时候存放的是程序计数器中的值
异常
- 线程请求的栈深度>虚拟机栈的允许最大深度,抛出StackOverflowError
- 如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存是,抛出OutOfMemoryError(当前大部分虚拟机都支持动态扩展,只不过虚拟机规范中也允许固定大小的虚拟机栈)
本地方法栈
本地方法栈和虚拟机栈相似,区别就是虚拟机为虚拟机栈执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法服务。本地方法栈中使用的语言,使用方式,数据结构没有强制要求。
程序计数器
也是线程私有的,主要记录线程执行到哪了
堆
所有线程共有
java堆是JVM中内存最大的一块,被所有线程共享。几乎所有的对象实例都在这里分配,所以java堆也是JVM垃圾回收的主要区域。
堆又分为新生代和老年代,新生代进一步可以划分为Eden空间,From Survivor空间、To Survivor空间。比例是8:1:1
新生代
新生代为分成eden、From Survivor、To Survivor!
为什么比例是8:1:1!
这其实跟新生代使用的回收算法有关系
- 一般情况下新生成的对象都会被分配到新生代的Eden区,个别大对象(如数组、很长的字符串)会直接分配到老年代
- 当Eden区快满的时候会进行GC,将继续存活的对象放入From Survivor区,然后清空Eden区 GC完成,然后继续分配对象到Eden区
- 当Eden快满时又进行GC,这时会把Eden区、From Survivor区存活的对象放入To Survivor区,然后清空
- 如此循环,总的来说就是当Eden快满的时候就触发GC copy活对象放到空的Survivor里,然后清空其他两区。但是另一块Survivor空间放不下GC后存活的对象、或者是连续GC15次左右的情况。就把这部分存活对象放入到老年代空间。
这就是新生代的复制算法
老年代
老年代使用的是标记-整理算法这里就不再细说了,文末有传送门我的另一篇文章中有说
异常
堆会抛出OutOfMemoryError也就是常说的OOM,原因有很多比如分配的堆内存太小、程序中有死循环等等
方法区
所有线程共有,也叫永久代
方法区用于存储JVM加载的类信息、final常量、static静态变量等数据,方法区中的数据都是整个程序中唯一的。方法区还包含了运行时常量池,主要存放编译期生成的字面量和符号引用(在类加载后放入)。String对象的字面量就会被放入到运行时常量池中。
异常
方法区也会抛出OOM异常
jdk1.8之后的改动
最最主要的就是元空间取代了方法区(永久代)
空间和永久代的性质是一样的,都是对JVM方法区的实现,作用是一样的。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机JVM内存中,而是使用本地内存。
为什么用元空间取代永久代呢?
- 字符串存在永久代中,容易出现性能问题和内存溢出。
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
- 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
点这里看:垃圾回收机制