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

1.运行时数据区域:

java虚拟机运行时数据区

 

(1). 程序计数器:较小的内存空间,他可以看成当前线程所执行的字节码行号的指示器。 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native(非Java代码接口)方法,这个计数器值则为空。 这是唯一一个在Java虚拟机中不会抛出OutOfMemoryError的区域。

 

(2).Java虚拟机栈:线程私有,包括局部变量表等。局部变量表:在编译器可知的各种基本类型。 在Java虚拟机中规定了两种异常:如果线程请求的栈深度大于虚拟机允许的最大深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,但是扩展时候无法申请到足够的内存,就会抛出OutOfMemoryError异常。

 

(3).本地方法栈:为Native方法服务,类似于虚拟机栈。。。

 

(4).Java堆:线程共享,存放对象实例。如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,抛出OutOfMemoryError异常。

 

(5).方法区:线程共享,存储,已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

 

(6).运行时常量池:其属于方法区的一部分,Java虚拟机对于Class文件的每一部分都有严格的规定(自然包括常量池),但是对于运行时常量池,并没有做出任何细节上的要求。 其相比于CLass常量池的另外一个重要特性就是,Java语言并不要求常量一定要在编译器产生,运行期也可以将新的常量放入池中(String类的intern方法:jdk1.7之后是查看常量池中是否存在和调用方法的字符串内容一样的字符串,如果有的话,就返回该常量池中的字符串,若没有的话,就在常量池中写入一个堆中该字符串对象的一个引用,指向堆中的该对象,并返回该引用。 ) 类似方法区,其也会抛出OutOfMemoryError异常。

 

2.对象的创建:

 

(1)虚拟机收到new指令:检查该指令的参数是否能够在常量池中定位到一个类的符号的引用,并检查这个符号的引用代表的类,是否已被加载,解析和初始化过。没有,则执行该过程。

(2)分配内存:指针碰撞方法:Java堆中内存结对规整时采用;空闲列表方法:Java

堆中内存并不规整。对于是否规整,需要看采用的垃圾回收器是否带有压缩整理功能。

(3)解决,分配内存时,指针改变指向的线程不安全问题:对于分配内存空间动作做同步处理,或者采用分配缓冲,吧内存分配动作,按照线程划分在不同空间之中。

(4)内存分配完成后,执行内部空间初始化为0值(不包括对象头):保证对象的实例字段在Java中不赋初始值届可以直接使用。

(5)对于对象进行必要的配置:该对象时哪个类的实例,如何才能找到类的元数组信息,对象的哈希码,对象的GC分代年龄等信息(该信息在对象头中)。

(6)执行init方法:从虚拟机角度上已经执行完了,但是在Java的角度上,才刚开始,因为需要执行init进行初始化。

 

3.对象的内存布局:对象头,实例数据,对齐填充

(1)对象头:哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳。(Mark Word)因为这部分时与定义内存无关的额外内存,为了减少内存消耗,其被设计成非固定数据结构。

下面表示32位HotSpot虚拟机中,如果对象处理未被锁定状态,Mark Word的32位空间分成:25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,而在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下:

(2)实例数据:真正存储信息的部分,分配策略:真正存储有效信息,即程序代码中所定义的各种类型的字段内容,包括父类继承下来的和子类定义的。 这部分存储顺序受虚拟机分配策略参数(FiledsAllocationStyle)和字段在Java源码中定义的顺序的影响。 HotSpot虚拟机默认分配策略为longs/double、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略可以看出,相同宽度的字段总是被分配到一起。在此前提条件下,父类中定义的变量会出在子类之前,若CompactFileds的参数值为true,那么子类中较窄的变量也可能会插到父类变量的空隙中。

(3)对其填充:并不是必然存在的,起到占位作用。

 

4.对象的访问定位:

建立对象是为了使用对象,Java程序需要通过栈上的引用数据来操作堆上的具体对象,由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中对象的具体位置,故对象访问方式取决于虚拟机实现。

目前主要有两种方式:

(1)使用句柄

Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。如下图所示:

 

(2)直接指针

Java堆对象的布局中需要放置访问类型数据的相关信息,reference中存储的直接就是对象的地址,如下图所示:

(3)二者相比:句柄的好处是reference存放的是稳定的句柄地址,不会因为对象被移动而修改(垃圾回收时经常会移动对象)。直接指针的好处是,减少了一次指针定位的时间开销,速度更快。

 

5.一些常见的内存问题以及解决思路:

(1)Java堆溢出:不停创建对象,并且保证保证GC Roots到对象之间有可达路径,防止被回收。导致堆溢出。 抛出:OutOfMemoryError异常。

解决:通过内存映像分析工具,判断是,内存泄漏还是内存溢出。内存泄漏:通过工具,找到泄露对象到GC Roots的引用链。如果不存在泄露:检查虚拟机的堆参数(-Xmx与-Xms),与及其物理内存相比,看是否可以调大。

(2)虚拟机栈和本地方法栈溢出:如果是因为建立过多线程导致的内存溢出,再不能减少线程数或者更换64位虚拟机的情况下,可以通过减少最大堆和减少栈的容量来换取更多的线程。

(3)在你的系统OOM之后Dump文件很小,内存中直接或者间接使用了NIO,检测下是不是本机直接内存溢出,由于Directmemory导致的。

OOM:out of memory,指在linux里面,由于系统内存压力,系统会选择保护一些系统进程,而将一些其他的进程kill掉,释放内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值