Java虚拟机结构及常见内存溢出异常

每个Java虚拟机都有一个类加载器子系统,根据某个全限定名来装入类型,同样每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载类的方法中的指令。

当虚拟机运行一个程序时,就需要从已加载的文件中得到信息,将这些信息组织到运行时数据区,以便于管理。

Java运行时的数据区域划分

1、程序计数器:程序计数器是一块较小的内存空间,可以看做是当前线程的字节码的行号指示器。

Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个时刻,一个处理器只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每一个线程都需要有一个独立的程序计数器,各条线程之间互不影响,独立存储。所以也称该内存区域为线程私有的内存。

此内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM(OutOfMemoryError)情况的区域。

2、Java虚拟机栈

其生命周期与线程相同,每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接等。每一个方法调用到完成的过程,对应着栈帧入栈到出栈的过程。

人们经常说的java内存分为堆内存和栈内存,所谓的栈内存就是指Java虚拟机栈,或者说是虚拟机栈中局部变量表的部分。

局部变量表中存放各种基本类型和对象引用。

若线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverFlowError

如果虚拟机的栈可以动态扩展,但扩展也有一个上限,扩展时无法申请到足够的内存,抛出OutOfMemoryError

3、本地方法栈

本地方法栈的作用类似于虚拟机栈。

区别在于Java虚拟机栈为虚拟机执行Java方法服务

而本地方法栈则为虚拟机使用到的Native方法服务

类似虚拟机栈,本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常

4、Java堆

Java堆是Java虚拟机所管理的内存中最大的一块,是线程共享的区域。

Java堆由新生代、老年代和永久代组成

新生代:

是类的成长、诞生、消亡的区域,最后被垃圾收集器收集,结束生命。新生代又分为Eden、From survivor和 To survivor。对象首先是分配在Eden,当Eden中不够存储时,会将其存到survivor,如果survivor中无法存储会移动到老年代。

老年代:

存放从新生代中筛选出来的对象。

JAVA堆可以处于物理上的不连续空间,只要逻辑上是连续的即可。

堆在实现时,可以是固定大小、也可以动态扩展(-Xmx和-Xms)。当堆无法在扩展时,会抛出OOM异常。

5、方法区(永久代):

  JAVA虚拟机规范把方法区描述为堆的一个逻辑部分,但是它还有另外一别名是非堆(Non-Heap),目的应该与Java堆区分开来。

  

是一个常驻内存区域,存储了每一个类的结构信息,例如运行时常量

池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包

括一些在类、实例、接口初始化时用到的特殊方法,也就是运行环境必须的类信息,此区域的是不会被垃圾收集器回收掉的。JVM关掉时,才会释放此区域所占用的内存。方法区无法满足内存分配需求时,将抛出OOM。

Ps:方法区和永久代本质上不等价,但是对HotSpot来说,使用永久代来实
现方法区,对于其他虚拟机(BEA JRockit、IBM J9等)是不存在永久代
的概念的。

6、运行时常量池

运行时常量池是方法区的一部分,用于存放编译时期生成的各种字面量和符号引用。运行期间也可以将新的常量放入池中,比如String类的intern()方法。当常量池无法再申请到内存时会抛出OOM异常。

7、直接内存

直接内存是java虚拟机内存管理之外的一个内存区域,java引入NIO后,引入了一种基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过堆内对象对这块内存进行操作。

显然,本机直接内存的分配不会受到JAVA堆大小的限制,这类内存也会产生OOM异常,因为本机的内存大小是有限的。

  • 常见内存溢出异常例子

JAVA堆溢出

JAVA堆用于存储对象实例,不断创建对象,超过堆的大小,即可造成堆溢出。

/**
 *  参数:-Xms20m  -Xmx20m  -XX:+HeapDumpOnOutOfMemoryError
 * -Xms20m 堆的最小值
 * -Xmx20m 堆的最大值
 * 设置为一样,可避免自动扩展
 *
 * -XX:+HeapDumpOnOutOfMemoryError
 * 让虚拟机在出现内存溢出的时候DUMp出当前内存堆转储快照以便事后分析
 *
 * */
public class HeapOOM {

    public static void main(String[] args) {

        List<HeapOOM> list = new ArrayList<HeapOOM>();
        while(true)
        {
            list.add(new HeapOOM());
        }
    }
}

//输出: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

虚拟机栈和本地方法栈溢出

在单线程的情况,只能形成的异常是StackOverflowError,我们使用递归造成栈溢出。

 /**
 *使用 -Xss128k 减小栈容量
 */

public class JavaVMStackSOF {

    private int stackLength = 1;
    public void stackLeak()
    {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {

        JavaVMStackSOF oom = new JavaVMStackSOF();
        try{
            oom.stackLeak();
        }
        catch(Throwable e){
            System.out.println("stack length::"+oom.stackLength);
            throw e;
        }

    }
}
//输出:Exception in thread "main" java.lang.StackOverflowError

周志明老师在书上说到,在多线程情况下,会造成OutOfMemoryError异常,可是我试了一下,结果是死机了,最后Eclipse没反应,等了好久,强制关机了。

我贴上代码,但是运行时可能会死机,大家还是小心行事。

 public class JavaVMStackSOF {

    private void dontStop()
    {
        while(true)
        {}
    }

    public void stackLeakByThread()
    {
        while(true)
        {
            Thread th = new Thread(new Runnable() {

                @Override
                public void run() {

                    dontStop();
                }
            } );

            th.start();
        }
    }
    public static void main(String[] args) {
        JavaVMStackSOF oom = new  JavaVMStackSOF();
        oom.stackLeakByThread();
    }

}

方法区溢出

/*
 * VM args : -XX:PermSize=10M  -XX:MaxPermSize=10M  限制方法区的大小
 *
 *
 * Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
     * */
    public class RuntimeConstantPoolOOM1 {

        public static void main(String[] args) {

            //使用List保持引用,避免FullGC回收常量池
            List<String> list = new ArrayList<String>();
            int i = 0;
            while(true)
            {
                list.add(String.valueOf(i++).intern());
                System.out.println(i);
            }
        }

    }

//输出:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

关于书上String.intern的例子,我从网上转载了一篇很详细的文章解释

深入解析String#intern

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-07 23:44:39

Java虚拟机结构及常见内存溢出异常的相关文章

Java虚拟机学习4、内存溢出

堆溢出 Java堆唯一的作用就是存储对象实例,只要保证不断创建对象并且对象不被回收,那么对象数量达到最大堆容量限制后就会产生内存溢出异常了.所以测试的时候把堆的大小固定住并且让堆不可扩展即可.测试代码如下 1 package com.xrq.test; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * 测试内容:堆溢出 8 * 9 * 虚拟机参数:-Xms20M -Xmx20M -XX:+HeapDumpOn

Java常见内存溢出异常分析(OutOfMemoryError)

链接地址:http://my.oschina.net/sunchp/blog/369412 1.背景知识 1).JVM体系结构 2).JVM运行时数据区 JVM内存结构的相关可以参考: http://my.oschina.net/sunchp/blog/369707 2.堆溢出(OutOfMemoryError:java heap space) 堆(Heap)是Java存放对象实例的地方. 堆溢出可以分为以下两种情况,这两种情况都会抛出OutOfMemoryError:java heap spa

如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码

程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习<深入理解java虚拟机-JVM高级特性与最佳实践>这本书,终于初步了解了一下java虚拟机的内存模型.本文通过写出使jvm发生内存溢出异常的代码来对自己的学习结果进行总结,同时也提醒自己以后写代码时候不要再跳进这个坑啦. java的内存管理是由java虚拟机自动进行管理的,并不需要程序员过多的手动干预

Java虚拟机-内存溢出异常

内存泄露与内存溢出 内存泄露一般是代码设计存在缺陷导致的,指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存.即被分配的对象可达但是已经无用:通过了解内存泄露的场景,可以避免不必要的内存溢出和提高自己的代码水平: 内存泄露的几种场景:1.长生命周期的对象持有短生命周期对象的引用      例如:在全局静态map中缓存局部变量,且没有清空操作,随着时间的推移,这个map会越来越大,造成内存泄露:2.修改hashset中对象的参数值,且参数是计算哈希值的字段     当一个

深入理解java虚拟机学习 笔记 第二章 java 内存区域和内存溢出异常

2.2 运行时区域 java虚拟机划分成若干个不同的数据区域, 1.程序计数器,字节码解释器工作时就是通过改变计数器的值来取吓一跳需要执行的字节码命令 了解 String.intern()方法 作用: 如果字符串常量池中一个包含了一个等于此String对象的字符串,则返回代表池中的这个字符串的String对象,否则将此对象包含的字符串添加到常量池中,并返回此String对象的引用. 实战 将堆的最小值 -Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展  通过参数 -XX:+HeapD

深入理解java虚拟机系列(一):java内存区域与内存溢出异常

文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了开始.如果有什么错误或者遗漏,欢迎指出. 一.概述 先上一张图 这张图主要列出了Java虚拟机管理的内存的几个区域. 常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂,从上图就可以看出了.堆栈分法中所指的"栈"实际上只是虚拟机栈,或者说是虚拟机栈中的局部变量表部分.接下

《深入理解Java虚拟机》读书笔记---第二章 Java内存区域与内存溢出异常

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来.这一章就是给大家介绍Java虚拟机内存的各个区域,讲解这些区域的作用,服务对象以及其中可能产生的问题. 1.运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 1.1程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型中里,字

深入了解Java虚拟机(1)java内存区域与内存溢出异常

java内存区域与内存溢出异常 一.运行时数据区域 1.程序计数器:线程私有,用于存储当前所执行的指令位置 2.Java虚拟机栈:线程私有,描叙Java方法执行模型:执行方法时都会创建一个栈帧,存储局部变量,基本类型变量,引用等信息 3.Java本地方法栈:线程私有,为虚拟机使用到的Native方法服务 4.Java堆:线程共享,是垃圾收集器的主要工作地方:存储对象实例等 5.方法区:线程共享:存储类信息,常量,静态变量等 运行时常量:存放编译时生成的各种字面量和符号引用 6.直接内存:机器的内

【java虚拟机】java内存区域与内存溢出异常

本文参考自<深入理解Java虚拟机>一书.主要总结一下java虚拟机内存的各个区域,以及这些区域的作用.服务对象以及其中可能产生的问题. 1. 运行时数据区域 java虚拟机在执行java程序的过程中会把它说管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁.具体如下图所示: 结合这张图,下面逐个来分析一下每个数据区域的特点. 1.1 程序计数器(Program Counter