Java内存区和内存溢出异常运行时数据区程序计数器用来记录从内存中执行的下一条指令的地址,一个线程私有的一小块内存,也是唯一不会报OOM异常的区域。Java虚拟机栈(JavaVirtualMachineStack)是线程私有的,其生命周期与线程相同。虚拟机栈描述了Java方法执行的线程内存模型:每个方法执行时,Java虚拟机都会同步创建一个栈帧(StackFrame)来存放局部变量表、操作数栈、动态连接、方法导出信息.每个方法被调用到执行完成的过程对应于虚拟机栈中一个栈帧从入栈到出栈的过程。如果线程请求的栈深度大于虚拟机允许的深度,如果Java虚拟机栈的容量可以动态扩展,则会抛出StackOverflowError异常。当堆栈扩大,无法申请到足够的内存时,会抛出OutOfMemoryError异常。本地方法栈类似于Java虚拟机栈,只是服务对象不同。本地方法栈由虚拟机使用。本地方法服务,Java虚拟机栈为虚拟机执行Java方法(字节码)服务Java堆对于Java应用,Java堆(JavaHeap)是虚拟机管理的最大一块内存。Java堆是虚拟机启动时创建的所有线程共享的内存区域。该内存区域的唯一目的是存储对象实例。Java世界中“几乎”所有的对象实例都在这里分配内存。当堆内存没有足够的空间为对象实例分配内存,无法扩展堆内存时,会抛出OOM异常。方法区方法区类似于Java堆,也是各个线程共享的区域。用于存放已经被虚拟机加载过的类型信息、常量、静态变量、实时编译器编译后的代码缓存等数据。通常,使用别名“非堆”。与Java堆区分开来。当方法区没有足够的空间满足内存分配要求时,也会抛出OOM异常。运行时常量池是方法区的一部分,用于存放编译过程中产生的各种字面量和符号。引用受方法区内存的限制。当常量池不能再申请内存时,就会抛出OOM异常。直接内存直接内存不属于运行时数据区,但受总内存限制,也可能出现OOM异常HotSpot虚拟机ObjectQuest对象创建类加载检查通过后,虚拟机会为其分配内存新对象,内存分配主要有两种方式:指针碰撞自由列表对象的内存布局在HotSpot虚拟机中,对象在堆内存中的存储布局可以分为三部分:对象头(Header)、实例数据(InstanceData)和对齐填充(Padding)。GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等类型指针(指向其类型元数据的指针)有效存储在实例数据对象中,即代码中的各类型对齐paddingoffieldcontent由于HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,即任何对象的大小都是8字节的整数倍,所以如果实例数据部分没有对齐,需要对齐填充作为占位符完成对象的访问定位,Java程序会通过引用(对对象的引用)数据对堆上的具体对象进行操作栈,具体的访问方式由虚拟机实现。主流的接入方式主要有两种:Handledirectpointer实战OOM异常不同的JDK和garbagecollection收集器可能会产生不同的结果。下面的实战都是以JDK8和ParallelGC垃圾收集器为例运行代码#查看DefaultGarbageCollectionVM参数-XX:+PrintCommandLineFlags-versionJavaheapoverflow只要不断创建对象实例,避免垃圾收集器在同时,达到最大堆容量限制后会产生OOM异常publicclassHello{/***-Xms:最小堆内存20M-Xmx:最大堆内存20M两个设置相同,避免自动扩容*虚拟机参数:-Xms20M-Xmx20M-XX:+HeapDumpOnOutOfMemoryError*/publicstaticvoidmain(String[]args){Listhellos=newArrayList<>();while(true){hellos.add(newHello());}}}Java虚拟机栈和native方法栈溢出《Java虚拟机规范》明确让Java虚拟机选择是否支持动态扩展栈,而HotSpot虚拟机的选择不支持扩展,所以除非出现OutOfMemoryError创建线程申请内存时内存不足会出现异常,否则线程运行时不会因为扩容导致内存溢出,只会因为栈容量无法容纳新的栈帧,导致StackOverflowError异常使用-Xss参数减少栈容量publicclassHello{/***VM参数:-Xss128k*/privateintstackLength=1;publicvoidstackLeak(){stackLength++;//递归调用方法并继续推送stackLeak();}publicstaticvoidmain(String[]args)throwsThrowable{你好oom=new你好();try{//调用方法,推送oom.stackLeak();}catch(Throwablee){System.out.println("堆栈长度:"+oom.stackLength);扔e;}}}定义大量局部变量,增加本方法帧中局部变量表的长度(即调整栈帧的大小)publicclassHello{privatestaticintstackLength=0;publicstaticvoidtest(){//很多局部变量,栈桥增加longunused1,unused2,unused3,unused4,unused5,unused6,unused7,unused8,unused9,unused10,unused11,unused12,unused13,unused14,unused15,unused16,unused17,unused18,unused19,unused20,unused21,unused22,unused23,unused24,unused25,unused26,unused27,unused28,unused29,unused30,unused31,unused32,unused33,unused34,unused35,unused36,unused37,unused38,unused39,unused40,unused41,未使用42、未使用43、未使用44、未使用45、未使用46、未使用47、unused48,unused49,unused50,unused51,unused52,unused53,unused54,unused55,unused56,unused57,unused58,unused59,unused60,unused61,unused62,unused63,unused64,unused65,unused66,unused67,unused68,unused69,unused70,unused71,unused72,未使用73,未使用74,未使用75,未使用76,未使用77,未使用78,未使用79,未使用79,未使用80,未使用81,未使用82,未使用82,未使用83,未使用84,未使用85,未使用86,UNDUSE86,UNDUSE87,UNDUSE87,UNDUSE88,UNDUSE88,UNDUSEN89,UNDUSEN89,UNDUSES90,UNDUSESUNDUSESUNDUSES,未使用UNSUSE,UNSUSE,UNSUSEUNSUSE,UNUNSUS未使用98、未使用99、未使用100;堆栈长度++;//递归调用,不中断测试();unused1=unused2=unused3=unused4=unused5=unused6=unused7=unused8=unused9=unused10=unused11=unused12=unused13=unused14=unused15=unused16=unused17=unused18=unused19=unused20=unused21=unused22=unused23=unused24=unused25=unused26=unused27=unused28=unused29=unused30=unused31=unused32=unused33=unused34=unused35=unused36=unused37=unused38=unused39=unused40=unused41=unused42=unused43=unused44=unused45=unused46=unused47=unused48=unused49=unused50=unused51=unused52=unused53=unused54=unused55=unused56=unused57=unused58=unused59=unused60=unused61=unused62=unused63=unused64=unused65=unused66=unused67=unused68=unused69=unused70=unused71=unused72=unused73=unused74=unused75=unused76=unused77=unused78=unused79=unused80=unused81=unused82=unused83=unused84=unused85=unused86=unused87=unused88=unused89=unused90=unused91=unused92=unused93=unused94=unused95=unused96=unused97=unused98=unused99=unused100=0;}publicstaticvoidmain(String[]args){try{test();}catch(Errore){System.out.println("堆栈长度:"+stackLength);扔e;}}}方法区和运行时间常量池溢出方法区容量控制publicclassHello{/***JDK8之前的VM参数:-XX:PermSize=6M-XX:MaxPermSize=6M*JDK8VM参数:-XX:MetaspaceSize=6M-XX:MaxMetaspaceSize=6M*/publicstaticvoidmain(String[]args){//使用Set保留常量池引用,避免FullGC回收常量池的行为Setset=newHashSet<>();//shortrange足够6MPermSize(永久代,JDK8之前,JDK8及以后版本已经被metaspace取代)的大小产生OOMshorti=0;//JDK8之前,抛出OOM异常//JDK8下,正常情况下,会进入死循环,不会抛出任何异常while(true){//String.intern()进入字符串常量池集合.add(String.valueOf(i++).intern());}}}上面的代码在JDK8环境下不会抛出异常,因为字符串常量池已经移到了Java堆中,控制方法区的大小对Java堆没有影响。String.intern()方法介绍:如果字符串常量池中已经包含了一个等于这个String对象的字符串,则返回常量池中这个字符串的String对象;否则,将这个String对象中包含的字符复制并添加到常量池中,并返回这个String对象引用/***JDK6:falsefalse*JDK8:truefalse*/publicstaticvoidmain(String[]args){Stringstr1=newStringBuilder("Computer").append("Software").toString();System.out.println(str1.intern()==str1);Stringstr2=newStringBuilder("ja").append("va").toString();System.out.println(str2.intern()==str2);}JDK6因为newStringBuilder()分配Java堆内存,String.intern()会把第一个遇到的字符串复制到字符串常量池(方法区),所以两者都是falseJDK8,因为字符串常量池已移至Java堆。newStringBuilder()分配到Java堆内存后,字符串常量池也记录了第一次遇到的实例引用,所以String.intern()和newStringBuilder()都是一样的(true);又因为java字符串在sun的时候已经进入了常量池。创建了一个,自然就不一样用其他类填满方法区也会导致方法区溢出/**使用CGLib导致方法区溢出*VM参数:-XX:MetaspaceSize=10M-XX:MaxMetaspaceSize=10M*/publicclassHello{publicstaticvoidmain(String[]args){while(true){//创建CgLib增强对象Enhancerenhancer=newEnhancer();//设置代理类enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);//指定拦截器enhancer.setCallback(newMethodInterceptor(){@OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{returnproxy.invokeSuper(obj,args);}});//创建代理对象enhancer.create();}}staticclassOOMObject{}}nativedirectmemoryoverflow直接内存(DirectMemory)容量可以通过-XX:MaxDirectMemorySize参数指定,如果不指定,默认与Java堆最大(由-Xmx指定)//使用不安全分配本机内存publicclassHello{//VM参数:-Xmx20M-XX:MaxDirectMemorySize=10Mprivatestaticfinalint_1MB=1024*1024;publicstaticvoidmain(String[]args)throwsException{FieldunsafeField=Unsafe.class.getDeclaredFields()[0];不安全的领域。设置可访问性(真);不安全不安全=(不安全)不安全字段。得到(空);while(true){//真正申请内存分配unsafe.allocateMemory(_1MB);}}}参考文献《深入理解Java虚拟机》(第三版)第二章:Java内存区与内存溢出异常