Java 堆
方法区
Java 栈
局部变量表
操作数栈
动态连接
返回地址
本地方法栈
程序计数器
Class文件会通过加载、链接、初始化的过程加载到内存,JVM的内存模型是什么样子的,加下来我们一起看看。
Java虚拟机在执行Java程序的过程中会把管理的内存划分成若干不同的数据区域。
JVM内存模型主要分为五块:Java 堆内存(Heap)、方法区(Method Area)、JVM栈(JVM Stack)、本地方法栈(Native Method Stacks)、程序计数器(Program Counter Register)。
JVM内存模型实际上的意思是java运行时数据区域,它整个过程就是当程序要执行某一段代码时,类加载器加载我们的class字节码文件,把读取的信息翻译成类信息存放到我们的方法区,同时在堆中生成该类的Class对象,当我们程序运行调用方法时,局部变量、对象引用、数组引用会在虚拟机栈中生成,如果在方法调用中需要new对象,则会在堆中根据Class信息生成对象,最后由我们的字节码执行引擎解释执行方法区的代码块,就完成了我们的代码执行。当然这个过程中,涉及到线程的切换,这时程序计数器就派上用场了,它会记录当前线程执行到哪一行代码,下次该线程再次获取cpu执行权时,继续从该行代码继续执行,最后本地方法栈和虚拟机栈的功能几乎一模一样,只不过它是执行的native本地方法,底层是调用的C或C++代码。
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。
堆由老年代和新生代组成,新建的对象一般都是在存放在Eden区,当Eden区不够用时,就会就行Minior GC,回收垃圾对象释放内存空间,当垃圾回收后仍然存活的对象就会放到S0和S1其中一块区域,当再次发生Minior GC时,会把Eden区和有对象的S区中的存活对象移动到另外一块空的S区,这时先前有对象的S区又变成空的S区,所以S0和S1是相互转化的,当S区对象达到一定的阈值时,新生代的对象就会向老年代转移,当老年代的对象达到一定阈值时,就会触发Full GC
**新生代 = Eden(伊甸园区) + 2个survior区(幸存者区) **
a. YGC回收之后,大多数的对象会被回收(90%),活着的进入s0
b. 再次YGC,活着的对象eden + s0 -> s1
c. 再次YGC,eden + s1 -> s0
d. 年龄足够 -> 老年代 (15 CMS 6)
e. s区装不下 -> 老年代
老年代
a. 顽固分子
b. 老年代满了FGC Full GC
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1.7之前 方法区的实现是永久代
1.8之后 方法区的实现是元空间
这里有个注意点必须给大家提醒一下,那就是该区域并不会存放Class对象,Class对象是存放在堆中的,方法区存放的类信息是一些代码片段(类似于C语言中的结构体),供我们程序调用时字节码执行引擎解释执行的。
线程私有,它的生命周期与线程相同。一个线程一个栈,一个方法一个栈帧。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
局部变量表存放了编译期可知的各基本数据类型(byte、short、int、long、float、double、boolean、char)、对象引用、retrunAddress类型(指向了一条字节码指令的地址)。概况来说局部变量表用于存放方法参数和方法内部定义的局部变量。
在方法执行过程中,根据字节码指令,往栈里写入数据或提取数据,即入栈/出栈。
数据运算的地方,大多数指令都在操作数栈弹栈运算,然后结果压栈。
操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中临时的存储空间。
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)
jvm虚拟机栈中的动态链接是将符号引用转换成直接引用,因为java具有多态特性,父类引用调用方法时,如果子类重写了该方法,那么实际父类引用是会调用子类方法的,因此是需要在运行时期进行动态调用的,而不是在类加载的解析阶段能够确定的。
方法的返回分为两种情况,一种是正常退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者,一种是异常导致的方法结束,这种情况是不会传返回值给上层的调用方法。
方法的的一次调用就对应着栈帧在虚拟机栈中的一次入栈出栈操作,因此方法退出时可能做的事情包括:恢复上层方法的局部变量表以及操作数栈,如果有返回值的话,就把返回值压入到调用者栈帧的操作数栈中,还会把PC计数器的值调整为方法调用入口的下一条指令。
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。用于记录线程执行到哪一行代码。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
本文借鉴了很多网上的资料:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/promsing/article/details/126231854
内容来源于网络,如有侵权,请联系作者删除!