JVM运行时内存主要分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区五个部分,如下图所示。

java visualvm 分析内存占用过大 jvm运行时内存_程序计数器

1.程序计数器

  • 一块较小的内存空间,存储当前线程执行的字节码行号指示器
  • 线程私有,每个线程都有一个独立的程序计数器,随着线程的创建而创建,随着线程的销毁而销毁
  • 唯一一个在java虚拟机中不会OOM的区域
  • 用来记录当前线程执行到代码的具体某一行,此时若线程被挂起时,程序计数器用来记录代码已经执行的位置,当线程恢复执行后继续从记录位置开始执行。常见的异常处理、分支操作等都是通过通过程序计数器来完成的。

2.本地方法栈

  • 本地方法栈与虚拟机栈基本相同,主要用来管理nattive方法,如在Android中使用JNI。
  • 占用的内存区大小是不固定的,可根据需要动态扩展。
  • 线程私有

3.虚拟机栈 

  • 虚拟机栈里面是一个个的栈帧,栈帧里面包含局部变量表、操作数栈、动态链接、方法返回地址等信息
  • 每个java方法在执行的时候会创建一个栈帧,方法从执行开始到结束过程就是栈帧在虚拟机栈中入栈出栈过程。
  • 局部变量表:存放编译期可知的基本数据类型、对象引用、return类型,所需的内存空间会在编译期间完成分配,进入一个方法时在栈帧中局部变量表的空间是完全确定的,不需要运行时改变。
  • 操作数栈:主要用于保存方法中计算过程的中间结果,同时作为计算过程中变量临时的存储空间,每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就已经定义好了,保存在方法的Code属性中,为max_stack的值
  • 动态链接:每个栈帧都保存了一个可以指向当前方法所在类的运行时常量池,目的是当前方法中如果需要调用其他方法的时候,能够从运行时常量池中找到对应的符号引用, 然后将符号引用转换为直接引用,然后就能直接调用对应方法,也就是方法中调用其他方法的链接方式
  • 方法返回:存放该方法在寄存器中的值,即是该方法的指令地址,方便执行引擎在执行完该方法后,回到该方法对应的指令行号,这样才能继续执行下去。
  • 线程私有区域
  • 若线程申请的栈深度大于虚拟机允许的最大深度,会抛出SatckOverFlowError错误。 虚拟机动态扩展时,若无法申请到足够内存,会抛出OutOfMemoryError错误。

4.方法区

  • 方法区主要用来存储已被加载的类信息、静态变量、常量、即时编译器编译出来的代码数据。
  • 线程共享区域
  • 无法满足内存分配需求时会发生OOM。

5.堆

  • 堆中包含老年代,新生代
  • 新生代:一个Eden区和两个Survivor区,由于在发生Minor GC时候会把存活的对象拷贝到另一个Survivor区上,因此也称为from区和to区 。对象优先在Eden区域分配,大对象直接进入老年代;之所以大对象直接进入老年代的目的是为了避免Eden和Survivor的互相拷贝大对象。虚拟机提供了-XX:PretenureSizeThreshold(该设置只对Serial和ParNew收集器生效)参数,大于该参数设置值的对象将直接在老年代分配
  • 长期存活的对象会进入老年代;如果对象在Eden分配后并经过一次Minor GC(Young GC)依然存活,并且能被Survivor区域容纳,将对象复制到Survivor区域,同时将对象的年龄设置为1,对象在Survivor经历过一次Minor GC,年龄增加到一定程度(默认是15岁),就会晋升到老年代。这个阈值-XX:MaxTenuringThreshold来设置相关阈值。
  • MinorGC(Young GC)是指发生在新生代的垃圾收集动作,因为java 对象大多数都具备朝生夕灭的特性,所以YoungGC一般比较频繁,一般回收的速度也比较快。触发条件:Eden空间不足以分配内存给新的对象。
  • Major GC(Full GC)指发生在老年代GC,出现Full GC 一般会伴有Yong GC,Full GC 速度一般比Young GC慢10倍以上。触发条件: 老生代空间不足,新生代对象或者大对象无法转入老生代,大对象一般需要大的连续空间,如果直接进入老年代,很容易出现Full GC,因此避免短存活期的大对象存在.
  • 线程共享区域,是JAVA虚拟机管理的内存中最大的一块,在虚拟机启动时创建。
  • 存放对象实例,几乎所有的对象实例都在堆上分配,GC管理的主要区域。

参考

一文看懂JVM运行时内存分布