美文网首页
Java内存区域与内存溢出异常

Java内存区域与内存溢出异常

作者: 官子寒 | 来源:发表于2020-02-04 14:19 被阅读0次

1. 运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的区域

Java虚拟机运行时数据区

1.1 程序计数器

  • 一块较小的内存空间
  • 作用是当前现成所执行字节码的行号指示器
  • 是一块私有内存区域,因为为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器
  • 如果正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码的地址;如果正在执行的是Native方法,这个计数器为空
  • 没有规定任何OutOfMemoryError情况的区域

1.2 Java虚拟机栈

  • 线程私有,生命周期与线程相同
  • 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候创建一个线帧用于存储局部变量表,操作栈,动态链接,方法出口等信息,每一个方法被执行完,就意味着一个栈帧从入栈到出栈
  • 通常说的栈内存就是Java虚拟机栈
  • 局部变量表存放了编译器可知的各种基本数据类型对象引用returnAddress类型
  • 规定了两种异常状况:1)如果线程请求的深度超过了虚拟机允许的栈深度,将抛出StackOverflowError异常 2)如果虚拟机可以扩展,而扩展时无法获得足够的内存,则会抛出OutOfMemoryError异常

1.3 本地方法栈

  • 与虚拟机栈类似,区别是本地方法栈是为虚拟机使用到的Native方法
  • 会抛出StackOverflowErrorOutOfMemoryError异常

1.4 Java堆

  • Java虚拟机所管理的内存中最大的一块
  • 线程共享的一块内存区域,在虚拟机启动时创建
  • 此内存区域的唯一目的是存放对象实例
  • 垃圾收集管理的主要区域
  • 可以处于物理上不连续的内存空间
  • 目前主流虚拟机都是可以扩展的,如果堆中无法完成实例分配,堆也无法再扩展时,会抛出OutOfMemoryError异常

1.5 方法区

  • 各个线层共享的区域
  • 用于存放已被虚拟机加载的类信息、常量、静态变量等数据
  • Java虚拟机对这个区域限制宽松,可以不需要连续的物理空间,选择不实现垃圾回收
    -当方法区无法满足内存分配需要时,会抛出OutOfMemoryError异常

1.6 运行时常量池

  • 方法区的一部分
  • Class文件中除了有类的版本,字段,方法,接口等信息的描述外,还有一项信息就是常量池,用于存放各种编译器生成的字面量和符号引用,这部分内容在类加载后存放到方法区的运行时常量池中
  • 一般来说,除了保存class文件中描述的符号引用,还会把翻译出来的直接引用存放在运行时常量池中
  • 具备动态性,并非预置入Class常量池的内容才能进入方法区运行时常量池
  • 常量池无法申请到内存时,会抛出OutOfMemoryError异常

1.7 直接内存

  • 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分区域经常被使用
  • JDK1.4 中加入了NIO类,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作
  • 服务器管理员在配置虚拟机参数时,如果忽略掉直接内存,可能会使得各个部分的内存大于实际物理内存的情况,导致OutOfMemoryError异常

2. 对象访问

  • 最简单的访问也会涉及到Java栈,Java堆和方法区三个内存区域
Object object = new Object();
  • Object object意味着在Java栈中生成一个reference对象
  • new Object() 意味着在Java堆中形成了一块存储object实例的内存,存放对象实例数据
  • 方法区中存储此对象的对象类型数据

对象访问方式

  1. 句柄池:reference存储句柄池地址,句柄池存储对象的实例数据和类型数据各自的具体地址信息
    优点:稳定,对象被一定(垃圾回收)只改变句柄中实例数据的指针,reference本身不会改变
  2. 直接指针:reference直接存储地址
    优点:速度快

3. OutOfMemory异常

3.1 Java堆溢出

  • Java堆用于存储对象实例,并且保证GC ROOTS到对象之间有可达路径来避免垃圾回收机制回收这些对象,就会在对象数量达到最大堆的容量限制后产生内存溢出异常


    jvmoptions

Intellij IDEA 设置启动JVM参数

-server
-Xms128m
-Xmx512m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Djdk.http.auth.tunneling.disabledSchemes=""
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Dawt.useSystemAAFontSettings=lcd
-Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine
-Dsun.tools.attach.tmp.only=true
  • -Xms代表堆的最小值 -Xmx代表堆的最大值
  • -XX:+HeapDumpOnOutOfMemoryError可以dump出内存堆转储快照以便分析

解决手段

  1. 通过内存映像分析工具对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要分析是出现了内存泄露还是内存溢出
  2. 如果是内存泄露,则用工具查看GCRoots的引用链,准确定位泄露代码的位置
  3. 如果是内存溢出,则检查是否存在某些对象是否生命周期过长、持有持续时间过长等情况

3.2 虚拟机栈和本地方法栈

public class demo {
    int stackDepth = 1;

    public void seekDepth() {
        stackDepth ++;
        this.seekDepth();
    }
    public static void main(String[] args) {
        demo de = new demo();

        try {
            de.seekDepth();
        } catch (Throwable e) {
            System.out.println("-------------------" +de.stackDepth);
            e.printStackTrace();
        }

    }
}
  • 线帧太大,虚拟机栈容量太小都会造成StackOverflowError异常
  • 多线程情况下,不断地建立线程会产生内存溢出错误。而且,给每个线的栈分配的内存越大,反而越容易产生内存溢出

原因:系统给每个进程的内存是有限制的,Java用虚拟机参数规定Java堆和方法区的内存最大值,剩余内存为操作系统限制 - Xmx - MaxPermSize,程序计数器消耗内存很小,可以忽略,剩下的内存由虚拟机栈和本地栈瓜分,每个线程分配的内存越多,能分配的线程就会越少,建立线程时就容易把内存耗尽

3.3 运行时常量池溢出

  • String.intern:如果池中包含String,则返回这个String对象,否则添加此String,并返回String的引用
  • 我们可以通过-XX:PermSize-XX:PermSize来限制方法区大小,从而限制其中的运行时常量池大小

3.4 方法区溢出

  • 一个类如果要被垃圾回收机制回收,判定条件很苛刻,因此在经常动态生成大量Class的应用中,需要特别注意类的回收状况

3.5 本机直接内存溢出

  • 可以通过-XX:MaxDirectMemorySize指定,如果不指定,则与Java堆的最大值相同

Java的Unsafe类

相关文章

网友评论

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

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