• Java进阶3. 内存回收机制


    Java进阶3. 内存回收机制 20131029

    前言:

             学过C++的都知道,C++中内存需要程序员自己维护。说道这里,很多开发的同学就感觉很痛苦,当他转向Java的时候,就会说你看Java多好啊,程序员不用自己管理堆的内存了,我们终于可以尽情挥霍了,但实际上真的是这个样子吗?Java真的有这么好吗?其实Java回收垃圾是考的JVM后台的一条线程来运行的,这条线程会不定时的启动来回收垃圾,这样在表面上看来器省去了我们程序员的很多工作,但是后台启动这样的线程去回收垃圾就不耗时吗?不会占用资源吗?其实JVM的垃圾回收线程是十分的消耗资源的,JVM可以回收垃圾,但是不是允许我们程序员尽情挥霍的。

             这里我们学习Java内存回收机制,这样我们才能够更好的在程序中使用Java语言,让程序更加高效。

             本章主要内容:Java引用的功能和意义,Java内存泄露知识、Java垃圾回收机制、开发中如何注重开发过程中的。本章内容结合Java的内存分配知识,这一章主要介绍的是对应内存的回收。内存的分配加上内存的回收构成了Java的内存管理。

             Java内存回收包括:Java何时决定回收一个Java对象所占用的内存空间,java会不会漏掉回收某些对象从而造成内存的泄露;java回收对象内存的细节,JVM能否对不同的Java对象占用的内存区域区分对待回收,常见的而垃圾回收机制的实现细节是怎样的。

             Java开发者了解正真的JVM机制之后,开发出来的程序将会更加高效,可以从分的利用有限的内存空间,更快的释放那些没有用的Java对象所占有的内存,造成Java内存的泄露。

    1.Java引用的种类

             Java面向对象的编程语言,一个Java程序需要创建大量的对象,在调用这些对象的属性和方法操作他们。程序员要使用new关键字创建Java对象,分配内存空间,内存在堆中分配;当1个Java对象失去引用的时候,JVM的垃圾回收机制会自动清除他们,回收他们所占用的内存空间。初级程序员不会关心内存的分配和回收,造成了JVM的负担加重从而使程序的效率比较低。

             Java对象创建出来,JVM内存回收机制会一直监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等等。当垃圾回收机制监控到某个对象不再被引用变量引用的时候,就会被垃圾回收机制列为回收内存的目标。

             基本上可以将JVM内存的对象引用理解成一种有向图,把引用变量、对象当成图的定点,将引用关系当成图的有向边,有向边从引用端指向被引用的对象,可以将线程当做图的起始点。

    class Node{

        Node next;

        String name;

        public Node(String name){

           this.name = name;

        }

       

    }

    public class TestMain {

        public static void main(String[] args){

           Node n1 = new Node("yang");

           Node n2 = new Node("teng");

           Node n3 = new Node("fei");

           n1.next= n2;

           n3 = n2;

           n2 = null;

        }

    }

    这样在实际的内存中,n3在程序中是不可达的,这个时候,就会被JVM回收。但是这一种根据有向图的状态确定垃圾回收机制,精准度比较高,但是效率非常的低效:

        当一个对象在内存中运行的时候分为以下几种状态,可以将他们所有的状态分为如下几种:

        可达状态:当一个对象被创建,有一个以上的引用变量引用他,有向图可以从线程的起始点找到该对象,那么就是出于可达的状态,程序中可以通过引用变量调用对象的函数和变量。

        可恢复状态:如果程序总的某个对象不再会有任何的引用变量引用他,他将先进入可以恢复的状态,此时从有向图的开始点不能够到该对象。这种状态下,系统的垃圾回收机制准备回收该对象的内存。在回收内存之前,系统会首先调用可恢复状态的对象的finalize方法进行资源的清理,如果系统调用finalize方法会重新让引用变量引用该对象,那么这个对象可以编程可达的状态;否则对象直接进入不可达状态;

        不可达状态:当对象的所有关联状态都被切断,且系统调用finalize方法之后,该对象依然是不可以达的,这个对象将会彻底的失去引用,最后编程不可达状态,这个时候系统的JVM就会真正的回收该对象的内存空间。

       

        Java的几种引用状态: SoftReference WeakReference, PhantomReference.

        强引用

        最常见的方式,程序中创建一个对象,并且把这个对象赋值给一个引用变量的时候,就是强引用。当对象被一个个的强引用变量引用的时候,他是处于可达状态,他是不会被JVM的垃圾回收机制回收的。强引用是永远不会被JVM回收的,即使内存资源在紧张,甚至该对象在以后都不实用,都不会释放内存空间,因此强引用是造成内存泄露最主要的原因之一。

        SoftReference 软引用

        通过SoftReference实现,当一个对象只有软引用的话,他就可能被JVM回收掉。当系统内存足够的时候,不会别回收,但是只有系统内存空间不足的话,就会被回收。软引用是强引用很好的替代,这样当内存资源不足的话,就会被回收掉。

    class Node{

        String name;

        int age;

        public Node(String name, int age){

           this.name = name;

           this.age = age;

        }

        public String toString(){

           return this.name+ "," + this.age;

        }

    }

    public class TestMain {

        public static void main(String[] args) throws ClassNotFoundException {

           SoftReference<Node>[] nodes = new SoftReference[1000];

           for(int i = 0 ; i< 1000; i++){

               nodes[i] = new SoftReference<Node>(new Node("yang"+i,i));

           }

           System.out.println(nodes[2].get());

           System.gc();

           System.runFinalization();

           System.out.println(nodes[2].get());

        }

    }

    弱引用

    弱引用和软引用有点类似,但是若引用对象的生命周期是分段,只要系统运行垃圾回收机制,不管内存空间是否足够,都会将他们回收掉,使用WeakReference实现

    String str = "yangtengfei";

    WeakReference<String> wr =  new WeakReference<String>(str);

    str = null;

    System.out.println(wr.get());

     

    System.gc();System.runFinalization();

    System.out.print(wr.get());

    虚引用

       软引用和弱引用可以单独使用,但是虚引用是不可以直接使用,虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序通过检查与虚引用关联的引用队列是否有已经包含的虚引用,从而了解虚引用的对象是否即将被回收。

    2.Java内存泄露知识

             虽然Java垃圾回收机制会自动的回收那些无用的对象占用的内存空间,但是也会出现内存泄露的情况。什么是内存泄露呢:程序运行的过程中会不断的分配内存空间,那些不在使用的内存空间应该被及时回收,从而保证系统可以再次的使用这些内存,如果存在无用的内存没有被回收回来的话,就会出现内存泄露。

             对于C++来说,对象占用的空间是必须由程序员自己回收,否则就会出现内存泄露;对于Java来说,所有不可达的对象都由垃圾回收机制负责回收。但是对于Java程序,如果存在一些对象处于可达状态,但是程序中以后永远不会访问该对象的话,但是JVM的垃圾回收机制是不会回收他们的,就会出现了内存泄露的情况。对于Java中的情况,对象是可达的,但是对象不会在被访问,C++中程序员是可以手动释放该内存空间,但是Java不能够回收。

             我们自己实现集合元素如ArrayList删除指定的元素,但是该对象在内存中依然是可达的,所以需要我们将该位置的元素置为null才可以是该对象变得不可达,否则就会出现内存泄露的情况。

    3.垃圾回收机制

             垃圾回收机制主要完成两件事情:跟踪并且监控每一个Java对象,当某个对象处于不可达状态的时候,回收该对象占用的内存空间; 清理内存分配,回收过程中产生的碎片。垃圾回收机制依靠的是后台的线程,后台线程实现这两个部分的工作量是非常大的,一次回收垃圾算法成为限制Java程序运行效率的重要因素。实现一个高效的垃圾回收算法,保证垃圾回收运的快速运行,避免内存的分配和回收成为限制程序性能的瓶颈。

    3.1常见的垃圾回收算法

             实际上JVM是不可能监控每一个Java对象的状态,因此当java对象失去引用的时候,不可能被立即回收,只有等到垃圾回收机制运行的时候才可以。常见的垃圾回收机制设计:

             串行回收算法和并行回收算法:串行回收不管有多少个CPU,始终只有一个CPU来执行垃圾回收操作,并行回收就是把整个垃圾回收的工作拆分成多个部分每一个部分由一个CPU负责,从而让多个CPU并行回收,回收的效率很高,但是负责度比较高,还有副作用,会出现内部碎片。

             并发执行和应用程序停止(Stop-The-World):Stop-The-World的垃圾回收机制在执行垃圾回收的时候,会导致应用程序的赞同。并发虽然不会导致应用程序的暂停,但由于并发执行垃圾回收需要解决和应用程序之间的执行冲突,因此并发开销比StopTheWorld的开销更大。

             压缩(Compacting)和不压缩(Non-Compacting)复制:为了减少内存碎片,支持压缩的垃圾回收器把所有的有效对象搬迁到一起,然后将之前战友的内存全部释放。不压缩的只会回收掉内存,但是这样造成内存的大量碎片。不压缩的回收机制回收内存更快,但是分配内存的时候比较慢,而且无法解决内存的大量碎片问题;复制方式将所有的可达独享赋值到另一块相同的内存中,之后回收掉全部的内存空间,虽然不会产生内存碎片,但是需要赋值数据和二外的内存空间;

             不管采用哪一种回收机制,都是有好有坏的,所以不同的JVM实现,有着不同的垃圾回收机制。现代的垃圾回收器采用的是分代方式来采取不同的回收设计,分代的基本思想是根据对象的生存时间长短,分成三代:

             Young Old Permanent,垃圾回收机制会根据不同代的特点实现采取不同的回收算法。

             堆内存的分代回收:绝大多数的对象不会被长时间的引用,这些对象在不Young期间就会被回收掉;很老的对象和很新的对象之间很少存在相互引用的情况。所以对于Young代的对象而言,大部分的对象都会很快的进入不可达状态,只有很少的独享能够熬到垃圾回收机制运行的时候,而垃圾回收机制只需要保留Young代中可达状态的对象,如果采用复制算法,只需要少量的赋值成本,因此大部分的垃圾回收期对于Young代都会采用复制算法。

             Young: 只需要遍历那些处于可达状态的对象,而这些对象的数量是比较少的,赋值成本不会很大,所有使用这一个算法比价好。Young代由一个Eden区域和两个Survivor区构成。绝大多数对象都会先分配到Eden区中,有一些大的对象被直接分配在Old中,Survivor区保留的是在Young经历了一次垃圾回收之后保存的对象,另一个Survivor是空的,用于在下次回收垃圾的时候保存Young代中的对象。每一次垃圾回收就是将Eden和第一个Survivor中可达的对象复制到第二个Survivor区域中,然后清空Eden和第一个Survivor区域。

             Old代:如果Young中的对象经历多次垃圾回收机制都没有别回收,而且处于可达的状态,垃圾回收机制就会将它转移到Old代。随着时间的基类Old代中的对象会越来越多,因此Old的空间是比Young的大的,Old代的垃圾回收特征如下:

             Old垃圾回收的执行平率不必太高,因为很少会有对象死掉;每一次对于Old代执行垃圾回收都需要更长的时间来完成。

             所以对于Old代通常会使用标记压缩算法,这种算法可以避免复制Old代的大量独享,而且不会产生大量的内存碎片。

             Permanent:主要用于装在Class,方法等信息,默认是64M,垃圾会回收机制通常不会回收Permanent的对象,对于那些需要加载很多的类的服务器程序,往往需要加大Permanent代的内存空间,否则可能会因为内存不足导致程序终止。

    4.内存管理的小技巧

             了解了内存的垃圾回收机制,就是为了在程序中更加高效的使用内存空间,我们写程序的时候,很少会注意到自己的内存使用情况,到那时这也正是我们自己写程序低效的原因。下面讲解一下写程序的技巧

             尽量使用直接量,针对String和其他的额基本数据类型,尽量使用直接量,而不要使用new关键字在堆中新建一个对象。

             比如String str = “name”;这样创建一个字符串,同时在JVM字符串缓冲池中还会缓存这个字符串,但是如果使用new关键字,除了上面的开销之外还有在堆中的一个char数组,保存字符串的内容。

             使用StringBuffer和StringBuilder进行字符串连接,因为在Java中字符串是不可以改变的,所以建议使用StringBuffer和StringBuilder。

             尽早释放无用对象的引用:大部分的收,方法内的局部变量所引用的对象会随着方法的结束而变成垃圾,因为局部变量的生存期限很短,该方法结束的时候,局部变量结束了声明周期,因此无需将局部变量置为null。但是程序中是可以的。

             尽量少使用静态变量:如果一个对象被静态对象引用的话,那么垃圾回收机制是不会回收对象的内存空间的。在对应的class不被卸载的情况之下,静态变量一直存在,对应的对象就会常驻内存空间,直到程序结束。

             避免在循环中创建对象,这样代价是非常的大的,尽量在循环外部创建之后,在循环的时候进行修改。

             缓存经常使用的对象:将对象使用对象缓冲池保存起来的话,当使用的时候,直接从缓冲对象池中拿出来即可,典型的就是数据库连接池

             尽量不要使用finalize方法:垃圾回之前会首先调用对象的finalize方法执行资源的清理工作,垃圾回收已经非常耗费资源,如果在finalize回收资源,导致垃圾回收更加耗费资源。

             考虑使用SoftReference,因为当内存足够的时候,他等同于普通的引用,但是当内存资源不足的时候,就会牺牲自己,释放软引用的对象空间。

    追梦的飞飞

    于广州中山大学图书馆 20131102

    HomePage: http://yangtengfei.duapp.com

  • 相关阅读:
    leetcode 122. Best Time to Buy and Sell Stock II
    leetcode 121. Best Time to Buy and Sell Stock
    python 集合(set)和字典(dictionary)的用法解析
    leetcode 53. Maximum Subarray
    leetcode 202. Happy Number
    leetcode 136.Single Number
    leetcode 703. Kth Largest Element in a Stream & c++ priority_queue & minHeap/maxHeap
    [leetcode]1379. Find a Corresponding Node of a Binary Tree in a Clone of That Tree
    正则表达式
    十种排序算法
  • 原文地址:https://www.cnblogs.com/hbhzsysutengfei/p/3409558.html
Copyright © 2020-2023  润新知