我们通常所说的Java虚拟机(JVM)的内存布局,一般是指Java虚拟机的运行时数据区(RuntimeDataArea),也就是当字节码被类加载器加载之后的执行区域划分。当然它通常是JVM模块的第一个面试问题,所以,接下来我们一起来看它里面包含了哪些内容。官方定义 《Java虚拟机规范》中将JVM运行时数据区域划分为以下5部分:程序计数器(ProgramCounterRegister)Java虚拟机栈(JavaVirtualMachineStacks)本地方法栈(NativeMethodStack)Java堆(JavaHeap)方法区(MethedArea) 如下图所示: 接下来,我们分别来看每个模块的作用及详细介绍。1。程序计数器 《Java虚拟机规范》中对程序计数器的定义如下: TheJavaVirtualMachinecansupportmanythreadsofexecutionatonce(JLS17)。EachJavaVirtualMachinethreadhasitsownpc(programcounter)register。Atanypoint,eachJavaVirtualMachinethreadisexecutingthecodeofasinglemethod,namelythecurrentmethod(2。6)forthatthread。Ifthatmethodisnotnative,thepcregistercontainstheaddressoftheJavaVirtualMachineinstructioncurrentlybeingexecuted。Ifthemethodcurrentlybeingexecutedbythethreadisnative,thevalueoftheJavaVirtualMachinespcregisterisundefined。TheJavaVirtualMachinespcregisteriswideenoughtoholdareturnAddressoranativepointeronthespecificplatform。 以上内容翻译成中文,简单来说它的含义是:JVM中可以有多个执行线程,每个线程都有自己的程序计数器,在程序计数器中包含的是正在执行线程的指令地址。 也就是说,程序计数器(ProgramCounterRegister)是线程独有一块很小的内存区域,保存当前线程所执行字节码的位置,包括正在执行的指令、跳转、分支、循环、异常处理等。1。1作用 我们知道,CPU核数是比较少的,而任务(线程)是比较多的,所以真实的情况是,CPU会不停的切换线程以执行所有的程序,当然因为(CPU)切换的速度比较快,所以我们是感知不到的,我们感觉好像所有的程序都是一直在执行,其实从微观的层面来看,所有的程序都是切换执行的。 那么问题来了,CPU一直在切换线程执行任务,那CPU再次切换到某个线程时,它是怎么知道当前的线程上次知道到哪了? 这就是程序计数器的作用,程序计数器里面保存了当前线程执行的行号,这样当CPU切换到当前线程时,才能接着上次执行的位置,继续执行。 PS:程序计数器中真实记录的是下一行任务的执行指令。程序计数器也是JVM运行时数据区中执行最快的一块区域。1。2线程共享 程序计数器记录的是每个线程的执行行号,所以每个线程都拥有自己的程序计数器,所以此区域不是线程共享的,而是线程私有的。1。3GC GC是GarbageCollection的缩写,译为垃圾收集。 此区域不存在GC。1。4OOM OOM是OutofMemory的缩写,译为内存溢出。 此区域不存在OOM的问题。2。Java虚拟机栈 Java虚拟机栈(JavaVirtualMachineStack)也叫做JVM栈,《Java虚拟机规范》对此区域的说明如下: EachJavaVirtualMachinethreadhasaprivateJavaVirtualMachinestack,createdatthesametimeasthethread。AJavaVirtualMachinestackstoresframes(2。6)。AJavaVirtualMachinestackisanalogoustothestackofaconventionallanguagesuchasC:itholdslocalvariablesandpartialresults,andplaysapartinmethodinvocationandreturn。BecausetheJavaVirtualMachinestackisnevermanipulateddirectlyexcepttopushandpopframes,framesmaybeheapallocated。ThememoryforaJavaVirtualMachinestackdoesnotneedtobecontiguous。 IntheFirstEditionofTheJavaVirtualMachineSpecification,theJavaVirtualMachinestackwasknownastheJavastack。 ThisspecificationpermitsJavaVirtualMachinestackseithertobeofafixedsizeortodynamicallyexpandandcontractasrequiredbythecomputation。IftheJavaVirtualMachinestacksareofafixedsize,thesizeofeachJavaVirtualMachinestackmaybechosenindependentlywhenthatstackiscreated。 AJavaVirtualMachineimplementationmayprovidetheprogrammerortheusercontrolovertheinitialsizeofJavaVirtualMachinestacks,aswellas,inthecaseofdynamicallyexpandingorcontractingJavaVirtualMachinestacks,controloverthemaximumandminimumsizes。 ThefollowingexceptionalconditionsareassociatedwithJavaVirtualMachinestacks:IfthecomputationinathreadrequiresalargerJavaVirtualMachinestackthanispermitted,theJavaVirtualMachinethrowsaStackOverflowError。IfJavaVirtualMachinestackscanbedynamicallyexpanded,andexpansionisattemptedbutinsufficientmemorycanbemadeavailabletoeffecttheexpansion,orifinsufficientmemorycanbemadeavailabletocreatetheinitialJavaVirtualMachinestackforanewthread,theJavaVirtualMachinethrowsanOutOfMemoryError。 以上内容翻译成中文的含义如下: Java虚拟机栈是线程私有的区域,它随着线程的创建而创建。它里面保存的是局部变量表(基础数据类型和对象引用地址)和计算过程中的中间结果。Java虚拟机的内存不需要连续,它只有两个操作:入栈和出栈。 Java虚拟机栈要么大小固定,要么根据计算动态的扩展和收缩。程序员可以对Java虚拟机栈进行初始值的大小设置和最大值的设置。 Java虚拟机栈出现的异常有两种:当Java虚拟机栈大小固定时,如果程序中的栈分配超过了最大虚拟机栈就会出现StackOverflowError异常。如果Java虚拟机栈是动态扩展的,那么当内存不足时,就会引发OutOfMemoryError的异常。2。1作用 Java虚拟机栈主要是管Java程序运行的,它保存的是方法的局部变量、方法执行中的部分结果,并参与方法的调用和返回。 简单来说,栈是运行时单位,而堆是存储单位。也就是说:栈解决的是程序运行的问题,即程序如何执行?或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放?放在哪儿。2。2线程共享 Java虚拟机栈是线程私有的,它的生命周期和线程的生命周期一致。2。3GC Java虚拟机栈因为只有入栈和出栈两个操作,所以它是不涉及垃圾回收的。2。4OOM 此区域虽然没有GC,但存在两种异常:当Java虚拟机栈大小固定时,如果程序中的栈分配超过了最大虚拟机栈就会出现StackOverflowError异常。如果Java虚拟机栈是动态扩展的,那么当内存不足时,就会引发OutOfMemoryError的异常。 也就是,Java虚拟机栈是可能存在OOM的。2。5常见参数设置 设置Java虚拟机栈大小:Xss。 如设置:Xss128k,表示设置每个线程的栈大小为128k,此设置等价于XX:ThreadStackSize128k。2。6常见问题演示 最简单的错误示例就是死循环,方法自己调自己,这样Java虚拟机栈就会只入栈不出栈,当到达Java虚拟机栈的最大数之后就会出现StackOverflowError异常,如下图所示: 3。本地方法栈 本地方法栈(NativeMethodStacks),《Java虚拟机规范》对此区域的说明如下: AnimplementationoftheJavaVirtualMachinemayuseconventionalstacks,colloquiallycalledCstacks,tosupportnativemethods(methodswritteninalanguageotherthantheJavaprogramminglanguage)。NativemethodstacksmayalsobeusedbytheimplementationofaninterpreterfortheJavaVirtualMachinesinstructionsetinalanguagesuchasC。JavaVirtualMachineimplementationsthatcannotloadnativemethodsandthatdonotthemselvesrelyonconventionalstacksneednotsupplynativemethodstacks。Ifsupplied,nativemethodstacksaretypicallyallocatedperthreadwheneachthreadiscreated。 Thisspecificationpermitsnativemethodstackseithertobeofafixedsizeortodynamicallyexpandandcontractasrequiredbythecomputation。Ifthenativemethodstacksareofafixedsize,thesizeofeachnativemethodstackmaybechosenindependentlywhenthatstackiscreated。 AJavaVirtualMachineimplementationmayprovidetheprogrammerortheusercontrolovertheinitialsizeofthenativemethodstacks,aswellas,inthecaseofvaryingsizenativemethodstacks,controloverthemaximumandminimummethodstacksizes。 Thefollowingexceptionalconditionsareassociatedwithnativemethodstacks:Ifthecomputationinathreadrequiresalargernativemethodstackthanispermitted,theJavaVirtualMachinethrowsaStackOverflowError。Ifnativemethodstackscanbedynamicallyexpandedandnativemethodstackexpansionisattemptedbutinsufficientmemorycanbemadeavailable,orifinsufficientmemorycanbemadeavailabletocreatetheinitialnativemethodstackforanewthread,theJavaVirtualMachinethrowsanOutOfMemoryError。 以上内容,挑重点简单翻译一下:本地方法栈俗称C栈,它是Native(本地)方法(用Java编程语言以外的语言编写的方法),此区域和Java虚拟机栈类似,这不过诸如C语言等使用的栈空间。它也是存在两种异常:StackOverflowError和OutOfMemoryError。 PS:因为此区域是非Java语言实现和使用的,所以本文就不做过多的赘述,总之,记得一句话:此区域和Java虚拟机栈类似,不过是给CC语言使用的。4。堆 堆(Heap)《Java虚拟机规范》对此区域的说明如下: TheJavaVirtualMachinehasaheapthatissharedamongallJavaVirtualMachinethreads。Theheapistheruntimedataareafromwhichmemoryforallclassinstancesandarraysisallocated。 Theheapiscreatedonvirtualmachinestartup。Heapstorageforobjectsisreclaimedbyanautomaticstoragemanagementsystem(knownasagarbagecollector);objectsareneverexplicitlydeallocated。TheJavaVirtualMachineassumesnoparticulartypeofautomaticstoragemanagementsystem,andthestoragemanagementtechniquemaybechosenaccordingtotheimplementorssystemrequirements。Theheapmaybeofafixedsizeormaybeexpandedasrequiredbythecomputationandmaybecontractedifalargerheapbecomesunnecessary。Thememoryfortheheapdoesnotneedtobecontiguous。 AJavaVirtualMachineimplementationmayprovidetheprogrammerortheusercontrolovertheinitialsizeoftheheap,aswellas,iftheheapcanbedynamicallyexpandedorcontracted,controloverthemaximumandminimumheapsize。 Thefollowingexceptionalconditionisassociatedwiththeheap:Ifacomputationrequiresmoreheapthancanbemadeavailablebytheautomaticstoragemanagementsystem,theJavaVirtualMachinethrowsanOutOfMemoryError。 以上内容,挑重点简单翻译一下: 堆是线程共享的,程序中所有类实例和数组的内存都存储在此区域,它在Java虚拟机启动时就会创建。对象不会被显式释放,只会在垃圾收集时释放。堆的大小可以是固定的,也可以动态扩展或收缩。堆的内存在物理层面不需要是连续的。 程序员可以对堆进行初始大小控制,或者设置最大、最小堆的容量。 堆可能会出现OutOfMemoryError异常。4。1作用 堆是Java虚拟机的主要存储单位,Java中所有的对象和数组都是保存在此区域的。4。2线程共享 堆是线程共享的,堆上的对象可能被多个线程同时访问。4。3GC 堆是JVM最大的一块区域,也是垃圾回收器进行垃圾回收最频繁的一块区域。4。4OOM 当堆空间不足时,会发生OutOfMemoryError异常。4。5常见参数设置Xms:设置初始Java堆大小,比如:Xms10m,表示设置堆的初始大小为10MB。Xmx:设置最大Java堆大小,比如:Xmx10m,表示设置堆的最大空间为10MB。4。6常见问题演示 接下来,我们来演示一下堆空间OOM的问题,我们先使用Xmx50m的参数来设置一下Idea,它表示将程序运行的最大内存设置为50m,如果程序的运行超过这个值就会出现内存溢出的问题,设置方法如下: 设置后的最终效果这样的: PS:因为我使用的Idea是社区版,所以可能和你的界面不一样,你只需要点击EditConfigurations。。。找到VMoptions选项,设置上Xmx50m参数就可以了。 配置完Idea之后,接下来我们来实现一下业务代码。在代码中我们会创建一个大对象,这个对象中会有一个10m大的数组,然后我们将这个大对象存储在ThreadLocal中,再使用线程池执行大于5次添加任务,因为设置了最大运行内存是50m,所以理想的情况是执行5次添加操作之后,就会出现内存溢出的问题,实现代码如下:importjava。util。concurrent。LinkedBlockingQimportjava。util。concurrent。ThreadPoolEimportjava。util。concurrent。TimeUpublicclassThreadLocalOOMExample{定义一个10m大的类staticclassMyTask{创建一个10m的数组(单位转换是1M1024KB10241024B)privatebyte〔〕bytesnewbyte〔1010241024〕;}定义ThreadLocalprivatestaticThreadLocalMyTasktaskThreadLocalnewThreadLocal();主测试代码publicstaticvoidmain(String〔〕args)throwsInterruptedException{创建线程池ThreadPoolExecutorthreadPoolExecutornewThreadPoolExecutor(5,5,60,TimeUnit。SECONDS,newLinkedBlockingQueue(100));执行10次调用for(inti0;i10;i){执行任务executeTask(threadPoolExecutor);Thread。sleep(1000);}}线程池执行任务paramthreadPoolExecutor线程池privatestaticvoidexecuteTask(ThreadPoolExecutorthreadPoolExecutor){执行任务threadPoolExecutor。execute(newRunnable(){Overridepublicvoidrun(){System。out。println(创建对象);创建对象(10M)MyTaskmyTasknewMyTask();存储ThreadLocaltaskThreadLocal。set(myTask);将对象设置为null,表示此对象不在使用了myT}});}} 以上程序的执行结果如下: 从上述图片可看出,当程序执行到第5次添加对象时就出现内存溢出的问题了,这是因为设置了最大的运行内存是50m,每次循环会占用10m的内存,加上程序启动会占用一定的内存,因此在执行到第5次添加任务时,就会出现内存溢出的问题。5。方法区 方法区(MethodArea)《Java虚拟机规范》对此区域的说明如下: TheJavaVirtualMachinehasamethodareathatissharedamongallJavaVirtualMachinethreads。Themethodareaisanalogoustothestorageareaforcompiledcodeofaconventionallanguageoranalogoustothetextsegmentinanoperatingsystemprocess。Itstoresperclassstructuressuchastheruntimeconstantpool,fieldandmethoddata,andthecodeformethodsandconstructors,includingthespecialmethods(2。9)usedinclassandinstanceinitializationandinterfaceinitialization。 Themethodareaiscreatedonvirtualmachinestartup。Althoughthemethodareaislogicallypartoftheheap,simpleimplementationsmaychoosenottoeithergarbagecollectorcompactit。Thisspecificationdoesnotmandatethelocationofthemethodareaorthepoliciesusedtomanagecompiledcode。Themethodareamaybeofafixedsizeormaybeexpandedasrequiredbythecomputationandmaybecontractedifalargermethodareabecomesunnecessary。Thememoryforthemethodareadoesnotneedtobecontiguous。 AJavaVirtualMachineimplementationmayprovidetheprogrammerortheusercontrolovertheinitialsizeofthemethodarea,aswellas,inthecaseofavaryingsizemethodarea,controloverthemaximumandminimummethodareasize。 Thefollowingexceptionalconditionisassociatedwiththemethodarea:Ifmemoryinthemethodareacannotbemadeavailabletosatisfyanallocationrequest,theJavaVirtualMachinethrowsanOutOfMemoryError。 以上内容,挑重点简单翻译一下: 方法区是线程共享的,方法区类似于传统语言的编译代码的存储区,或者类似于操作系统进程中的文本段。它存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码。 方法区域是在Java虚拟机启动时创建的,尽管方法区域在逻辑上是堆的一部分,但简单的实现可能选择不进行垃圾收集或压缩。方法区域可以是固定的大小,也可以动态扩展。方法区的(物理)内存不需要连续。 Java虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在可变大小的方法区域的情况下,对最大和最小方法区域大小的控制。 如果方法区中的内存无法满足分配请求,Java虚拟机将抛出一个OutOfMemoryError。5。1作用 用于存储每个类的结构,包括运行时常量池、静态变量、字段和方法数据。5。2HotSpot方法区实现 HotSpot虚拟机是SunJDK和OpenJDK中自带的虚拟机,也是目前使用范围最广的Java虚拟机。作为官方Java虚拟机的化身,目前所讲的所有知识,几乎都是针对此虚拟机的,所以我们要看HotSpot虚拟机对《Java虚拟机规范》中方法区的实现。 对于HotSpot虚拟机来说,不同的JDK方法区的实现是不同的,在JDK1。7之前,HotSpot技术团队使用的是永久代来实现方法区的,但这种实现有一个致命的问题,这样设计更容易造成内存溢出。因为永久代有XX:MaxPermSize(方法区分配的最大内存)的上限,即使不设置也会有默认的大小。例如,32位操作系统中的4GB内存限制等,并且这样设计导致了部分的方法在不同类型的Java虚拟机下的表现也不同,比如String::intern()方法。所以在JDK1。7时HotSpot虚拟机已经把原本放在永久代的字符串常量池和静态变量等移出了方法区,并且在JDK1。8中完全废弃了永久代的概念。 JDK1。8之后,HotSpot虚拟机开始使用元空间(MetaSpace)来实现方法区了。5。3线程共享 方法区是线程共享的。5。4GC 《Java虚拟机规范》中规定方法区可以没有GC(垃圾回收),但HotSpot中对此区域实现了GC操作。5。5OOM 方法区是存在OOM情况的,比如在JDK1。8中,如果元空间设置空间过小,而类信息产生的过多就会产生OOM,如下示例所示:importnet。sf。cglib。proxy。Eimportnet。sf。cglib。proxy。MethodIimportnet。sf。cglib。proxy。MethodPimportjava。lang。reflect。M方法区OOM演示(JDK1。8)设置XX:MaxMetaspaceSize10m元空间最大内存为10MB。publicclassMethodAreaOOMExample{publicstaticvoidmain(String〔〕args){while(true){EnhancerenhancernewEnhancer();enhancer。setSuperclass(MethodAreaOOMExample。class);enhancer。setUseCache(false);enhancer。setCallback(newMethodInterceptor(){OverridepublicObjectintercept(Objectobject,Methodmethod,Object〔〕args,MethodProxymethodProxy)throwsThrowable{returnmethodProxy。invokeSuper(object,args);}});enhancer。create();}}} 以上程序的执行结果如下图所示: 以上代码是通过CGLIB不断的产生动态代理类将方法区填满,从而就导致OOM的问题。 PS:在使用CGLIB之前,需要现在当前项目中导入CGLIB才可以正常使用。5。6常用参数设置 永久代(HotSpot虚拟机,JDK1。7之前设置有效):XX:PermSize100m:设置永久代初始值为100MB。XX:MaxPermSize100m:设置永久代最大值为100MB。 元空间(HotSpot虚拟机,JDK1。8之后设置有效):XX:MetaspaceSize100m:设置元空间初始大小为100MB。XX:MaxMetaspaceSize100m:设置元空间最大容量为100MB,默认不设置,则没有限制。XX:CompressedClassSpaceSize100m:设置ClassMetaspace的大小为100MB,默认值为1G。 直接内存(HotSpot虚拟机,JDK1。8之后设置有效): XX:MaxDirectMemorySize100m:指定直接内存的最大容量为100MB。总结 《Java虚拟机规范》中将JVM运行时数据区域划分为以下5部分:程序计数器(ProgramCounterRegister);Java虚拟机栈(JavaVirtualMachineStacks);本地方法栈(NativeMethodStack);Java堆(JavaHeap);方法区(MethedArea)。 其中线程私有的区域是:程序计数器、Java虚拟机栈、本地方法栈;而线程共享的是:Java堆和方法区。 而除了程序计数器,其他区域都是可以会出现OOM的。参考鸣谢 docs。oracle。comjavasespecsjvmsse8htmljvms2。htmljvms2。5。1 zhuanlan。zhihu。comp518151056