理解JVM内存结构的目的在Java的开发过程中,由于JVM自动内存管理机制,不再需要像in这样手动释放对象的内存空间C和C++开发。容易出现内存泄漏和内存溢出问题。但是,正是因为内存管理权交给了JVM。一旦出现内存泄漏和内存溢出问题,就很难理解JVM是如何使用内存的,JVM的内存结构是什么样子的。一旦找到问题的根源,修复它就变得更加困难。JVM内存结构介绍在JVM管理的内存中,大致分为以下几个运行时数据区:程序计数器:当前线程执行的字节码的行号指示符。虚拟机栈:Java方法执行的内存模型,用来存放局部变量表、操作数栈、动态链接、方法出口等信息。本地方法栈:本地方法执行的内存模型和虚拟机栈非常相似,不同的是本地方法栈是为JVM使用的Native方法服务的。堆:用于存放对象实例,是垃圾收集器管理的主要区域。方法区:用于存放JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。其中,程序计数器、虚拟机栈、本地方法栈在黄色区域是线程私有的,红色区域的堆和方法区是线程共享的。下面我们详细分析每个区域。程序计数器程序计数器(ProgramCounterRegister)是一块很小的内存空间,记录了当前线程执行的字节码的行号。在JVM的概念模型中,字节码解释器通过改变它的值来选择下一条要执行的字节码指令来工作。分支、循环、跳转、异常处理、线程恢复等基本功能都依赖Itcomesdone。通过轮流切换线程,分配处理器执行时间,实现了JVM的多线程运行。在任何给定时刻,一个处理器(在多核处理器的情况下是一个内核)仅在一个线程中执行指令。因此,为了在线程切换后回到正确的执行位置,每个线程都需要有一个独立的程序计数器。线程间的计数器互不影响,独立存储。这种类型的内存区域称为“线程私有”。的记忆。如果线程正在执行Java方法,则记录正在执行的虚拟机字节码指令的地址;如果它正在执行Natvie方法,则其值为空(未定义)。此内存区域是唯一未在Java虚拟机规范中指定任何OutOfMemoryError条件的内存区域。虚拟机堆栈与程序计数器相同。Java虚拟机栈(JavaVirtualMachineStacks)也是线程私有的。如上图所示,每个线程都有自己的虚拟机栈。它的生命周期与线程相同。线程创建的时候,虚拟机栈也同时创建;当线程被销毁时,虚拟机栈也同时被销毁。在线程内部,每个方法执行时,都会同时创建一个栈帧(StackFrame),用于存放局部变量表、操作数栈、动态链接、方法出口等信息,如如上图所示。每个方法被调用到执行完成的过程对应着虚拟机栈中一个栈帧从入栈到出栈的过程。其中,栈帧中的局部变量表存储了各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(referencetype)和returnAddress类型(指向对象的地址)字节码指令)。其中64位长度的long和double类型数据会占用2个局部变量空间(Slots),其余数据类型只会占用1个slot。局部变量表所需的内存空间是在编译时分配的。当进入一个方法时,该方法需要在帧中分配多少局部变量空间是完全确定的,在方法运行过程中不会改变局部变量表的大小。在Java虚拟机规范中,针对这块区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常。让我们写一段代码让它抛出这个异常:/***VMArgs:-Xss128k*/publicclassJVMStackSOF{privateintstackLength=1;publicvoidstackLeak(){stackLength++;堆栈泄漏();}publicstaticvoidmain(String[]args){JVMStackSOFsof=newJVMStackSOF();尝试{sof.stackLeak();}catch(Throwablee){System.out.println("堆栈长度:"+sof.stackLength);扔e;}}}运行前设置JVM的参数为-Xss128k,运行结果如下:Stacklength:1002Exceptioninthread"main"java.lang.StackOverflowErroratOneMoreStudy.JVMStackSOF.stackLeak(JVMStackSOF.java:10)在OneMoreStudy.JVMStackSOF.stackLeak(JVMStackSOF.java:11)在OneMoreStudy。JVMStackSOF.stackLeak(JVMStackSOF.java:11)……当栈深度达到1002时,抛出StackOverflowError异常。如果虚拟机栈可以动态扩容,扩容时无法申请到足够的内存就会抛出OutOfMemoryError异常,或者让我们写一段代码让它抛出这个异常:/***VMArgs:-Xss2M*/publicclassJVMStackOOM{privatevoiddontStop(){while(true){}}publicvoidstackLeakByThread(){while(true){Threadt=newThread(newRunnable(){publicvoidrun(){dontStop();}});t.开始();}}publicstaticvoidmain(String[]args){JVMStackOOMoom=newJVMStackOOM();oom.stackLeakByThread();}}这段代码会创建无限多个线程,因为Java线程会映射到系统的内核线程,所以会造成CPU占用100%,系统假死等现象,请谨慎运行。运行前,将JVM参数设置为-Xss2M。运行了很久,结果如下:Exceptioninthread"main"java.lang.OutMemoryError:unabletocreatenewnativethreadatjava.lang.Thread.start0(NativeMethod)atjava.lang.Thread。start(UnknownSource)atOneMoreStudy.JVMStackOOM.stackLeakByThread(JVMStackOOM.java:18)atOneMoreStudy.JVMStackOOM.main(JVMStackOOM.java:24)NativeMethodStacks虚拟机栈起到的作用非常相似。不同的是,虚拟机栈服务于虚拟机执行的Java方法(即字节码),而本地方法栈服务于虚拟机使用的Native方法。.本地方法栈中方法的语言、用法、数据结构在虚拟机规范中没有强制要求,具体虚拟机可以自由实现。甚至有些虚拟机(如SunHotSpot虚拟机)直接将本地方法栈和虚拟机栈合二为一。和虚拟机栈一样,native方法栈区也会抛出StackOverflowError和OutOfMemoryError异常。堆Java堆(JavaHeap)是Java虚拟机管理的最大的一块内存。它是所有线程共享的内存区域,在虚拟机启动时创建。它用于存放对象实例,几乎所有的对象实例都在这里分配内存。堆是垃圾收集器管理的主要区域。从内存回收的角度来看,由于当前收集器基本采用分代收集算法,因此Java堆也可以细分为:新生代和老年代;有Eden空间,FromSurvivor空间,ToSurvivor空间等。从内存分配的角度来看,线程共享堆中可能会划分出多个线程私有分配缓冲区(ThreadLocalAllocationBuffer,TLAB)。根据Java虚拟机规范,Java堆可以在一个物理上不连续的内存空间,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现的时候,可以实现为固定大小,也可以是可伸缩的,但是目前主流的虚拟机都是按照可伸缩性来实现的(由-Xmx和-Xms控制)。如果堆中没有内存来完成实例分配,堆不能再扩展,就会抛出OutOfMemoryError异常。让我们写一段代码让它抛出这个异常:/**VMArgs:-Xms20M-Xmx20M*/publicclassHeapOOM{staticclassOOMObject{}publicstaticvoidmain(String[]args){List
