Java内存区域

霸气de小男生 提交于 2020-02-07 04:16:32

Java内存区域与OOM

引言

一句契合Java/C++使用者的话,“两者之间存在一堵由内存分配与GC技术筑建起来的高墙,墙里面的人想出去,墙外面的人却想进来”。

概述

Java有着自动的内存分配管理机制,也有自己独特的内存回收机制,只有熟练掌握这些知识,才能够真正地使用这门语言

内存区域

先上一个内存区域图:

在这里插入图片描述

如图,JAVA运行时内存区域分为

  • 方法区(线程共享)
  • 堆(线程共享)
  • 虚拟机栈(线程私有)
  • 本地方法栈(线程私有)
  • 程序计数器(线程私有)

下面我们来讲讲这些内存区域所负责的工作。

程序计数器

  • JVM中的程序计数器与操作系统中的程序计数器功能是一致的,只不过操作系统的程序计数器记录的是下一条执行指令的地址,而JVM的程序计数器记录的是“当前”线程执行的字节码指令或者分支、循环、异常、线程恢复等功能的字节码支持。
  • 程序计数器对不同方法的支持
Java method JNI
记录执行的字节码指令地址 Native方法这个计数器记录为空值
  • 程序计数器是JVM中唯一不会出现OOM的地方。

VM Stack虚拟机栈

  • JVM中的虚拟机栈也是线程私有的,一个线程在执行指令的过程中,会创建自己的虚拟机栈,生命周期与线程相同。
  • 虚拟机栈的描述
    • 其中栈帧的内容其实是线程执行的方法,可以这么说,线程每执行一个方法,就会在自己的虚拟机栈上创建一个方法栈帧,栈帧中包括局部变量表、操作数栈(OP)、动态链接、方法出口等信息。
  • 栈帧中的局部变量表存放了编译早期可知的各种基本数据类型以及引用类型。
  • 虚拟机栈
    • 当调用深度超过栈深度,就会发生SOF异常
    • 当分配栈帧时内存不足,则会发生OOM异常

总之,一个线程在执行过程中,一个方法执行完毕,则会将栈帧弹出栈。


Native Stack本地方法栈

  • 本地方法栈与虚拟机栈的功能一样,只不过执行的是JNI方法,因此,有些虚拟机会将这两部分合二为一。

Heap堆内存

  • 这里的堆内存是指JVM中的堆内存区域,而非计算机的堆内存区。

堆内存的功能

  • 对象实例、数组都在堆上分配内存的地方
  • 堆内存也是GC的重点关照之处,通过对堆内存的进一步划分,GC可以更好地回收内存和提高JVM的运行性能

堆内存的特点

  • 堆内存从宏观是来讲是线程共享内存,但是,在JAVA的实现中,可能会对线程划分出一些私有的本地缓冲,即类似与ThreadLocalMap之类的,这是语言层面上的内存私有,与JVM层面的内存共享不同。
  • 堆内存并不要求物理上是连续的,同时,堆内存处于JVM内,而JVM本身也相当于一个程序,堆内存与OS的内存之间,会存在一层复制,(Netty的0拷贝技术也是利用堆外内存)

当堆内存不足的时候,会发生OOM异常。


方法区与常量池

  • 方法区与区中的常量池都是线程共享的,它不属于堆内存,它存放着JVM加载的Class信息、常量、静态变量等信息。
  • 可能有的人会认为这个属于GC中的永久代,而实际上,GC中的永久代仍然位于堆内存中,而方法区是“永久”,但并非永久代。

特点

  • JVM的常量池与C是不同的,C的常量在编译时已经进行空间的分配,而JVM的常量池是动态的,比如进行动态的类加载,就会改变常量池分配的大小。或者String的intern也可以动态拓展方法区常量池。
  • 由于常量池也是动态的,因此,也会发生OOM异常。

Direct Memory直接内存

什么是直接内存

  • 直接内存并不是运行时数据区的一部分,它并不属于JVM的内部内存,但是这部分内存有很大的应用以及操作不当的风险,因此这里必须对堆外内存进行解释。
  • 名“堆外内存”或“直接内存”

直接内存的特点

  • 在JDK1.4之后,引入了NIO,它基于一种Channel与Buffer的I/O方式,可以使用Native方法进行堆外内存的分配,然后利用一个存储在JAVA堆中的一个DirectByteBuffer对象作为这块堆外内存的引用进行操作,实际数据是存储在堆外。这样能够减少数据在JVM与OS堆中互通有无的性能消耗。

应用

  • Netty就是利用堆外内存进行channel数据的0拷贝,加快传输速度。

注意

  • 物理内存=JVM设置的最大内存+(JVM)堆外内存
  • 有时候我们只考虑了JVM的内存大小参数,而忽略了堆外内存大小,也是会发生OOM

讲讲OOM

OOM即OutOfMemoryError,发生OOM有两种可能:

  • 内存溢出
    • 这个是比较常见的,如使用时候把太多的数据加载到JVM而没有及时释放,就会出现这个问题。
  • 内存泄漏
    • 发生这个问题,肯定是代码中某些地方存在引用,而又非必须的引用。

解决方式:

  • 讲dump日志进行转储,利用分析工具,确认内存中的对象是否是必要存在,如果非必要存在,那么很可能就是发生了内存泄漏,再去查看GC Roots,减少不必要的引用即可解决。
  • 如果是内存不足,那么就从物理、虚拟机、代码上去拓展优化。

总结

以上就是JVM内存区域的解释,掌握了JVM内存区域,才能理解JAVA的GC机制,以及编写更加高性能的代码。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!