JVM学习之自动内存管理机制(一)

一、java内存区域与内存溢出异常(简介)

A. 根据《java虚拟机规范(java SE 7 版》的规定),java细腻及所管理的内存宝库以下几个运行时数据区域:

  • 程序计数器:一块较小的内存空间,可以看做是当前线程所执行的字节码的信号指示器。多线程是线程切换需要恢复到正确的执行位置,所以每个线程都拥有独立的程序计数器,此类内存区域则为“线程私有”的内存。
  • java虚拟机栈:线程私有,生命周期和线程同步,方法执行时同步创建,用于储存局部变量,操作数栈,动态链接,方法出口等信息。这里注意,虚拟机栈有可动态扩展和固定深度,如果是固定深度,线程请求深度超出栈固定深度就会抛出StackOverflowError异常,或者在栈动态扩展到超出内存限制的时候会抛出OutOfMemoryError异常。
  • 本地方法栈:与虚拟机栈相似,不过虚拟机栈是为了执行java方法,而本地方法栈是为虚拟机找到本地方法服务的,简单来说可以当做和本地的一个链接口。
  • java堆:虚拟机管理的最大一块内存,所有线程共享,虚拟机启动时创建。因是垃圾收集管理器主要管理区域,又称GC堆。后面具体介绍堆中内存分配和回收细节。
  • 方法区:所有线程共享区,储存已被虚拟机加载的类信息,常亮,静态变量,即使编译器编译后的代码等数据。注意区别于堆的长久代。此区域很少发生GC,主要针对常量池回收与类型卸载。
  • 运行时常量池:方法区的一部分。class文件有一部分常量池信息,存放编译期生成的各种字面量和符号引用,在类加载之后进去方法区存入运行时常量池。但运行时常量池并非是只有编译器才能产生,运行期也可以将新的常量放入此区域,例如String类的intern()方法。
  • 直接内存:独立于java虚拟机规范中定义的内存区域,例如NIO类,可以直接引用native方法分配堆外内存,所以在配置虚拟机参数时,容易忽略此部分内存导致超出物流内存抛出OutOfMemoryError异常

B. HotSpot虚拟机在Java堆中对象的分配,布局和访问

  1. 对象创建:除去引用检测,类加载过程,此处主要关注新生对象内存分配策略:“指针碰撞”,“空闲列表”;从堆内存是否规整和对象创建频繁度考虑选取分配策略。同时,进行分配动作同步——CAS配置失败重试,或者将每个线程在内存中预分配一小块内存(TLAB)两种方法解决分配并发问题。之后对象的一些设置这里不细谈。
  2. 对象的内存布局:
  • 对象头:

      1.Mark Word

      

       2.类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

  • 实例数据:对象真正储存的有效信息。
  • 对齐填充:非必然存在,起占位符的作用。HotSpot VM的自动内存管理要求对象地址必须是8字节的整数倍,用于填充补齐。

  3.对象的访问定位:

    1.句柄式访问:稳定

      

    2.直接指针式:快速,HotSpot使用

      

 C 内存溢出异常及排除

  1.java堆溢出

    a.内存泄漏:通过工具查看快照找到GC Roots和泄漏对象间的引用链,排查泄漏位置;

    b.内存溢出:检查虚拟机堆参数(-Xms和-Xmx)和物理机内存大小比例,优化代码,减少长生命周期的对象等;

  2.栈溢出:HotSpot虚拟机并不区分虚拟机栈和本地方法栈,即-Xoss参数(本地方法栈大小设置)无效,只有-Xss对栈大小设置有效

    在单线程时,无论是栈帧太大还是栈容量太小,内存无法分配时抛出的都是StackOverflowError异常,多线程时线程栈越大越容易出现OutOfMemoryError,物理内存是有限的,除去堆,方法区,计数 器,本地方法栈,虚拟机栈等容量,线程栈的大小决定了最大线程数的上限。

  3.方法区和运行时常量池溢出(-XX:PermSize和-XX:MaxPermSize设置,限制方法区大小)

    方法区是一个很少发生GC的区域,所以在生成大量Class的应用中,特别注意此类内存溢出异常

  4.本机直接内存溢出(默认等于java堆最大内存,通过-XX:MaxDirectMemorySize参数指定)

    此类异常的明显特征就是无明显的异常,但Dump文件一般很小,并且如果程序中直接或者间接使用了NIO,可以考虑检查这方面原因

 

posted @ 2019-09-05 16:18  鲁尐肅  阅读(129)  评论(0)    收藏  举报