前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java虚拟机--运行时数据区与内存溢出

Java虚拟机--运行时数据区与内存溢出

作者头像
王小明_HIT
发布2019-08-13 11:29:41
4610
发布2019-08-13 11:29:41
举报
文章被收录于专栏:程序员奇点程序员奇点

JVM内存区域

了解java内存区域的划分,和每个区域存储的数据,可以帮助我们分析问题。

JVM内存区域分成堆 ,方法区,虚拟机栈,本地方法栈, 程序计数器

上图说明了 运行时数据区的划分,关注

方法区,堆是线程共享

虚拟机栈,程序计数器,本地方法栈是线程私有

方法区

存放的数据是JVM加载的类信息,常量,静态变量和编译器编译后的代码等,这里要注意的是JDK1.8之后已经将这个方法区删除了,使用元空间,metaspace代替了,理由有如下:

1.方法区存放的是常量,容易造成内存溢出,outofmemory:permGen space

2.编译后的代码,类和方法难确定大小,太小,容易造成永久代溢出,太大,容易造成堆溢出,使用元空间,不受JVM虚拟机内存限制,受本地内存的限制。

3.同时永久代的GC复杂,回收效率偏低。

元空间常用的配置参数

1.MetaspaceSize

初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数

2.MaxMetaspaceSize

限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

3.MinMetaspaceFreeRatio

当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。

4.MaxMetasaceFreeRatio

当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。

5.MaxMetaspaceExpansion

Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。

6.MinMetaspaceExpansion

Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。

new出来的对象就放在堆,这部分区域目的就是为了存放对象实例。也是JVM管理的内存最大的最大的一块区域。

堆又分成 新生代(YoungGeneration)和 老年代(OldGeneration),新生代还可以分成Eden, from Survivor,to Survivor

程序计数器

非常小的一个内存空间,是当前线程执行字节码的行号指示器,每个线程都有自己的程序计数器,是线程私有的,程序计数器是唯一一个不会发生内存溢出的区域。

虚拟机栈

虚拟机栈,也是线程私有的一个空间

虚拟机会为每个线程提供一个虚拟机栈,每个虚拟机栈都有若干个栈帧,每个栈帧存储了局部变量表,操作数栈,动态链接,返回地址,当线程执行一个方法时,这个方法对应的栈帧,就处于虚拟机栈,栈帧的顶部,每一个java方法,从被调用,到结束,对应了一个栈帧入栈到出栈的过程。

本地方法栈

虚拟机栈上执行的是 Java方法,本地方法栈上执行的是本地方法(Native Methmod),HotSpot虚拟机将,虚拟机栈和本地方法栈合二为一。

JVM 内存溢出

1.堆溢出

public class HeapOOM { static class OOMObject { } /** * VM args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * -XX:HeapDumpPath=D://java_pid.hprof -Xms:是初始化堆内存值 -Xmx:是堆内存最大值 * -XX:+HeapDumpOnOutofMemoryError 在堆溢出时保存快照 * -XX:HeapDumpPath=./java_pid.hprof来显示指定路径 * * MAT 工具 http://download.eclipse.org/mat/1.6/update-site/ * https://blog.csdn.net/u010335298/article/details/52233689/ * https://blog.csdn.net/liu765023051/article/details/75127361 * @param args */ public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while (true) { list.add(new OOMObject()); } } }

运行后会出现异常

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

新生代中,new 出来的对象会被放到Eden,第一次Mirror GC 之后,会被转移到 from survivor, 新生代采用的是复制算法进行MirrorGC ,当from survivor不足时,对象转移到to survivor,每进行一次MirrorGC ,年龄+1,当survivor对象年龄达到一定程度时,新生代对象,转移到老年代。

2.虚拟机栈溢出

当线程请求的栈深度大于虚拟机栈支持锁允许的最大深度,或抛出StackOverFlowError异常,即是虚拟机栈过多,导致了堆栈溢出

public class JavaVMStackSOF { private int stackLength = 1; private void stackLeak(){ stackLength++; stackLeak(); } /** * VM args: -Xss128k * -Xss 栈内存容量 * @param args */ public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); try { oom.stackLeak(); // Throwable ->Error, Exception } catch (Throwable e) { System.out.println("stack length:"+oom.stackLength); // e.printStackTrace(); } } }

以上代码会抛出

stack length:1003

java.lang.StackOverflowError 异常

虚拟机栈溢出还有一种 OutofMemoryError异常,这个异常,可以这样理解,一台机器的物理内存是4个G,其他系统应用占用2G,堆占用1G,永久代占用512M,栈可用的空间是512M,如果每个线程设置成1M,最大可创建512个线程

/** * 本地虚拟机栈溢出 * 设置每个线程的栈大小:-Xss2m * 运行时,不断创建新的线程(且每个线程持续执行),每个线程对一个一个栈,最终没有多余的空间来为新的线程分配,导致OutOfMemoryError */ public class StackOOM { private static int threadNum = 0; public void doSomething() { try { Thread.sleep(100000000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { final StackOOM stackOOM = new StackOOM(); try { while (true) { threadNum++; Thread thread = new Thread(new Runnable() { @Override public void run() { stackOOM.doSomething(); } }); thread.start(); } } catch (Throwable e) { System.out.println("目前活动线程数量:" + threadNum); throw e; } } }

以上代码会报错

java.lang.OutOfMemoryError: unable to create new native thread

3.方法区溢出

说了堆,栈,还有个方法区(永久代)也会出现溢出,方法区什么时候溢出,主要看,方法区存放的是类信息,常量和静态变量,与编译器编译后的代码等。

溢出一般会报错 java.lang.OutOfMemoryError: PermGen space

JDK 1.8之后去除了PermGen space 使用Metaspace 代替了,如果方法区溢出,会有如下异常:

Caused by: java.lang.OutOfMemoryError: Metaspace

import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * 设置方法区最大、最小空间:-XX:PermSize=10m -XX:MaxPermSize=10m * 1.8 设置 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m * 运行时,通过cglib不断创建JavaMethodAreaOOM的子类,方法区中类信息越来越多,最终没有可以为新的类分配的内存导致内存溢出 */ public class JavaMethodAreaOOM { public static void main(final String[] args){ try { while (true){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(JavaMethodAreaOOM.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o,objects); } }); enhancer.create(); } }catch (Throwable t){ t.printStackTrace(); } } }

4.本机直接内存溢出

本机直接内存(DirectMemory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域,但 Java 中用到 NIO 相关操作时(比如 ByteBuffer 的 allocteDirect 方法申请的是本机直接内存),也可能会出现内存溢出的异常。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员奇点 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档