专栏首页程序员开发者社区Java虚拟机--运行时数据区与内存溢出

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

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 方法申请的是本机直接内存),也可能会出现内存溢出的异常。

本文分享自微信公众号 - 程序员开发者社区(gh_016ffe40d550),作者:猿星人

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-09-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 数据库原理: Change Buffer 是干什么的?

    redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写),而 change buffer 主要节省的是随机读磁盘的IO消耗。

    王小明_HIT
  • 【深入理解Java原理】 JVM 内存区域

    Java 内存区域分成,堆,方法区,虚拟机栈,本地方法栈,程序计数器 (直接内存不是JVM内存的一部分但是有时候会导致OutOFMemory)

    王小明_HIT
  • MySQL 普通索引和唯一索引该如何选择?

    普通索引和唯一索引在查询能力上没啥差别,主要考虑对更新性能的影响,要尽量选择普通索引。接下来分析两种索引在查询语句和更新语句对性能的影响。

    王小明_HIT
  • JVM - 运行时数据区域(1)

    Java 虚拟机在执行 Java 程序的过程中,会把它管理的内存划分成若干个不同的数据区域。

    happyJared
  • day85- <缓存+序列化+信号+ORM性能相关+随机验证码>

    少年包青菜
  • Java面试题—基础题目

    在并发编程领域,有进程和线程两个概念,在Java语言中说起并发编程,常常是指多线程,但是了解进程的概念也非常重要:

    阿杜
  • Java多线程-带你认识Java内存模型,内存分区,从原理剖析Volatile关键字

    地址:https://juejin.im/post/59f8231a5188252946503294

    用户2802329
  • broadcasting(自动扩展)与合并分割(上)

    boradcasting简而言之有两大特点:(1)功能类似于上期讲的expand,可以实现维度扩展。(2)但它不需要复制数据,因此可以极大的节省内存空间。

    用户6719124
  • Java核心知识详解:JVM+微服务+分布式+设计模式+数据结构与算法

    JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、

    慕容千语
  • 并发编程学习笔记04-内存模型基础

    Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。

    汐楓

扫码关注云+社区

领取腾讯云代金券