一、Java内存区域与内存溢出异常
java虚拟机在运行java程序时将它所管理的内存区域划分为程序计数器、java虚拟机栈、本地方法栈、堆、方法区这几个运行时数据区。
1.1、程序计数器:一块很小的内存,它的作用是当前线程正在执行字节码的行号指示器。(线程私有)
java虚拟机多线程是通过线程轮流切换执行处理器时间的方式实现的,任何一个确定时刻,一个处理器只会执行一个线程的某个指令,为了多个线程切换后能正确的执行,每个线程都需要一个程序计数器,各个线程的程序计数器互不影响,我们称之为“线程私有”。
1.2、java虚拟机栈(线程私有):是描述java方法执行的内存模型,用于存放局部变量、操作栈、动态链接、方法出口等信息,会抛出*Error、OutOfMemoryError异常。
1.3、本地方法栈(线程私有):和java虚拟机栈很像,只不过它是为执行Native方法服务,会抛出*Error、OutOfMemoryError异常。
1.4、堆(所有线程共享):唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存;它是垃圾收集器管理的主要区域,也称为GC堆,会抛出OutOfMemoryError异常。
1.5、方法区(所有线程共享):存储类信息、常量、静态变量等(运行时常量池属于方法区的一部分),会抛出OutOfMemoryError异常。
直接内存:在jdk1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。当本机内存不够时会抛出OutOfMemoryError异常。
对象访问举例
[java] view plaincopy
1. Object obj = new Object();
假设这句代码出现在方法体中:其中“Object obj”这段语法定义将反映到虚拟机栈的本地变量表中,“new Object();”这部分将反映到java堆中形成一块存储改对象的内存区域,其次在堆中必须还需包含到能查到该对象类型数据(对象类型、父类型、实现的接口、方法等)地址信息,这些数据存放在方法区中。
二、垃圾收集器与内存分配策略
垃圾收集(GC)主要针对堆、方法区进行内存回收,需要完成三件事:1.那些内存需要回收、2.什么时候回收、 3.如何回收
判断对象是否存活算法:引用计数算法、根搜索算法
1.引用计数算法:给对象添加一个引用计数器,引用该对象则计数器+1,取消引用则-1,如果计数器为零就认为对象已死,可回收(该方法无法解决对象间互相引用的问题)
[java] view plaincopy
1. objA.instance = objB; objB.instance = objA;
2.根搜索算法:通过一系列名为“GC Roots"的对象作为根节点,然后通过这些节点往下搜索,搜索经过的路径称为引用链,当一个对象到GC Roots没有任何引用链时,可以认为对象已死,可回收。在java语言中,可作为GC Roots的对象包括以下几个方面:
1.虚拟机栈中引用的对象
2.方法区静态属性引用的对象
3.方法区常量属性引用的对象
4.本地栈中引用的对象
引用:
在jdk1.2以前java中的引用定义:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用
在jdk1.2以后对引用的概念进行了扩充:分为强引用、软引用、弱引用、虚引用
强引用:代码中普遍存在的”Object obj = new Object():"这类引用
软引用:描述一些还有用,但并非必须的对象,提供SoftReference类来实现软引用
弱引用:也是描述非必须对象的,提供WeakReference类来实现
虚引用:最弱的一种引用关系,提供PhantomRefercence类来实现
方法区(永久代)垃圾收集主要回收两部分的内容:废弃常量和无用的类
废弃常量:没有再被引用的常量
废弃类:该类的所有实例都被回收、加载该类的ClassLoader已经被回收、该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法
垃圾收集算法:
1.标记-清除算法:算法分为标记和清除两个部分,先标记出所有需要回收的对象,然后统一回收被标记的对象。缺点:该算法标记和清除效率都不高,标记清除后会产生大量的不连续内存碎片,分配大对象时可能由于连续内存不够,而导致另一次垃圾回收操作。
2.复制算法:将内存分为大小相等的两块,开始使用其中的一块,在垃圾回收时将存活的对象复制到另一块内存,然后一次清理掉第一块内存,这种算法的缺点是可使用内存减少了一半。
3.标记-整理算法:标记部分和标记-清楚算法一样,然后将存活的对象向一端移动,最后清理掉在端边界外的对象。
4.分代收集算法:根据对象的存活周期将内存分为几块,一般将java堆分为新生代和老年代,在新生代中每次垃圾收集时都有大量对象死去,少量存活使用复制算法,在老年代中对象存活率高,采用标记-清理或标记-整理算法。
垃圾收集器:可以认为收集算法是内存回收的方法论,垃圾收集器则为具体实现。