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

运行时数据区域

JVM执行java程序的时候有一个运行时数据区,每个区域有自己的作用,了解这些区域有助于我们理解JVM。JVM运行时数据区如图所示:

程序计数器

该区域是线程私有的,字节码解释器通过改变程序计数器来获取下一条指令来执行程序,每一个线程都有一个独立的程序计数器。在执行java方法的时候,计数器记录的是虚拟机字节码指令的地址,执行本地Native方法的时候,计数器的值为空,程序计数器是没有OutOfMemoryError的现象的

Java虚拟机栈

虚拟机栈也是线程私有的,是Java方法执行的内存模型,方法执行的时候会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的执行就是一个栈帧在虚拟机栈中入栈出栈的过程。

虚拟机栈有两种异常情况:

  • 线程请求的栈深度大于JVM允许的深度,会抛出StackOverflowError异常
  • 如果虚拟机可以动态扩展,那么扩展到无法申请足够的内存时,会抛出OutOfMemoryError异常

本地方法栈

和虚拟机栈差不多,一样有虚拟机栈的两种异常情况

Java堆

所有线程共享,内存管理中最大的一块,用于存放对象实例,几乎所有的对象实例都在这里分配,垃圾收集器管理的重点区域,当Java堆无法再扩展时,将抛出OutOfMemoryError异常。

方法区

所有线程共享,存储已经被虚拟机加载的类信息、常量、静态变量等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

直接内存

不是运行时数据区的一部分,但是值得注意一下,尤其是配置内存的时候。

HotSpot虚拟机对象

对象的创建

平常我们创建对象一般使用New这个关键字,实际上是一个很复杂的过程

  1. New指令发出来后,虚拟机会去检查这个指令参数能否在常量池找到一个类的符号引用
  2. 检查这个符号引用代表的类是否已被加载、解析、初始化、没有就执行相应的类加载过程
  3. 虚拟机为新生对象分配内存
  4. 对分配的内存空间初始化为零值
  5. 执行init方法,按照程序员的意愿进行初始化

分配内存的方式:

指针碰撞:内存规整,只需从空闲内存中分配一块

空闲列表:内存不是规整的,从空闲列表中找到足够大内存空间进行分配

PS:内存是后规整和垃圾收集器的方式有关

对象的内存布局

对象内存布局可以分为3块区域:对象头、实例数据、对齐填充

对象头分为两部分,一部分用于存储对象自身的运行时数据:

储存内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 重量级锁定
空、不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

另外一部分是类型指针,虚拟机通过这个指针确定这个对象是哪个类的实例。

对象的访问定位

Java程序通过栈上的引用来操作堆上的具体对象。

目前主流的访问方式有使用句柄和直接指针:

  • 句柄访问的方式,java堆会划分出一块内存来作为句柄池,栈上的引用储存的就是对象的句柄地址。句柄地址中包括了对象的实例数据与类型数据的具体地址信息
  • 直接指针访问,栈上的引用储存的就是对象的地址

句柄的优点:对象被移动,只会修改句柄中的实例数据的指针,引用不需要修改

直接访问的优点:速度更快,减少时间开销

测试内存异常

Java堆溢出

Java堆用于存储对象实例,不断创建对象,就可以测试到现象

 /**
* VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
* @author Axe
**/
import java.util.ArrayList;
import java.util.List; public class HeapOOM { static class OOMObject { }
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid5488.hprof ...
Heap dump file created [13371418 bytes in 0.049 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at HeapOOM.main(HeapOOM.java:17

堆内存的OOM异常是最常见的,出现的时候我们可以使用Memory Analyzer工具分析hprof文件,具体的使用方式,网上很多教程、如果都是正常的,就要考虑是不是堆的空间给得不够大。

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

直接测试:

/**
* VM Args: -Xss128k
*/
public class JavaVMStacksOF {
private int stackLength = 1; public void stackLeak(){
stackLength++;
stackLeak();
} public static void main(String[] args) {
JavaVMStacksOF of = new JavaVMStacksOF();
try {
of.stackLeak();
}catch (Throwable e){
System.out.println("stack length:"+ of.stackLength);
throw e;
} }
}

运行结果:

stack length:981
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:8)
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)

出现StackOverflowError可以阅读堆栈信息查找问题

方法区和运行时常量池溢出

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
while (true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o,objects);
}
});
enhancer.create();
}
} static class OOMObject{ } }

很遗憾,我在java1.8里面没有复制到现象

本地直接内存溢出

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
* VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM {
public static final int _1MB = 1024 * 1024; public static void main(String[] args) {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
try {
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

运行结果:


Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at DirectMemoryOOM.main(DirectMemoryOOM.java:17)

直接内存溢出后Dump文件会很小,如果程序中使用了NIO,就需要往这方面思考。

《深入理解JVM》第二章读书笔记的更多相关文章

  1. 《深入理解JVM虚拟机》读书笔记

    前言:<深入理解JVM虚拟机>是JAVA的经典著作之一,因为内容更偏向底层,所以之前一直没有好好的阅读过.最近因为刚好有空,又有了新目标.所以打算和<构架师的12项修炼>一起看 ...

  2. 20135320赵瀚青LINUX第二章读书笔记

    第二章-从内核出发 获取内核代码 使用git 获取最新提交到版本树的一个副本 $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/tor ...

  3. 《Ansible自动化运维:技术与佳实践》第二章读书笔记

    Ansible 安装与配置 本章主要讲的是 Ansible 安装与基本配置,主要包含以下内容: Ansible 环境准备 安装 Ansible 配置运行环境 Ansible 环境准备 从 GitHub ...

  4. Android深度探索--HAL与驱动开发----第二章读书笔记

    1. 底层开发工具包括: JDk6或者以上版本:Eclipse3.4或以上版本:ADT(用于开发Android应用程序),CDT(用于开发AndroidNDK程序):Android SDK:Andro ...

  5. Linux第二章读书笔记

    1.获取内核源码 1.1Git 分布式的:下载和管理Linux内核源代码: - 获取最新提交到版本树的一个副本 $ git clone git://git.kernel.org/pub/scm/lin ...

  6. 2013337朱荟潼 Linux第二章读书笔记——从内核出发

    1.获取内核源码 1.1Git 分布式的:下载和管理Linux内核源代码: - 获取最新提交到版本树的一个副本 $ git clone git://git.kernel.org/pub/scm/lin ...

  7. <深入理解计算机系统>第七章读书笔记

    第七章读书笔记 链接 链接:将各种代码和数据部分收集起来并组合成为一个单一文件的过程.(这个文件可被加载或拷贝到存储器并执行) 链接可以执行于编译,加载或运行时. 静态链接: 两个主要任务: 1 符号 ...

  8. Linux内核分析第四章 读书笔记

    Linux内核分析第四章 读书笔记 第一部分--进程调度 进程调度:操作系统规定下的进程选取模式 面临问题:多任务选择问题 多任务操作系统就是能同时并发地交互执行多个进程的操作系统,在单处理器机器上这 ...

  9. 《构建之法》第四&十七章读书笔记

     <构建之法>第四&十七章读书笔记 一.         前言 再次阅读<构建之法>,愈发被其中生动有趣的举例吸引.作为一本给予软件工程学生的书籍,其不以枯燥的理论知识 ...

随机推荐

  1. Codeforces Round #555 (Div. 3) D. N Problems During K Days 【数学思维】

    一 题面 D. N Problems During K Days 二 分析 对于这题,刚开始我就是陷入了对公式的执着,企图用公式直接确定第一个数,然后试着去找序列.经过思考和手动模拟后发现是很难保证正 ...

  2. Go语言指针

    指针简介 (Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值.由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元.因此,将 ...

  3. 基于Anaconda 安装 geatpy 和 tensorflow

    装了好久的第三方包终于成功了,暴风哭泣!!!总结一下 分两部分说: 一. 首先是在本地电脑windows系统下装: 首先安利一下这个包括各种 Genetic and Evolutionary Algo ...

  4. 本地docker镜像上传Docker Hub,并且在腾讯云上pull该镜像,最后运行成功。

    1:在docker hub 上注册一个账号(本人直接能注册,有的说不能),然后创建自己的仓库. 2:登录docker hub ( longdbdocker --hub账号,longdb --仓库名称) ...

  5. Chrome获取微信授权,调试公众号页面

    1.目的 你可能遇到过这种情况,在微信中打开公众号是这样的. 复制链接,在chrome中打开是这样的. 博主今天要解决的就是,如果在chrome中加载需要微信授权的页面,至于加载成功后要干嘛,测试?抓 ...

  6. 13 Timer和TimerTask

    下面内容转载自:http://blog.csdn.net/xieyuooo/article/details/8607220 其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了ru ...

  7. 弗格森&红魔

    本周日曼联对阵斯旺西时,一位名叫皮特-莫利纽克斯的58岁老人,将会举起一张老旧的床单,就如他曾做过的那样.那一次“臭名昭着”的行为要追溯到1989年12月,弗格森当时已经来到了悬崖边上,他的背后是一堵 ...

  8. CSS小技巧(一)

    左右布局 将内部的子元素加浮动,父元素清除浮动即可. 代码: <!DOCTYPE html> <html> <head> <title>test< ...

  9. 获取服务器时间ajax

    $.ajax({ type:"OPTIONS", url:"/", complete:function(x){ // alert(x.getResponseHe ...

  10. PL/SQL之异常

    异常分为预定义异常和用户自定义异常.预定义异常是由系统定义的异常.由于他们已在STANDARD包中预定义了,因此,这些预定义异常可以直接在程序中使用,而不用在预定义部分声明.而用户自定义异常则需要在定 ...