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

正文

一. 基本概念

在开始讲解之前, 需要先明确关于 JVM 的一些基本概念

我们都知道, Java 是一个跨平台的语言, Java 跨平台的基本支撑其实就是 JVM 对操作系统底层细节的屏蔽, 相当于加了一个中间层(计算机中的任何问题都可以加一个中间层解决~), Java 不再像 C/C++ 等语言一样直接翻译为针对特殊平台的机器码, 而是翻译为字节码, 也即是我们的 class 文件, 下图大概可以比较简明的概括了~; 字节码就相当于 Java 世界中的汇编, 而 JVM 则不是跨平台的, 只是不同平台的 JVM 都能识别和运行标准格式的字节码文件而已

关于 JVM 运行 class 文件, 我觉得下图已经可以比较准确的表达了

我们下面要讲的就是 Runtime Data Area 部分

二. 运行时数据区

JVM 会在执行 Java 程序的时候把它所管理的内存划分为若干个不同的数据区, 如下:

2.1 程序计数器

线程私有

2.1.1 存储数据类型

指向下一条需要执行的字节码指令; 如果线程正在执行一个 Java 方法, 该计数器记录的是正在执行的虚拟机字节码指令的地址; 如果正在执行 Native 方法, 该计数器值则为空( Undefined )

2.1.2 异常情况

该区域是是唯一一个在 Java 虚拟机中没有规定任何 OutOfMemoryError 情况的区域

2.2 Java虚拟机栈

线程私有

2.2.1 存储数据类型

描述 Java 方法执行的内存模型, 每个方法调用就对应着一个栈帧的入栈和出栈; 一个栈帧里面存储了局部变量表, 操作数栈, 动态链接, 方法出口等信息

局部变量表存储了编译器可知的各种基本数据类型, 对象引用, returnAddress ; 局部变量表的大小在编译期间即可确定, 运行期间大小不变

2.2.2 异常情况

StackOverflowError : 线程请求栈深度大于虚拟机允许深度

异常示例代码:

public class JavaVMStackSOF {    private int stackLength = 1;    public void stackLeak() {

        stackLength++;

        stackLeak();

    }    public static void main(String[] args) {

        JavaVMStackSOF sof = new JavaVMStackSOF();        try {

            sof.stackLeak();

        } catch (Throwable e) {

            System.out.println("Stack Length: " + sof.stackLength);            throw e;

        }

    }

}

OutOfMemoryError : 虚拟机栈动态扩展时无法申请到足够内存

异常示例代码:

public class JavaVMStackOOM {    private void dontStop() {        while (true) {

        }

    }    public void stackLeakByThread() {        while (true) {            new Thread(new Runnable() {                @Override

                public void run() {

                    dontStop();

                }

            }).start();

        }

    }    public static void main(String[] args) {

        JavaVMStackOOM oom = new JavaVMStackOOM();

        oom.stackLeakByThread();

    }

}

注: 由于操作系统分配给每个进程的内存空间是有限制的, 所以如果是由于建立过多的线程导致内存溢出, 在不能减少线程数或者更换 64 位虚拟机的情况下, 可以选择通过减少最大堆和减少栈容量来换取更多的线程

2.3 本地方法栈

线程私有

2.3.1 存储数据类型

和虚拟机栈类似, 只是本地方法栈提供的是 Native 方法服务

2.3.2 异常情况

StackOverflowError 和 OutOfMemoryError

2.4 Java堆

线程共享

垃圾收集管理的主要区域

2.4.1 存储数据类型

几乎所有的对象实例都在这里分配

2.4.2 异常情况

OutOfMemoryError

异常示例:

public class JavaVMHeapOOM {    static class HeapOOM {

    }    public static void main(String[] args) {        List<HeapOOM> list = new ArrayList();        while (true) {            list.add(new HeapOOM());

        }

    }

}

2.5 方法区

线程共享

该区域的垃圾回收目标主要是针对常量池的回收和对类型的卸载

2.5.1 存储数据类型

存储已被虚拟机加载的类信息, 常量, 静态变量, 即使编译器编译后的代码等数据

2.5.2 运行时常量池

运行时常量池是方法区的一部分, 但是 JDK6 之后, 常量池被放入了堆中;

Class 文件中也有常量池部分, 即编译期生成的各种字面量和符号引用, 这部分将在类加载后进入方法区的运行时常量池中, 此外还会把翻译出来的直接引用也存储在运行时常量池中

运行时常量池相对于 Class 文件常量池的另外一个最重要的特征是具备动态性, 即运行期间也可以将新的常量放入池中, 比如 String 的 intern() 方法

String.intern() 作用是: 如果字符串常量池中已经包含一个等于此 String 对象的字符串, 则返回代表池中这个字符串的 String 对象; 否则, 将此 String 对象包含的字符串添加到常量池中, 并且返回此 String 对象的引用

同样, 收方法区的限制, 当常量池无法再申请到内存时会抛出 OutOfMemoryError

2.5.3 异常情况

OutOfMemoryError : 方法区无法满足内存分配需求

异常示例:

public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {        List<String> list = new ArrayList<>();        int i = 0;        while (true) {

            list.add(String.valueOf(i++).intern());

        }

    }

}

2.6 直接内存

直接内存不是虚拟机运行时数据区的一部分, 但是也被频繁使用, 如: 在 JDK1.4 中新加入了 NIO 类, 引入了一种基于通道( Chanel )和缓冲区( Buffer )的 I/O 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后通过一个存储在Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作, 避免了在 Java 堆和 Native 堆中来回复制数据, 提高性能

同样会产生 OutOfMemoryError

三. 常见问题

注: 下文摘自文末参考链接, 权作个人笔记~

程序运行永远都是在栈中进行的,因而参数传递时, 只存在传递基本类型和对象引用的问题, 不会直接传对象本身; 但是传引用的错觉是如何造成的呢? 在运行栈中, 基本类型和引用的处理是一样的, 都是传值, 所以, 如果是传引用的方法调用, 也同时可以理解为“传引用值”的传值调用, 即引用的处理跟基本类型是完全一样的; 但是当进入被调用方法时, 被传递的这个引用的值, 被程序解释(或者查找)到堆中的对象, 这个时候才对应到真正的对象; 如果此时进行修改; 修改的是引用对应的对象; 而不是引用本身; 即: 修改的是堆中的数据; 所以这个修改是可以保持的了

我产生的对象不多呀, 为什么还会产生 OutOfMemory ?

答: 你继承层次忒多了, Heap 中产生的对象是先产生父类, 然后才产生子类, 明白不?

OutOfMemory 错误分几种? 答: 分两种, 分别是 OutOfMemoryError:java heap size 和 OutOfMemoryError: PermGen space , 两种都是内存溢出, heap size 是说申请不到新的内存了, 这个很常见,检查应用或调整堆内存大小;PermGenspace 是因为永久存储区满了, 这个也很常见, 一般在热发布的环境中出现, 是因为每次发布应用系统都不重启, 久而久之永久存储区中的死对象太多导致新对象无法申请内存, 一般重新启动一下即可

为什么不建议在程序中显式的生命 System.gc() ? 答: 因为显式声明是做堆内存全扫描, 也就是 Full GC , 是需要停止所有的活动的( Stop The World Collection ), 你的应用能承受这个吗?

如果你想学好JAVA这门技术,也想在IT行业拿高薪,可以参加我们的训练营课程,选择最适合自己的课程学习,技术大牛亲授,8个月后,进入名企拿高薪。我们的课程内容有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来,q群号为:180705916 进群免费领取学习资料。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容