Java内存模型对比参考
:Java内存模型
0、序言(运行时数据区)
JVM中的堆啊、栈啊、方法区什么的,是Java虚拟机的内存结构,Java程序启动后,会初始化这些内存的数据。
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。每个区域都有各自的作用。
分析 JVM 内存结构,主要就是分析 JVM 运行时数据存储区域。JVM 的运行时数据区主要包括:堆、栈、方法区、程序计数器等。
而JVM 的优化问题主要在线程共享的数据区中:堆、方法区。
内存结构就是上图中内存空间这些东西,而Java内存模型,完全是另外的一个东西。
Java中的内存分配:
Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
具体划分为如下5个内存空间:
- 栈:存放局部变量
- 堆:存放所有new出来的东西
- 方法区:被虚拟机加载的类信息、常量、静态常量等。
- 程序计数器(和系统相关)
- 本地方法栈
1、程序计数器(Program Counter Register)
程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。
一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。
为什么需要程序计数器 ?
我们知道对于一个处理器(如果是多核cpu那就是一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。
注意:如果线程执行的是个java方法,那么计数器记录虚拟机字节码指令的地址。如果为native【底层方法】,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
2、虚拟机栈(Java Stack)
又叫Java栈,就是我们平常说的栈
虚拟机栈也是线程私有的,生命周期与程序计数器相同,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。
虚拟机栈-过程详情描述
①每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,
②虚拟机栈表示Java方法执行的内存模型,每调用一个方法,就会生成一个栈帧(Stack Frame)用于存储方法的本地变量表、操作栈、方法出口等信息,当这个方法执行完后,就会弹出相应的栈帧。
如果请求的栈的深度过大,虚拟机可能会抛出StackOverflowError
异常,如果虚拟机的实现中允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出OutOfMemoryError
异常。
栈帧(Stack Frame)
栈帧分为三部分:局部变量区(Local Variables)、操作数栈(Operand Stack)和帧数据区(Frame Data)。
- 局部变量区(Loca Variables):局部变量区被组织一个一个从0开始的字数组,byte、short、char在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true,long和double占据两个字长。
- 操作数栈(Operand Stack):操作数栈也被组织为一个字数组,但不同于局部变量区,它不是通过数组下标访问的,而是能过栈的Push和Pop操作,前一个操作Push进的数据可以被下一个操作Pop出来使用。
帧数据区(Frame Data)这部分的作用主要有三部分:
- 常量池中数据的解析
- 方法执行完后处理方法返回,恢复调用方现场
- 方法执行过程中抛出异常时的异常处理,存储有一个异常表,当出现异常时虚拟机查找相应的异常表看是否有对应的Catch语句,如果没有就抛出异常终止这个方法调用
3、本地方法栈(Native Method Stack)
与虚拟机栈类似,只是是执行本地方法时使用的
- (本地方法栈则为 JVM 使用到的 Native 方法服务。)
4、方法区(Method Area)
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码等信息。
方法区是线程间共享的,当两个线程同时需要加载一个类型时,只有一个类会请求ClassLoader加载,另一个线程会等待。
小结:
静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中
拓展内容 - 常量池分类
常量池大体可以分为:静态常量池,运行时常量池。
静态常量池 存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面
运行时常量池:就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 指的是运行时常量池。所以呢,讨论的都是运行时常量池
5、堆(Heap)
虚拟机中用于存放对象与数组实例的地方,垃圾回收的主要区域就是这里(还可能有方法区)。
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。
新生代又可能分为Eden区,From Survivor区和To Survivor区,主要是为了垃圾回收。所有的线程共享Java堆
,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。
堆的大小可以通过参数 –Xms、-Xmx 来指定。
Java堆只要求逻辑上是连续的,在物理空间上可以不连续。
什么是新生代 ?
新生代(Young Generation)主要是用来存放新生的对象。
新生代又被进一步划分为Eden区(伊甸园区)和Survivor区(幸存区,包含空间相等的S0、S1区,或者说From、To区,没有先后顺序,是Copying算法的需要)。
大多数情况下,java中新建的对象都是在新生代上分配的,通过Copying算法来进行分配内存和垃圾回收。
什么是老生代 ?
老生代(Old Generation)主要存放应用程序中生命周期长的内存对象。
在新生代中经过多次垃圾回收仍然存活的对象,会被存放到老生代中。老生代通过标记/整理算法来清理无用内存。
The best investment is to invest in yourself