Java内存区域与内存溢出异常(一)

来源:互联网 发布:javascript模式 在线 编辑:程序博客网 时间:2024/06/03 15:15

Java内存区域与内存溢出异常(一)

概述

对于一个C或C++程序开发者来说,他们参与了一个对象创建,分配,初始化和销毁的所有过程,但是Java相比于C开发来说,不同的是,jvm自动内存管理机制的帮助下去自动分配地址,创建对象和销毁对象;好处是关于内存溢出和内存泄漏的问题不容易发生,坏处就是一旦出现内存相关的问题,定位问题是极为复杂的

运行时的内存局域

  1. 程序计数器

    1. 作用

      程序计数器是一个很小的内存区域,可以认为这一个区域是作为当前线程执行字节码的行数

    2. 作用域

      对于多个线程来说,每一个线程都有一个独立的程序计数器的内存区域,互不影响

    3. Java方法和native方法中的区别

      Java方法中程序计数器指向的是正在执行的字节码地址;native方法程序计数器没有定义(且只有一个块内存是Java虚拟机中没有被定义任何OutOfMemoryError)

  2. Java虚拟栈

    1. 作用

      主要用来存储局部变量(例如基本类型数据和对象的引用虚拟地址),动态链接和方法出口等信息

    2. 作用域

      线程私有,每一个线程执行过程中都有一个独自的栈空间

    3. 异常情况

      1. 线程请求栈深度大于虚拟机所允许的深度,抛出StackOverflowError
      2. 线程占用栈大小操作栈的最大空间,抛出OutOfMemoryError
  3. Java堆

    1. 作用

      几乎所有对象的存储的内存区域

    2. 作用域

      线程共享的区域

    3. 异常情况

      如果堆中没有足够的内存给对象分配空间的时候,抛出OutOfMemoryError

  4. 方法区

    1. 作用

      主要存储静态变量,常量,class信息和变异后的字节码信息的一块内存

    2. 作用域

      线程共享区域

    3. 异常情况

      和堆差不多,没有足够内存抛出OutOfMemoryError

    4. GC特殊区域

      这个区域又叫“永久代”,意思就是说这里的内存几乎不回收,就算回收一块内存区域的内存回收条件也是极为苛刻的

  5. 直接内存

    常见的情况就是NIO中native方法直接访问系统的真实内存区域,这样的内存就称之为直接内存

一个对象的创建

  1. 对象的创建内存分配模式

    1. 指针碰撞

      划分一块完整的堆内存区域,将已经分配的内存放在一边,空闲的内存放在一边,由一个指针作为分界线,需要分配指定大小的内存时,将分界指定向空闲内存区域移动指定内存大小,这样一种内存方式称之为指针碰撞

    2. 空闲列表

      一块堆内存,这块内存里存储的对象不是顺序存储在内存中,由一张列表记录所有内存的分布方式,需要存储对象的时候,选择一块合适的内存区域存放,且在列表上记录对象的内存放置情况,这样的方式称之为空闲列表

    3. 内存分配模式的取决方式

      选择内存分配模式,取决于内存的回收模式;通常来说使用Serial,ParNew选择是指针碰撞方式内存分配模式;使用Mark-SWeep回收方式选择的空闲列表方式

  2. 对象内存同步方式

    1. 背景

      对于多个线程同时进行对象的内存分配时,如果不采用一种同步方式,那么会导致内存分配的不一致(简单来说就是可能同一块内存被同时分配给两个不同的对象)

    2. 内存同步的方式

      1. 内存动作进行同步处理

        每一次内存分配时,都必须是一个单独的操作,这一个动作只有一个线程进行操作;两个线程同时分配时,其中一个线程会分配失败,但是对于失败的线程,会不停的重新分配,知道分配成功

      2. 内存分配缓冲

        每一个不同的线程都有一个自己单独的内存块,每一个线程只能操作自己的内存块,每一个线程的内存块由虚拟机来分配;这样的方式又叫TLAB(Thread Local Annocation Buffer)

  3. 对象的内存布局

    1. 对象的主要内存

      一个具体对象主要包括对象头,实例数据和对齐填充数据

    2. 对象头主要包括数据

      哈希值,GC分代年龄,锁状态标识,线程持有锁,偏向线程ID和偏向时间戳等运行时数据

      对象类型指针,当然这里并不是所有都有这个对象类型指针

      假如这个对象是数组的话,那么还需要存储数组的长度数据

    3. 对象的实例数据

      作为这一部分数据是作为对象真正存储的有效数据部分,例如对象的成员变量具体值

      对象实例数据分配策略:一般来说是先父类后子类,先宽字节字段再短字节字段(long/double > int > short/char > byte/boolean …)

    4. 对象的对齐填充数据

      由于虚拟机规定,对象占用内存必须是8个字节的整数倍,所以对于部分对象实例数据不是8个字节的数据必须要进行填充,一般一来说对象头都是8个字节的整数倍

  4. 对象的访问方式

    1. 句柄方式

      虚拟机会在堆中单独划出一个内存区域作为句柄池,栈中的句柄指针不是直接指向堆中的具体对象,而是指向堆内存中句柄池中某一个具体的句柄,而这个句柄存储着对象实例数据的指针和对象类数据的指针,这样对于对象这个对象来说是不用存储对象的类型数据

    2. 直接指针访问

      栈中的对象指针直接指向堆内存中的对象实例数据,而这个对象的实例数据中同时存储着对象类型数据的指针

    3. 句柄方式的优势

      对象实例数据的地址发生改变的时候,栈中的句柄指针是不需要改变的,只要改变句柄中的对象实例数据指针

    4. 直接指针的优势

      速度快,减少了一次指针的定位开销

原创粉丝点击