• java内存区域与内存溢出异常


    运行时数据区域

    一、程序计数器

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

    虚拟机多线程中,为了线程来回切换可以找到各自的位置,各自都需要计数器。

    如正在执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址

    如正在执行的是Native方法,计数器记录为空

    此内存区域是唯一一个没有OutOfMemoryError

    二、java虚拟机栈

    描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至自行完成的过程,就对应着一个栈帧在虚拟机中的入栈与出栈的过程。

    局部变量表中存放了编译期可知的各种基本数据类型、对象引用(不等于对象本身)等。

    局部变量表所需的内存空间在编译期间完成分配,运行时不会改变大小。

    存在两个异常:

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

    OutOfMemoryError:如果虚拟机栈可以动态扩展(大部分都可以扩展),扩展时无法申请到足够的内存。

    三、本地方法栈

     虚拟机栈为虚拟机执行java方法服务;本地方法栈为虚拟机使用到的Native方法服务。

    存在两个异常:

    StackOverflowError OutOfMemoryError

    四、java堆

    最大的一块区域

    唯一的目的存放对象实例

    垃圾回收器管理的主要区域

    可以处于物理上不连续的内存空间,逻辑上连续即可

    可扩展通过(-Xmx和-Xms控制)

    堆无法扩展抛出OutOfMemoryError

    五、方法区

    用于存储已被虚拟机加载的类信息、常量、静态变量等数据

    堆的一个逻辑部分

    很多人称为“永代区”,但不准确,字符串常量池已经移除

    回收的效率不高但是有必要进行

    方法区无法满足内存分配时,抛出OutOfMemoryError

    六、运行时常量池

    Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放前一期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    注:直接内存(并不是运行时数据区)

    基于通道与缓冲区(NIO)此处需要扩充看

    避免了在java堆和Native堆中来回复制数据

    HotSpot虚拟机对象探秘

    一、对象的创建

    1.虚拟机遇到一条new指令,首先检查常量池能否找到类的引用符号,并检查这个引用代表的类是否已被加载、解析和初始化。如果没有先加载类

    2.加载后为新生对象分配内存,大小在加载后即确定

    (1)分配有两种方法

    “指针碰撞”(java堆中内存绝对规整,用过的放一边,空闲的放一边,中间放指针,要用就挪动指针)

    “空间列表”(不规整,虚拟机就维护一个列表)

    是否规整由采用的垃圾收集器是否压缩整理功能决定

    (2)对象创建在虚拟机中是非常频繁地行为,并发情况下并不安全

    两种方案:

    对分配动作同步处理

    把内存分配工作按线程划分在不同的空间进行,即每个线程预先分配一小块内存,成为本地线程分配缓冲(TLAB),哪个线程要分配,就在TLAB上分配

    3.内存分配后虚拟机要将分配到的内存空间都初始化为零值,(保证了)java代码中不赋初始值就可以使用

    4.虚拟机要对对象进行设置,如对象是哪个类的实例,对象的哈希码等,这些信息都在对象头中

    5.从虚拟机的视角,一个新的对象已经产生了,但从java程序,<init>还没执行,所有字段都为0,所以执行new之后,接着执行<init>,进行初始化,这样对象才算产生

    二、对象的内存布局

    3块区域:对象头、实例数据(真正的有效信息)、对齐填充(占位作用)

    三、对象的访问定位

    两种方式:

    使用句柄:java堆划分出块内存作为句柄池,reference(操作对象)中存储的就是句柄地址

    直接指针:直接地址

    优点:

    句柄:稳定

    指针:速度快

    OutOfMemoryError异常

    一、java堆溢出

    对象数量达到堆的容量限制

    不可扩展(将堆的最小值-Xms参数与值-Xmx参数设置为一样即可避免堆自动扩充)

    通过(-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时,通过dump进行分析)

    解决办法:通过内存影响分析工具对Dump出来的堆转储快照进行分析,分析出是内存泄漏还是内存溢出。

    如果是内存泄漏(程序申请内存后,无法正常释放),查看泄露对象到GC Roots的引用链,就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾回收器无法自动回收他们的。

    如果是内存溢出(申请内存时没有足够的空间),不存在泄露,就是内存中的对象还必须存活着,那就检查虚拟机的堆参数(-Xmx与-Xms),与及其物理内存对比看是否还可以调大。

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

    -Xss设置栈大小

    为每个线程的栈分配的内存越大,反而越容易产生内存溢出,因为每个线程分配到的栈容量越大,可以建立的线程数量就越少,建立线程时就越容易把剩下的内存耗尽。

    如果建立线程导致内存溢出,在不能减少线程数或更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

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

    JDK1.6以前运行时常量池是在永代区

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

    String str1 = new StringBuilder("计算机”).append("软件").toString();

     System.out.println(str1.intern() == str1);

    String str2 = new StringBuilder("ja”).append("va").toString();

     System.out.println(str2.intern() == str2);

     JDK1.6是两个false:,JDK1.7是一个true一个false:intern()会把首次遇到的字符串实例复制到常量池,返回的是常量池中的引用,而StringBuilder的实例是在java堆上,。而JDK1.7的intern()不会复制实例,知识在常量池中记录首次出现的实例引用。

    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。这一区域的测试基本的思路就是填满直到溢出,比如CGLib对类的动态增强还有JVM的动态语言Groovy。

    四、本机直接内存溢出

    DirectMemory容量可通过-XX:MaxDirectMemorySize指定,不指定则与java堆最大值(-Xmx指定)一样

    有DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中看不到明显的异常。 

  • 相关阅读:
    Git的commit your changes or stash them before you can merge
    php面试题汇总一(基础篇附答案)
    php面试题汇总二(基础篇附答案)
    php面试题汇总三(基础篇附答案)
    php面试题汇总四(基础篇附答案)
    nodejs 后台服务启动
    解决failed to push some refs to
    读书计划
    spring cloud 学习
    spring IOC
  • 原文地址:https://www.cnblogs.com/jqqiang/p/7684272.html
Copyright © 2020-2023  润新知