作者:仇顺嵐 | 来源:互联网 | 2023-09-24 12:21
Java虚拟机在运行时会将内存划分为若干区域,我们经常会说堆、栈,栈中存放的是基础类型和对象引用地址,堆中存放的是对象。那么实际上JVM运行时到底将内存划分了多少区域,这些区域都会存储什么数据以及如何进行回收?
如上图所示,JVM将内存划分为五个区域。
程序计数器:是线程隔离的一块较小的内存区域,因为java多线程是并发的,即轮轮流的切换线程并且分配处理器来执行的一种方式,所以每个线程都需要记录自己所执行的位置,所以在程序计数器内为每个线程都要保存一份独立的计数。程序计数器可以理解为程序执行到的字节码位置,一般来讲程序的执行就是改变这个计数值。如果当前执行的是Native方法,则这个计数器值为空(undefined)。该内存区域是唯一一块没有规定任何OutOfMemoryError的区域。
Java虚拟机栈:也是线程隔离的一块内存区域,与线程的生命周期相同。它描述了java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等。每一个方法的调用到执行完成,对应着栈的入栈与出栈操作。我们一开始说的存放基础类型和对象引用地址的栈就是指的虚拟机栈中的局部变量表。局部变量表里就存放了boolean、byte、int、long、float、double、对象的引用和returnAddress类型,其中long和double占用两个局部变量空间,其余都占用一个。局部变量表在编译器就已经确定大小,并分配内存空间,在运行过程中是不会改变的。returnAddress没有看太明白。这个区域有两种异常:一个是请求的栈深度超过了虚拟机所允许的深度,会报出***Error;一个是如果虚拟机可以动态扩展虚拟机栈,当无法申请更多内存时,会报OutOfMemoryError。
本地方法栈:与java虚拟机栈的区别就是,本地方法栈执行本地方法使用。
java堆:堆是被所有线程共享的一块最大的内存,所有的对象实例和数组都在堆上分配内存。此话现在不绝对,后续会说到。为了更快地进行垃圾回收,一般现在会将堆分代,新生代和老年代,又可以细化为eden区、from区、to区、老年代。
方法区:线程共享的内存,存储已经加载的类的信息、常量、静态变量、即时编译器编译后的代码等。运行时常量是方法区的一部分,就是我们所说的string所存放的地方。在之前的hotspot虚拟机上,方法区也可以叫做永久代,我们经常会遇到java.lang.OutOfMemoryError: PermGen space 这个就代表方法区溢出。因为将运行时常量、类信息存放在方法区,会经常导致内存溢出,并且永久代是hotspot独有的概念,所以从jdk1.7开始就逐渐的取消了永久代的概念。到jdk1.8已经完全取消,类信息、stirng和静态变量都将会被移到堆和本地内存中。永久代由MetaSpace所取代,保存到本地内存中,MetaSpace思想就是类和他的元数据的生命周期和他的类加载器的生命周期一致,每个类加载器的存储区都叫做一个元空间,所有的元空间组合到一起。我们再大量的创建string对象导致内存溢出,不会再是PermGen
space了,而是变成了heap space溢出,因为stirng常量池已经移到了堆中。要想使MetaSpace溢出,需要创建新的类,导致其溢出。
新的设置MetaSpace的参数有:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
以上就是JVM的基本运行时内存分配情况