java内存模型和常见的内存溢出异常

对于java来说,虽然存在着内存管理但是,只有了解了内存模型,才能更好的解决内存溢出和内存泄漏方面的问题。

01)java的内存模型

   程序计数器 程序技术器是作为当前线程所执行的字节码行号指示器。由于java虚拟机的多线程是通过线程轮流切换并且分配处理器执行时间的方式来实现的,在任何一个时刻,一个处理器只是会执行一个线程,为了能够在线程切换后,恢复到正确的代码执行位置,每一个线程都有一个独立的程序计数器,相互之前没有影响,为线程私有内存,而且是java中唯一一个没有规定内存溢出异常的区域。

02)虚拟机栈和本地方法栈

    虚拟机栈为java方法服务,本地方法栈为使用到的native方法服务

在java虚拟机规范中规定了两种异常,StackOverflowError和OutOfMemeoryError异常

03)java堆

    此区域的唯一目的就是存放对象实例,几乎所有的对象都在这里分配内存,也是GC发生的主要区域

04)方法区

方法区和java对一样都是java线程共享的内存区域

05)运行时常量池

 运行时常量池是方法区的一部分,对于这个部分,java规范没有做任何细节要求,不同虚拟机厂商可以有不同实现方式。运行时常量池不一定要求必须编译期才能产生,可以通过String类的intern()方法,在运行期间将常量放入常量池中。

06)对象的创建

  虚拟机在遇到一条new指令的时候,首先去检查这个指令参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化过,如果没有则执行类加载过程,在类加载过程通过以后,虚拟机将会为这个类分配内存,所需内存在类完成加载后便可完全确定。分配内存使用的方式,指针碰撞和空闲列表。指针碰撞的前提是java堆是绝对规整的,有用的和空闲各自放在一边,中间放着一个指针作为分界点指示器,在使用Serial,和ParNew等收集器时候使用的是指针碰撞。如果java堆不是规整的,比如CMS收集器使用的标记清理算法,所以使用空闲列表,空闲列表是因为java堆是不连续的,所以维护了一个列表,记录了那些事可用的内存,在分配的时候从列表中寻找够用空间给对象使用。

在对象的分配是非常频繁的行为,而且java堆是线程共享的区域,所以有线程安全问题,解决方案:使用本地线程分配缓冲(TLAB),通过参数指定和CAS配上失败重试的方式。

07)对象的内存布局

   对象在内存中分为三个区域,对象头,实例数据和对齐填充。对象头一部分存储对象的自身运行时数据,如:GC分代年龄,锁状态标志,一部分存储类型指针,指向它的元数据的指针,虚拟机通过这个确定这个对象是哪个类的实例。但是,如果是数组,在对象头里面还需要一块记录数组的长度,因为普通的对象可以通过元数据确定对象的大小,而数组的元数据无法确定数据的长度,所以需要在对象头里面存储数组的长度。

08)对象的访问定位

   对象的访问方式有两种,句柄访问和直接指针访问

   句柄访问优势:对象的reference中存放的是稳定的句柄地址,当对象移动时候(垃圾收集)只会改变句柄中实例数据的指针,reference本身不需要修改。

   直接指针访问优势:速度快。

 

 

    原文作者:烟尘
    原文地址: https://www.cnblogs.com/histlyb/p/6719079.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞