JVM内存区域与内存溢出的异常

作者: 异步_缓存_队排好 | 来源:发表于2019-06-03 12:42 被阅读24次
    java虚拟机运行时数据区
    线程独享区域 线程共享区域
    虚拟机栈 方法区
    本地方法栈
    程序计数器
    区域 存储内容
    对象和数组
    程序计数器 虚拟机字节码指令的地址或者是Undefined
    本地方法栈 Native方法
    虚拟机栈 局部变量表操作数栈动态链接方法出口
    方法区 类信息常量静态变量

    JVM内存区域

    程序计数器

    内存空间小,线程私有。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,循环、异常处理、线程恢复等基础功能都需要依赖计数器来完成。

    java虚拟机栈

    线程私有,生命周期和线程一致。描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈桢用于存储局部变量表、操作数栈、方法出口等信息。每一个方法从调用直到执行结束,就对应这一个栈桢从从虚拟机栈中入栈到出栈的过程

    局部变量表:存放了编译期可知的各种基本类型(八种基本的数据类型)、对象引用类型和返回地址类型(指向了一条字节码指令的地址)

    • stackOverFlowError:线程请求的栈深度大于虚拟机所允许的深度。
    • outOfMemoryError:如果虚拟机栈可以动态扩展,而扩展是无法申请到足够的内存。

    本地方法栈

    java虚拟机栈是执行java方法的,而本地方法栈则是虚拟机使用到的Native方法服务的。也会有stackOverFlowError、outOfMemoryError异常。

    栈是什么?

    栈也叫做栈内存,主管java程序的运行,是在线程创建是创建的,它的生命周期是和线程一样的,线程结束栈内存也就是释放了。对于栈来说不存在垃圾回收的问题。

    栈帧中主要保存什么?

    • 本地变量: 输入参数和输出参数以及方法内的变量
    • 栈操作:记录出栈入栈的操作
    • 栈帧数据: 包括类文件方法

    栈的运行原理

    栈中的数据都是以栈帧的格式存在的,栈帧是一个内存块是一个数据集,是一个有关方法和运行期数据的数据集
    遵循 FILO原则

    示意图

    顶部栈就是当前方法,该方法执行完毕会自动将此栈帧出栈

    栈顶就是当前方法 栈结构示意图

    java堆

    对于绝大多数应用来说,这块区域是JVM所管理的最大的一块,线程共享,主要是用来存放 对象和数组。内部会划分出多个线程私有的分配缓冲区。可以位于物理上不连续的空间,但是逻辑上要连续。

    堆是什么?(1.7及之前)

    结构图

    一个JVM实例只存在一个堆内存堆内存的大小是可以调节的类加载器读取了类文件后,需要把类、方法、常变量****放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为了三个部分。
    * 新生区
    * 养老区
    * 永久区

    新生区

    示意图

    新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器回收,结束生命。新生区又分为两个部分,伊甸区和幸存者区,幸存者区又分为幸存一区和幸存二区。

    1. 当伊甸区的空间用完时,程序又需要创建对象,jvm的垃圾回收器将会对伊甸区进行垃圾回收,然后将伊甸区的剩余对象移动到幸存0区
    2. 如果将幸存0区也满了,再对该区域进行垃圾回收,然后移动到1区
    3. 如果1区也满了,再次进行垃圾回收,满足条件后再移动到养老区
    4. 养老区也满了会产生FullGC,会产生OOM异常。

    如果出现java.lang.OutOfMemoryError: Java heap space异常,说明
    Java虚拟机的堆内存不够。原因有二::

    1. java虚拟机的内存设置的太小,通过-Xms -Xmx来调整
    2. 代码中创建了大量的大对象,并且长时间不能被垃圾回收器收集。

    养老区

    存放新生代中经历多次GC仍然存活的对象

    永久区

    永久区是一个常驻内存区域,用于存放JDK自身所携带的Class,Integerface的元数据,也就是说他存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收的,关闭JVM才会释放此区域的所占用的内存

    jdk1.7: 有永久代,但已经逐步“去永久代”,常量池1.7在堆
    jdk1.8以后,去掉了永久区,常量池1.8在元空间

    永久代和方法区的区别?

    方法区和堆一样,是各个线程共享的内存区域,不同的是,可以说永久代实现了方法区,(相当于是一个接口的实现),方法区存储的是一些字节码文件,而堆存储的是对象。

    常量池和方法区的关系

    常量池是方法区的一个部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池在类加载后进入方法区的运行时常量池中存放。

    方法区

    属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    运行时常量池

    属于方法区的一个部分,用于存放编译器生成的各种字面量和符号引用。编译器和运行期都可以将常量放入池中。

    内存有限,无法申请是抛出OutOfMemoryError。

    直接内存

    在 JDK 1.4 中新加入 NIO类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作。

    OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常。

    JVM堆参数调优

    -Xms 设置初始分配大小,默认为物理内存的1/64
    -Xmx 最大分配内存,默认为物理内存的1/4

    VM参数: -Xms1024m -Xmx1024m -XX:+PrintGCDetails

    使用MAT工具

    • 分析dump文件,快速定位内存泄漏;
    • 获得堆中对象的统计数据
    • 获得对象相互引用的关系
    • 采用树形展现对象间相互引用的情况
    • 支持使用OQL语言来查询对象信息

    -XX:+HeapDumpOnOutOfMemoryError
    OOM时导出堆到文件。

    相关文章

      网友评论

        本文标题:JVM内存区域与内存溢出的异常

        本文链接:https://www.haomeiwen.com/subject/iwccxctx.html