springboot运行时内存溢出_深入理解JVM虚拟机——JVM内存区域(运行时数据区)

03af12489091464df04b8f5b972c8639.png

欢迎关注专栏《Java架构筑基》——专注于Java技术的研究与分享!

Java架构筑基​zhuanlan.zhihu.com
65563f972bb439c4dac90bb7cbc08615.png
  • Java架构筑基——专注于Java技术的研究与分享!
  • 后续文章将首发此专栏!
  • 欢迎各位Java工程师朋友投稿和关注

一、运行时数据区

50b5ac9cb5dbef189fb977cc4002651d.png

线程私有的内存区域:

  • 程序计数器、虚拟机栈、本地方法栈

线程共享的内存区域:

  • 方法区、Java堆

二、线程私有的内存区域

2.1 程序计数器

Program Counter,简称 PC,用于存放 下一条 指令所在单元的地址,是 线程所执行的字节码的行号指示器。因为JVM的多线程是通过轮流切换来分配CPU的执行时间(时间片轮询),当切换到下一条线程的时候, 线程要能知道当前要执行的字节码位置,这就要求每条线程都要有一个自己的程序计数器,独立 存储待执行的虚拟机字节码指令的地址。

2.2 虚拟机栈

34e32ce6f2ed50b23ccb18ab3974464c.png
虚拟机栈生命周期同线程,所以不必担心垃圾回收问题。 虚拟机栈(VM Stack)这个内存区域对应的是每个线程class方法执行的内存模型(不仅Java,像Kotlin、Scala、Groovy等于都可以编译成class文件并允许在JVM上)。线程中的每个方法在执行的同时都会创建一个 栈帧(Stack Frame)。每个class方法从 被调用执行完成的过程,都对应着一个栈帧在虚拟机栈从 入栈出栈的过程。 栈帧是用来 存储 (方法内的)局部变量表、(计算时的)操作数栈、(引用的)动态链接、(进入或退出方法时的)方法出入口等。
  • 局部变量表:存放 编译期 可知的数据类型,包括 基本数据类型(boolean、byte、char、short、int、long、float、double)、对象引用(reference类型)、returnAddress类型(指向一条字节码指令的地址)。每个 局部变量空间(slot)的长度是 32位,而 longdouble类型的每个变量占用 2个局部变量空间,也就是会占用64位空间。局部变量表所需的内存大小在编译期就已经确定,并记录在class文件中。
  • 操作数栈:栈是一个先进后出的线性表,操作数栈是逻辑计算的数据容器,比如下面的计算:

int a = 1; int b = 2; int c = 1 + 2; retuen c;

2.3本地方法栈

本地方法栈和虚拟机栈相似,只不过虚拟机栈是用于线程中执行Java方法(字节码,因为还有Scala、Kotlin、Groovy等其他运行在虚拟机上的编程语言),本地方法栈是用于执行本地的 native方法

三、线程共享的内存区域

3.1 堆

堆Heap又称 Java堆,这个内存区域是JVM线程共享的区域,也是JVM管理的最大一块内存区域。堆内存区域创建于JVM启动时。 唯一 目的就是存放 实例对象,反之不成立,也就是说有少量的对象实例并不在Java堆中存放,也可以在栈上分配, Java堆仅存放绝大量的对象实例Java堆 不像 栈 那样,随线程而生、随线程而亡。Java堆中的实例对象可能被多个线程中的变量所引用,一个线程死亡,而其他线程还可能正使用此实例对象,所以 Java堆 成为垃圾收集器收集垃圾一个重要的区域(宏观来讲是垃圾收集器管理的主要区域)。
现在主流的垃圾收集器采用 分代收集算法,堆内存又被细分为 新生代(Young Generation)老年代(Old Generation)(默认比: 1:2)。 新生代 再细分为:1个 Eden空间2个Survivor空间(默认比例8:1:1),2个Survivor空间其中一个是 From Survivor空间和一个 To Survivor 空间分代分空间的唯一理由就是优化GC性能
常量池
  • 静态常量池:即.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数值)字面量,还包含类、方法的信息*,占用class文件绝大部分空间;
  • 运行时常量池:则是JVM虚拟机在完成 类装载 操作后,将class文件中的常量池载入到内存中,并保存在 方法区 中,我们常说的 常量池,就是指方法区中的运行时常量池,存放在字面量和符号引用

3.2方法区

方法区用来 存储类信息(类的版本、接口描述)、常量、静态变量、方法(数据与编译后代码)等数据,也有一些方法区的数据生命周期长,进而将方法区称为“永久代(Permanent Generation)”,永久存在不被GC回收,但本质上并不相等。方法区在逻辑上是和Java堆相连的,该区域一般GC很少参与,偶尔会存在对不使用的常量进行内存回收,对于一些卸载的类进行资源回收,因为这些数据占用内存本来就比较少,所以GC的回收效果也非常的一般。
  • JDK1.6的永久代(PermGen)
  • JDK1.7的永久代(PermGen)(过度阶段)
  • JDK1.8的元空间(Metaspace)(直接内存)
用jdk1.6运行后会报错,永久代这个区域内存溢出会报: Exception in thread “main” java.lang.OutOfMemoryError:PermGen space的内存溢出异常,表示永久代内存溢出。
在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是 针对常量池的回收和对类型的卸载。但是在之后的HotSpot虚拟机实现中,逐渐开始将方法区从永久代移除。Java7中已经将运行时常量池从永久代移除,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做 元空间(MetaSpace)
使用jdk1.7后 验证如下:执行代码和上面相同 设置参数:-Xmx20m -Xms20m -XX:-UseGCOverheadLimit,这里的-XX:-UseGCOverheadLimit是关闭GC占用时间过长时会报的异常,然后限制堆的大小,运行程序,果然,一会后报异常:
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space 从上面的异常可以知道我们测试增加的常量都放到了堆中,所以限制堆内存以后,不断增加常量,堆内存会溢出。
总结: jdk1.6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面,和堆相独立。所以导致String的intern()方法因为以上变化在不同版本会有不同表现。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值