zoukankan      html  css  js  c++  java
  • 深入理解java虚拟机--Java内存区域和内存溢出异常

    前言

    对于Java程序员来说,在虚拟机自动内存管理的机制下,不需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出的问题。不过,如果在编写程序时没有合理的创建对象,就会造成内存泄漏或者溢出这样的问题,如果不了虚拟机内存的区域划分以及创建的对象时虚拟机对它的内存分配情况,那么排查这些错误就会比较困难。下面我们来具体介绍一下Java内存区域以及内存溢出的相关知识。

    Java运行时数据区域

    java虚拟机在程序运行时把它管理的内存划分为若干个数据区域,这些区域都有各自的用途,以及创建和销毁时间,有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而建立和销毁。它包括一下几个运行时数据区域,如下图所示。

    JVM运行时内存图

    图1 java运行时数据区图

    程序计数器

    程序计数器是比较小的一块儿内存区域,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型了,字节码解释器就是通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个程序计数器来完成。
    它属于线程隔离的数据区,因为虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。在任何一个确定的时刻,一个处理器或者一个多核处理器的一个内核只能来执行一个线程中的指令。因此为了线程切换后能够恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,所以程序计数器它是线程私有的

    JVM虚拟机栈

    虚拟机栈也是线程私有的,它和程序计数器的生命周期是相同的。虚拟机栈它描述的是java方法执行的内存模型:每个方法在执行的同时都换创建一个栈帧,用来存储局部变量表操作数栈动态链接方法出口等信息。每个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
    局部变量表存储了编译期可知的各种基本数据类型(boolean、byte、char、short、int、long、float、double)、对象的引用(reference类型,它不是对象本身,它只是指向了对象起始地址的引用指针,或者指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向一条字节码指令的地址)
    在该区域中,规定了两种异常状态如果线程请求栈深度大于虚拟机所允许的深度,则会抛出StackOverFlowError异常如果虚拟机可以动态扩展(当前大部分虚拟机都能够动态扩展),如果无法申请到足够的内存,那么就会抛出OutOfMemoryError异常

    本地方法栈

    本地方法栈和虚拟机栈非常类似,它是用来执行虚拟机调用到的Native方法。在虚拟机规范中对虚拟机使用的语言、使用方式和数据结构并没有强制的规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(sun hotspot)直接就把本地方法栈和虚拟机栈合为一个。与虚拟机栈一样,它也会抛出StackOverFlowError和OutOfMemoryError异常。

    Java堆

    java堆是虚拟机管理的最大的一块内存,在虚拟机的启动时创建。Java堆是被所有线程共享的一块区域,几乎所有对象的实例都存放在这块区域
    java堆是垃圾收集管理的主要区域,所以也被叫做GC堆。从内存回收的角度来看,由于现在的收集器基本都采用分代收集的算法,所以java堆还可以细分为:新生代和老年代;再细分还有Eden空间、From Survivor空间、To Survivor空间。关于这些区域的分配和回收,我们会在之后的文章进行详细介绍。
    java堆可以是物理上不连续的一片存储空间,只要逻辑上是连续的一片内存空间即可。在实现时,既可以固定大小的也可以是可扩展的,**当前主流的虚拟机都是按照可扩展的来实现的(通过-Xmx和-Xms来控制)**。如果在堆中没有完成内存的分配,则会抛出OutOfMemoryError的异常。

    方法区

    方法区和java堆内存一样,都是线程共享的内存区域。它是用来存放已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。方法区从内存回收的角度来讲,可以被叫做永久代但其实两者是不等价的,只是因为HotSpot虚拟机的设计团队把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。其实这样的方案更容易使方法区出现内存溢出,所以在jdk1.7的hotspot中,已经把原本放在永久代的字符常量池移出
    这个区域的垃圾回收目标主要是对常量池的回收和类型的卸载。尤其是类型的卸载的回收,要求很苛刻,但这部分的回收是很必要的。当方法区无法满足内存的分配需求时,会抛出OutOfMemoryError的异常。

    运行时常量池

    运行时常量池是方法区的一部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项就是常量池,用于存放编译期生成的字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。它属于方法区的一部分,同样也会因为内存没有满足分配需求而抛出OOM的异常。

    内存溢出异常

    在讲内存溢出之前,我们先来了解一下两个概念。

    内存泄漏

    内存泄漏(memory leak)是指程序在申请内存后,无法释放已经申请的内存空间,多次的内存泄漏就会导致内存被占满。

    内存溢出

    内存溢出(out of memory)是指程序在申请内存时,没有足够的内存空间供使用,导致OOM。

    参考文献

    《深入理解Java虚拟机》

    ----------------------------------------未完待续-----------------------------------------

  • 相关阅读:
    梯度下降进阶
    梯度下降基础
    python---matplotlib
    python---numpy
    浅析Jupyter Notebook
    anaconda安装
    机器学习---导学
    python---线程与进程
    mapping values are not allowed in this context at line 115 column 10
    laravel进行单元测试的时候如何模拟数据库以及mockery的调用
  • 原文地址:https://www.cnblogs.com/mr-ziyoung/p/13237360.html
Copyright © 2011-2022 走看看