JVM内存结构
程序计数寄存器(Program Counter Register)
- 每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和字节码行号指示器等,线程执行或恢复都要依赖程序计数器。程序计数器在各个线程之间互不影响,此区域也不会发生内存溢出异常。
- 在多线程中,就会存在线程上下文切换执行,为了线程切换后能恢复正确的执行位置,所以需要从程序计数器中获取该线程需要执行的字节码的偏移地址(偏移地址:执行javap命令后**数字:**中的数字)。
- 如果执行java方法则程序计数器记录的是正在执行的虚拟机字节码指令的地址。如果执行Native方法,程序计数器则为空。
- 由于是线程私有的,生命周期随着线程,线程启动而产生,线程结束而消亡。
- Java 虚拟机规范里面, 唯一 一个没有规定任何 OutOfMemoryError 情况的区域,由于保存的是线程需要执行的字节码的偏移地址,当执行下一条指令的时候,改变的只是程序计数器中保存的地址,并不需要申请新的内存来保存新的指令地址,因此,不会产生内存溢出。
Java虚拟机栈
特点
- JVM中的虚拟机栈是描述Java方法执行的内存区域,它是线程私有的。
- 线程启动时会创建虚拟机栈,每个方法在执行时会在虚拟机栈中创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法返回地址、附加信息等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈(压栈)到出栈(弹栈)的过程。
- 两种异常
1)StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求的栈深度大于虚拟机允许的最大深度时(但内存空间可能还有很多),就抛出此异常
2)OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常
3)动态栈通常有两种方法:Segmented stack和Stack copying。
(1)Segmented stack可以简单理解成一个双向链表把多个栈连接起来,一开始只分配一个栈,这个栈的空间不够时,就再分配一个,用链表一个一个连起来。
(2)Stack copying就是在栈不够的时候,分配一个更大的栈,然后把原来的栈复制过去。
栈帧(Stack Frame)
- 栈帧存在于java虚拟机栈中,是 Java 虚拟机栈中的单位元素,每个线程中调用同一个方法或者不同的方法,都会创建不同的栈帧。
2. 局部变量表:存放方法参数和局部变量。Java 虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始到局部变量表最大的 Slot(局部变量表所需的存储空间) 数量。
3. 操作数栈:方法的执行操作在操作数栈中完成,每一个字节码指令往操作数栈进行写入和提取的过程,就是入栈和出栈的过程。return返回的值就是栈中的值。
4. Java 虚拟机栈执行过程:参考博客
Java 堆
- Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时被创建。
- 分为两个部分:年轻代和老年代(1:2)。新生代 (Young) 又被划分为三个区域:Eden、From Survivor、To Survivor(8:1:1)。永久代在jdk1.6之前存在于堆中,1,7移到了方法区,1.8移除永久代,使用元空间。
- 分代原因:提高内存空间利用率和垃圾回收的效率。不同对象的生命周期不同,大部分新对象都在年轻代,生命周期一般很短,可以很高效的进行回收,不用遍历所有对象,而老年代对象生命周期一般很长,可能只回收一小部分内存。gc方面在进行详解。
- java堆分配对象
5. JIT 编译器:Java 程序最初是通过解释器来解释执行的,当虚拟器发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”,为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译为机器码,并进行各种层次的优化,完成这个任务的编译器成为即使编译器
本地方法栈
- 本地方法栈和Java虚拟机栈实现的功能与抛出异常几乎相同
- 只不过虚拟机栈是为虚拟机执行Java方法(也就是字节码)服务,本地方法区则为虚拟机使用到的Native方法服务.
- 本地方法可以通过JNI(Java Native Interface)来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和JVM相同的能力和权限
- 在项目过程中,如果大量使用其他语言来实现JNI,就会丧失跨平台特性,威胁到程序运行的稳定性。假如需要与本地代码交互,就可以用中间标准框架进行解耦,这样即使本地方法崩溃也不至于影响到JVM的稳定。
方法区
- 定义:Java虚拟机规范中定义方法区是堆的一个逻辑部分,但是别名Non-Heap(非堆),以与Java堆区分.方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据.
- 线程共享,内存回收效率低(对方法区的内存回收的主要目标是:对常量的回收和对类型的卸载)
Metaspace (元空间)
- JDK8使用元空间替换永久代.区别于永久代,元空间在本地内存中分配.只要本地内存足够,它不会出现像永久代中java.lang.OutOfMemoryError: PermGen space
- Perm 区所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间
- 充分利用了Java语言规范:类及相关的元数据的生命周期与类加载器的一致
- 每个类加载器都有它的内存区域-元空间
- 只进行线性分配
- 不会单独回收某个类(除了重定义类 RedefineClasses 或类加载失败)
- 没有GC扫描或压缩,元空间里的对象不会被转移
- 如果GC发现某个类加载器不再存活,会对整个元空间进行集体回收
来源:CSDN
作者:青春淡定
链接:https://blog.csdn.net/weixin_44301710/article/details/103457646