天天看点

第一篇:自动内存管理机制第一章 Java内存区域与内存溢出异常第二章 垃圾收集器与内存分配策略

第一章 Java内存区域与内存溢出异常

一、概述:

        对于java程序员来说,在虚拟机的自动内存管理机制下,不再需要为每一个new操作去分配对应的delete/free操作,不再容易出现内存泄漏和内存溢出问题。不过,也正是因为java把内存管理交给了java虚拟机,一旦出现了内存泄漏和内存溢出的问题,问题就不是那么好解决了。

二、运行时数据区域:

        java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,不同的数据区域发挥着不同的作用。下面呈现运行时的数据区。

第一篇:自动内存管理机制第一章 Java内存区域与内存溢出异常第二章 垃圾收集器与内存分配策略

橙色部分:生命周期与线程同步,与线程同生同灭。

灰色部分:所有线程公用。

2.1 数据区详细介绍:

1、程序计数器:

         程序计数器的作用可以看作是 当前线程所执行的字节码的行号指示器,字节码解释器的作用就是通过改变这个字节码的值来选取下一条要执行的字节码指令。这样就可以实现分支、循环、跳转、异常处理、线程回复等基本职能。

        因为每个线程执行时涉及到线程之间的切换,所以每个线程当前的行号执行信息必须记录下来,这样就决定了每个线程都拥有一个程序计数器。各条线程之间的程序计数器互不影响、独立存储。

2、Java虚拟机栈:

         java虚拟机栈描述的是java 方法执行的内存模型:每个方法执行的时候都会创建一个栈帧,这个栈帧用于存储 局部变量、方法出口、操作栈、动态链接等。每一个方法从执行到完成的过程,就对应着每个方法从进入虚拟机栈到出栈的过程。

         在这个虚拟机规范中,对这个区域规定了两种异常状况:

                  (1)、如果线程请求的栈深度大于当前虚拟机栈的深度,将抛出StackOverflowError异常。

                  (2)、如果请求深度不够,当前虚拟机进行深度扩展后内存还是不够,就会抛出:OutOfMemoryError。

3、本地方法栈:

         本地方法栈与java虚拟机栈的作用相似,只是它是为虚拟机使用到的本地方法服务的。

         在这个虚拟机规范中,对这个区域规定了两种异常状况:(与虚拟机栈一样)

                  (1)、如果线程请求的栈深度大于当本地方法栈的深度,将抛出StackOverflowError异常。

                  (2)、如果请求深度不够,当前虚拟机进行深度扩展后内存还是不够,就会抛出:OutOfMemoryError。

4、方法区:

          它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

           方法区的特点:(1)、和堆一样可以不需要连续分配的物理存储空间。

                                       (2)、可以选择不对本区域产生的垃圾进行回收。

           方法区有一部分叫做:常量池。class文件中除了类的版本、字段、方法、接口等会存储这个信息。例如String类的intern()方法。

           异常:

                   当方法区无法满足内存分配要求时,OutOfMemoryError

5、堆:

           java堆是被所有内存共享的一块区域,此内存区域唯一存储的就是对象实例。

           java堆是垃圾收集器管理的主要区域。堆有可能在某些标准中继续划分,划分实质是为了更好地回收内存,或者更快地分配内存。

           java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的就是可以的。就像我们的磁盘一样,可以是固定的,也可以是扩展的。

       异常抛出:

                如果堆中实例分配不够用,并且堆也无法再进行扩展的时候,将会抛出OutOfMemoryError。

2.2 对象访问

       说明:一个最简单的对象的创建,都会涉及到内存中的栈、堆、方法区。

       例如:Object c = new Object();

Object c 这个首先会映射到内存的本地方法栈、new Object()毫无疑问映射到堆中。

因为java中的引用类型(reference)只是规定了一个指向对象的引用,却并没有规定怎么去定位,以访问java堆中的对象的具体位置,不同的虚拟机使用的方式是不一样的,主流的方法分为两种:

          (1)、通过句柄访问对象:

第一篇:自动内存管理机制第一章 Java内存区域与内存溢出异常第二章 垃圾收集器与内存分配策略

          (2)、通过指针访问对象:

第一篇:自动内存管理机制第一章 Java内存区域与内存溢出异常第二章 垃圾收集器与内存分配策略

2.3 java堆溢出

不管是哪个区发生了OOM问题,都可以在eclipse/myeclipse中安装 eclipse/myeclipseMemory Analyzer插件进行分析。 myeclipse安装memory analyzer: http://blog.csdn.net/yanghongchang_/article/details/7711911

java堆内存是发生OOM的最常见的情况,要分析 这个区的OOM问题,首先要搞清楚是发生了 内存泄漏 还是内存溢出。

     1、内存泄漏和内存溢出:

           内存泄漏:内存中的对象都死了。(使用工具查看内存泄漏对象到GC Roots的引用链)

           内存溢出:内存中的对象还都活着。(尝试减少内存消耗)

第二章 垃圾收集器与内存分配策略

一、概述:

垃圾回收(GC操作)需要完成的三种操作:

         (1)哪些内存需要回收

         (2)什么时候回收

         (3)怎样回收

1.1  哪些内存需要回收:

         根据上面数据区运行时的内存划分图,我们知道 程序计数器、虚拟机栈、本地方法栈都与线程的生命周期是相同的,所以说方法结束或者是线程结束后,这些内存区域也就跟着回收了。但是方法区和堆却不是这样,对方法区和堆来说,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序运行期的时候才知道程序需要创建的对象是哪些,这部分内存的分配和回收都是动态的,因为垃圾收集器与回收器关注的也就是这部分内容。

1.2  什么时候回收?

      1.2.1  在判断一个对象是否应该被回收的时候一般先要判断对象是活的还是死的。下面两个算法就是用来判断对象是活的还是死的。

       算法一:引用计数算法

               基本思想: 给每一个对象添加一个引用计数器,当有地方调用它时就加一,引用失效时就减一。任何时刻计数器都为0的对象就是死了的。

               缺陷:很难解决对象之间的循环引用。

       算法二:根搜索算法(在主流语言java、c#等都是使用这个算法)

              基本思想:当一个对象到GC Roots没有任何引用链相连(从图论来说就是从GC Roots到这个对象不可达时),就证明次对象是不可用的。

第一篇:自动内存管理机制第一章 Java内存区域与内存溢出异常第二章 垃圾收集器与内存分配策略
第一篇:自动内存管理机制第一章 Java内存区域与内存溢出异常第二章 垃圾收集器与内存分配策略

   如上图所示,object5、object6、object7是可以回收的对象。

   可以作为GC Roots对象的有以下几种:

             (1) 虚拟机栈

             (2)方法区中的类静态属性引用的对象

             (3)方法区中的常量引用的对象

             (4)本地方法栈中的JNI的引用的对象。

1.2.2  关于引用的补充(这个也是蛮重要的知识点,android网络图片批量下载显示的时候用过)

JDK 1.2之前:

         java中的引用定义是这样的:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。但是这样一种情况是很常见的:当内存足够时,对象是可以保存在内存中的,但是当内存进行GC(垃圾回收)之后,内存还是很紧张,这个时候抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。

JDK 1.2之后:

         java对引用的概念进行了扩展,将引用分为强引用、软引用、弱引用、虚引用。这四种引用强度依次递减。

         强引用:是最普遍存在的,例如Object c= new Object(),只要强引用一直存在,那垃圾回收器永远都不会回收掉这个对象。

         软引用:软引用并不是必须的对象。对于软引用关联着的对象,在报出内存溢出异常之前,垃圾回收器将会把这些对象列在回收范围之内进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出错误。

         弱引用:弱引用也是用来描述非必需对象的,被弱引用引用的对象只能生存道下一次垃圾收集发生之前。当垃圾收集器工作时,无论内存是否够用,都会回收掉只被弱引用关联的对象。

         虚引用:为一个对象设置虚引用关联的唯一目的就是希望这个对象被垃圾回收时收到一个系统通知。

上面的补充明显让我们知道JDK 1.2之后一个对象的生存和死亡并没有那么简单。(也就是说不止判断一次)

1.2.3  真正判断对象是否是生存还是死亡。(前提:使用算法二:根搜索算法)

要真正宣告一个对象死亡,至少要经历两次标记过程,过程如下所示:

  (1)如果对象在 根搜索之后没有发现与GC Roots相连接的引用链,进入第一次标记并进行筛选。

  (2)筛选条件:该对象有没有必要执行finalize()方法,当对象没有覆盖finalize方法、或是对象的finalize方法已经被虚拟机调用过,则筛选结果为没必要执行。

  (3)如果筛选结果finanize()方法标记为有必要执行,这些对象被放在 一个F-Queue队列中,之后在虚拟机自己创建的一个线程中去执行。如果一个对象的finalize()方法在队列中长时间执行或是循环执行,因此来了第二次标记。

  (4)如果对象要在第二次标记中成功拯救自己------只要重新将自己链接到 GC Roots链中即可。具体方法例如:把自己赋值给某个变量或是对象的成员对象。

 注意:任何一个对象的finalize方法只能被系统自动调用一次,如果面临下一次回收,它的finalize方法不会再被执行。在java中,这些都不用特别去做,java中的try-finally就可以很好地利用这个原理。

1.3 如何回收垃圾:(具体我们找书就可以了)

 1.3.1  垃圾收集算法:

           (1)标记-清除算法

                     原理:首先标记出所有要收集的对象,然后统一清除掉被标记的对象。(会产生空间碎片)

           (2)复制算法

                     原理:首先将内存分为两部分,第一部分用完之后,将第一部分活着的对象复制到第二部分,然后统一清除掉第一部分的对象。

           (3)标记-整理算法

                      原理:首先标记出所有要收集的对象,然后将所有存活的对象向一端移动,顺序排列,接着直接清除掉端边界以外的内存。(不会产生空间碎片)

           (4)分代收集算法

                      当代虚拟机都采用分代收集算法。

                       原理:根据对象的存活周期的不同将内存划分为几块,一般是吧java堆划分为 新生代和 老生代。然后对不同的代采用不同的垃圾回收算法。一般:新生代采用复制算法,老生代采用标记-清楚 或是标记-整理算法。

 1.3.2  垃圾收集器:(因为不同厂商提供的垃圾收集器不同,至今有太多版本,但没有一种是达到所有人的统一的)

            (1)G1收集器等(标记-整理算法)

                        将java堆划分为多个固定大小的块,先收集垃圾产生最多的区域(优先级),保证效率。

            (2)Serial收集器:(采用标记-整理算法)

                      特点:是一个单线程收集器,只会使用一个CPU或是一个垃圾收集线程去回收垃圾。

                                  执行时必须暂停掉其他所有的工作线程,知道它收集结束。

            (3)CMS收集器等(采用 标记-清除算法)

                      特点:是一种以获取最短停顿时间为目标的收集器。

             and so on...

继续阅读