深入理解Java虚拟机----(二)内存区域与内存溢出异常

 2019-12-22 11:08  阅读(768)
文章分类:JVM

内存区域: 与c++的由人为的内存管理相比,Java将内存交给了虚拟机来管理。 根据虚拟机规范,内存分为几个区域:
2019120001595\_1.png

  • 堆:线程共享区域。规范定义存放真正的对象和数组的内存区域。虚拟机大多采用分代收集算法,可以分为新生代老年代。新生代可分为Eden、From Survivor、To Survivor。线程分配角度,可能划分出线程私有的缓冲区域。如果堆内存不够分配,也无法扩展,就会OutOfMemoryError。

  • 虚拟机栈:线程私有栈。描述的是方法执行的内存模型,方法执行同时建立一个栈桢,存储局部表量表、操作数表栈、动态链接、方法出口等。方法的调用和调用结束的过程,对应了一个栈桢的出栈和入栈的过程。通常说的栈就是这个区域,或者说是这个区域内的局部变量表。局部表量表存放对象地址引用或指令地址。线程调用深度如果超出限制,会StackOverFlowError。如果无法申请到足够的内存,会OutOfMemoryError。

  • 方法区:线程共享区域。存储已经被虚拟机加载的类、常量、静态变量、即时编译器编译的代码等信息。HotSport将垃圾回收延伸到了此区域,所以常说的永久代,就是这个区域。但其实本质是不一样的。这个区域可以进行垃圾回收,但条件苛刻,所以效果一般也不好。内存不够会OutOfMemoryError。

    • 运行时常量池:方法区的一部分,存储字面常量、直接引用。可以动态的加入信息,如String的intern方法。可能OutOfMemoryError。
  • 本地方法栈:与虚拟机栈一样的功能,只不过是为了执行本地方法。规范相对自由,可以自由实现。HotSport将它与虚拟机栈合二为一。与虚拟机栈一样可能抛出两种错误。

  • 程序计数器:线程私有区域。存储当前的字节码的行号等信息。字节码解释器就是通过修改这个区域的值来读取下一条指令的地址,分支、循环、跳转、异常处理、线程切换等功能都靠这个区域实现。规范中,不会出现内存错误。

  • 这里还有一中特殊的情况,直接内存:JDK1.4加入了NIO,可以通过Native函数直接分配堆外内存,提高性能。不受虚拟机内存限制,但受物理内存限制。可能出现直接内存与虚拟机内存的总和过大,OutOfMemoryError。

对象创建过程:
new一个普通java对象时,首先进行类检查,如果没有加载,先加载类。加载后,为新对象分配空间。分配空间操作可能有多线程同步问题,有两种解决办法:循环重试CAS(实际采用)和把分配操作按线程划分在不同区域(预先为线程分配一块缓冲区TLAB,在这块上分配,满了再分配一块,这时候才需要同步)。虚拟机参数可以指定是否开启TLAB。分配过后,将分到的内存都初始化为零值,所以java里的变量都可以直接用,因为已经赋值过了。接下来设置对象头信息,包括:类、hashcode、GC年龄。根据是否使用偏向所等设置,对象头设置也不同。最后按照程序员的编码初始化init。

对象内存布局: 对象在内存中,可分为三部分。对象头、实例数据、对齐填充。

  • 对象头包括:

    • 运行时数据:hashcode、GC分代年龄、锁信息等。
    • 类型指针:指向类元数据
    • 如果是数组还包括长度。
  • 实例数据存储各定义的字段的数据。

  • 对齐填充只起到填充作用,例如HotSport规定对象大小必须8字节的整数倍。

对象的访问定位:
对象的访问需要通过栈上的对对象的引用来定位堆上的对象实体,但是规范没有定义引用具体如何存储对象信息、如何定位。有两种方式:句柄和直接指针。

  • 句柄:这种方式会在堆内划分出句柄池存储句柄。栈中的引用存储句柄的地址,句柄中存储类元数据地址和对象地址。
  • 直接引用:引用直接存储对象地址。但是如何找到类元数据就需要想办法了。例如存到对象头。(HotSport采用,速度快。)

下面是这两种方式的对比图:

2019120001595\_2.png

2019120001595\_3.png

OOM问题排除(HotSport):
除了程序计数器,其他区域都可能OOM。

  • 堆溢出:-XX:+HeapDumpOnOutOfMemoryError可以在OOM时,生成当前的堆快照 。可以使用工具分析快照,找到泄露对象和位置。如果不是泄露,那就要尝试扩大内存设置,或减少内存使用。
  • 虚拟机栈和本地方法栈:在HotSport中,二者合在一起了。OOM可能是单纯的单个线程的空间不够,或者线程太多,每个线程都要分配内存,这时减少栈空间反而是一种解决方案。
  • 方法区:OutOfMemoryError:PermGen space。这种情况也并非不常见,spring等框架都会动态生成类,可能将这个区域填满而又无法扩展。
  • 直接内存:-XX:MaxDirectMemorySize设置。不设置则与-Xmx堆最大值一样。这个异常的特征是在快照中通常看不到什么明显的异常信息,如果程序又使用了NIO,可以考虑这种情况。
点赞(1)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 深入理解Java虚拟机----(二)内存区域与内存溢出异常

相关推荐