zoukankan      html  css  js  c++  java
  • 内存管理技术

    任何语言都会涉及到内存的管理和使用,很多语言要求开发人员自己进行所有内存的管理工作,如c++等。而内存管理要求的技术难度很大,很多开发人员不能很好地完成,同时也成为意向沉重的负担。

    java则不同,其为内存管理提供的一套完整的解决方案——垃圾收集机制,大大减轻了开发人员编写内存管理代码的负担,减少了出错的机会,简化了开发。

    一、程序中的“垃圾“”是什么

    所谓垃圾,是指在内存中不再有用的对象,其占用的内存应该释放。将不再有用的对象清除出内存的工作称为“垃圾收集”。

    1、对象成为“垃圾”的条件

    (1)对于非线程对象来说,当所有的活动线程都不可能访问到该对象时,该对象便成为“垃圾”。

    (2)对于线程对象来说,除了满足第一条标准之外,还要求此线程本身已经死亡或者还处于新建状态。

    2、单个对象的情况

    对于非线程的耽搁情况来说,使其成为垃圾的方法很简单,只要将指向该对象的所有引用不再指向该对象即可。

    (1)将指向该对象的引用设置为null值。

    //创建字符串对象,并将引用s指向该对象
    String s=new String();
    //将引用s 的值设为null值
    s=null;

    (2)将引用指向别的对象。

    //创建字符串对象,并将引用s指向该对象
    String s=new String();
    //将引用s指向新的对象
    s=new String();

    (3)随着语句块或者方法的退出局部引用消亡。

    //创建一个函数,当函数执行完之后,局部对象,成为垃圾被回收
    public void fun(){
    String s=new String("xiao");
    System.out.println(s);
    }

    3、多个对象的孤岛情况

    有引用指向的一样可能是垃圾,关键看这些对象能不能被活动线程访问到。

    复制代码
    class Island{
    
    //引用类型为自己的成员变量
    public Island brother;
    }
    
    //创建3个Island类的对象
    Island i1 =new Island();
    Island i2 =new Island();
    Island i3 =new Island();
    //分别为三个对象中的成员变量赋值
    i1.brother=i2;
    i2.brother=i3;
    i3.brother=i1;
    
    //将Island的三个引用分别设置为null值
    i1=null;
    i2=null;
    i3=null;
    复制代码

    二、“垃圾”收集器

    让对象成为垃圾的工作是由来发人员来完成的,而java中清理垃圾的工作是由垃圾收集器自动完成,不需要开发人员做很多的工作。

    1、垃圾收集器的基本介绍

    开发人员需要做的工作仅仅是将不需要的对象根据规则“标识”为垃圾,而垃圾收集器何时收集垃圾,如何收集垃圾都不需要开发人员关心。其实,垃圾收集器就是一个后台守护线程,在内存充足的情况下,其优先级很低,一般不会出来运行,当垃圾充斥着内存,严重影响程序执行时,其优先级会提高,并出来运行收集垃圾,清理内存。正是因为如此,垃圾收集器的运行时间是没有保障的。

    2、申请垃圾收集器的运行

    垃圾收集器的运行是由系统自动决定的,但是这并不是说开发人员一点都不能干预,开发人员可以通过调用特定的方法申请垃圾收集器运行。当然,在收到申请后,如果系统不是很繁忙,垃圾收集器一般都会运行,但这也没有保障。

    Runtime类

    Runtime类的对象,通过自身的getRuntime()函数获得。

                                                                             Runtime类的几个常用的方法

    方法签名 功能
    public static Runtime getRuntime() 该方法将返回一个当前运行程序相关的Runtime类的对象,相当于一个对象工厂。
    public void gc() 申请垃圾收集器运行
    public long totalMemory() 该方法将返回当前JVM使用的总内存量,单位为字节
    public long freeMemory() 该方法将返回当前JVM中可使用的内存量,单位为字节

    代码如下:

    复制代码
     1 public class javaTest {
     2    
     3     public static void main(String[] args) throws InterruptedException  {
     4         Runtime rt=Runtime.getRuntime();
     5         rt.gc();
     6         Thread.sleep(100);
     7         System.out.println("没有创建对象之前的剩余内存:"+rt.freeMemory());
     8         for(int i=0;i<100000000;i++){
     9             new String("小帅哥");
    10         }
    11         Thread.sleep(100);
    12         System.out.println("创建100个字符串对象后剩余的内存:"+rt.freeMemory());
    13         rt.gc();
    14         Thread.sleep(100);
    15         System.out.println("申请垃圾收集器运行后的剩余内存:"+rt.freeMemory());
    16     }
    17 }
    复制代码

    运行结果:

     1 没有创建对象之前的剩余内存:63609344 2 创建100个字符串对象后剩余的内存:58639088 3 申请垃圾收集器运行后的剩余内存:63609272 

     三、如何收集“垃圾”

    对象作为垃圾清理出内存之前,可能需要进行一些扫尾的工作,在java中,这些扫尾工作的代码可以编写在被收集对象的finalize()方法中。(java中finalize()函数类似于c++中的析构函数~)

    1、finalize重写

    finalize方法来自Object()类,因此,每个类都有此方法。在一个对象被作为垃圾收集之前,垃圾收集器会首先调用垃圾对象的finalize()方法,然后再清除垃圾对象。

    protected void finalize() throws Throwable

    (1)由该方法的访问限制可以看出,因为所有的类都直接间接继承Object类,所以所有的类都可以具有该方法。

    (2)对象核实被进行垃圾收集是没有保障的,有可能在整个应用程序裕兴的生命周期中一直没有进行垃圾回收,因此需要保证执行的特定的处理代码不应编写在此方法中。

    (3)重写该方法时一般不但要编写自己类特定的处理代码,还应该使用“super.finalize();”调用父类的finalize()方法。因为自己特定类的对象也是一个父类的对象,父类对象的清理代码也应该执行,除非有意识的修改父类的清理对象才不需要调用父类的finalize()方法。

    (4)如果没有重写该方法,则在垃圾收集时会调用父类的方法,直至追溯到Object类的finalize()方法。

    复制代码
     1 //父类
     2 class People{
     3 
     4     @Override
     5     protected void finalize() throws Throwable {
     6         super.finalize();
     7         System.out.println("这是people类的finalize()方法!");
     8     }    
     9 }
    10 //子类
    11 class Man extends People{
    12     @Override
    13     protected void finalize() throws Throwable {
    14         super.finalize();
    15         System.out.println("这是Man类的finalize()方法!");
    16     }    
    17 }
    18 public class javaTest {
    19    
    20     public static void main(String[] args) throws InterruptedException {
    21         //获得Runtime类对象,用于后面的申请垃圾收集器调用方法的
    22         Runtime rt=Runtime.getRuntime();
    23         //创建People对象
    24          Man m=new Man();
    25         //将对象引用指向null,让People类的对象成为垃圾
    26         m=null;
    27         //申请垃圾收集器运行
    28         rt.gc();
    29         Thread.sleep(100);            
    30     }
    31 }
    复制代码

    运行结果:

     1 这是people类的finalize()方法! 2 这是Man类的finalize()方法! 

    2、finalize安全问题

    对象被执行垃圾收集前会调用其finalize()方法,如果仅仅这样,就可能带来安全问题。如果在finalize()方法中编写一些恶意的代码,在每次执行finalize()方法时使自己的对象不满足垃圾的条件,就可以组织垃圾收集,产生恶意的常驻对象。为了避免这种情况,在java中规定,一个对象的生命周期中的finalize()方法最多被执行一次。也就是说,若第一次执行垃圾收集时执行此方法阻止了垃圾收集,第二次再执行垃圾收集时不会再执行此方法,直接清除垃圾对象。

    复制代码
     1 class Man  {
     2     static public Man p;
     3     @Override
     4     protected void finalize() throws Throwable {
     5         super.finalize();
     6               p=this;
     7      System.out.println("这是Man类的finalize()方法!");
     8      if(p!=null){
     9         p=null;
    10         System.gc();
    11         Thread.sleep(100);
    12         }
    13         
    14     }    
    15 }
    16 public class javaTest {
    17    
    18     public static void main(String[] args) throws InterruptedException {
    19         //获得Runtime类对象,用于后面的申请垃圾收集器调用方法的
    20         Runtime rt=Runtime.getRuntime();
    21         //创建People对象
    22          Man m=new Man();
    23          Man m2=new Man();
    24         //将对象引用建立引用循环
    25          m=null;
    26         
    27         //申请垃圾收集器运行
    28         rt.gc();
    29         Thread.sleep(100);
    30 
    31     }
    32 }
    复制代码

    运行结果:

    这是Man类的finalize()方法!

    当一个对象,在其生命周期中只能执行一次finalize()函数。

    四、非线程“垃圾”

    通过下面一段代码,让大家了解一下非线程“垃圾”回收:

    复制代码
     1 class Man  {
     2 
     3     private String name;
     4     //构造函数
     5     public Man(String name) {
     6         this.name=name;
     7     }
     8     @Override
     9     protected void finalize() throws Throwable {
    10         super.finalize();            
    11      System.out.println(this.name+"对象被当作垃圾回收了!");        
    12     }    
    13 }
    14 public class javaTest {
    15    
    16     public static void main(String[] args) throws InterruptedException {
    17         //获得Runtime类对象,用于后面的申请垃圾收集器调用方法的
    18         Runtime rt=Runtime.getRuntime();
    19         //创建People对象
    20          Man m=new Man("帅哥1");
    21          Man m2=new Man("美女2");
    22         //将对象引用建立引用循环
    23          m=null;
    24          m2=null;        
    25         //申请垃圾收集器运行
    26         rt.gc();
    27         Thread.sleep(100);
    28     }
    29 }
    复制代码

    运行结果:

     1 美女2对象被当作垃圾回收了!

    2 帅哥1对象被当作垃圾回收了! 

    分析:特别注意“垃圾”的回收顺序,类似c++中的析构函数的执行顺序,先执行后被定义为垃圾的对象,是一个反的顺序。

    五、线程“垃圾”

    复制代码
     1 class Man extends Thread {
     2 
     3     private String name;
     4     //构造函数
     5     public Man(String name) {
     6         this.name=name;
     7     }
     8     @Override
     9     public void run() {
    10         // TODO Auto-generated method stub
    11         super.run();
    12         System.out.println("开始执行"+this.name+"线程!");
    13         try {
    14             Thread.sleep(1000);
    15         } catch (InterruptedException e) {
    16             // TODO Auto-generated catch block
    17             e.printStackTrace();
    18         }
    19         System.out.println("即将执行完线程"+this.name);
    20     }
    21     @Override
    22     protected void finalize() throws Throwable {
    23         super.finalize();            
    24      System.out.println(this.name+"对象被当作垃圾回收了!");        
    25     }    
    26 }
    27 public class javaTest {
    28    
    29     public static void main(String[] args) throws InterruptedException {
    30         //获得Runtime类对象,用于后面的申请垃圾收集器调用方法的
    31         Runtime rt=Runtime.getRuntime();
    32         //创建People对象
    33          Man m=new Man("帅哥");
    34          Man m2=new Man("美女");
    35          Man m3=new Man("人妖");
    36          m.start();
    37         //将对象引用建立引用循环
    38          m=null;        
    39         //对无引用但活着的线程,申请垃圾收集器运行
    40          System.out.println("**********对无引用但活着的线程进行垃圾回收**********");
    41         rt.gc();
    42         Thread.sleep(2000);
    43         
    44         m2=null;
    45         //对无引用但处于新建状态的线程,申请垃圾收集器运行
    46          System.out.println("**********对无引用但但处于新建状态的线程进行垃圾回收**********");
    47         rt.gc();
    48         Thread.sleep(2000);
    49         
    50         //执行线程m3(人妖)
    51         m3.start();
    52         //设置等待时间,为了使线程执行完成为死亡的线程
    53         Thread.sleep(1500);
    54         m3=null;
    55         //对无引用死亡的线程,申请垃圾收集器运行
    56          System.out.println("**********对无引用死亡的线程进行垃圾回收**********");
    57         rt.gc();
    58         Thread.sleep(100);    
    59     }
    60 }
    复制代码

    运行结果:

    复制代码
     1 **********对无引用但活着的线程进行垃圾回收**********
     2 开始执行帅哥线程!
     3 即将执行完线程帅哥
     4 **********对无引用但但处于新建状态的线程进行垃圾回收**********
     5 帅哥对象被当作垃圾回收了!
     6 美女对象被当作垃圾回收了!
     7 开始执行人妖线程!
     8 即将执行完线程人妖
     9 **********对无引用死亡的线程进行垃圾回收**********
    10 人妖对象被当作垃圾回收了!
    复制代码

    结果分析:第5行的结果,是因为当线程运行结束后,成为死线程而被回收。结果显示:

    对于线程对象来说,当所有的活动线程都不可能访问到该对象时,还要求此线程本身已经死亡或者还处于新建状态,则才会进行垃圾收集。活线程不会被垃圾收集~

  • 相关阅读:
    数据处理——时间数据处理
    数据处理——异常值检测
    数据处理——缺失值处理
    数据分析——数据校验
    Python之Pandas知识点
    Python基础知识之疑点难点
    Python习题(第3课)
    跳一跳小外挂(附完整代码)
    《软件工程》实训报告
    用户使用手册与测试报告(团队作业)
  • 原文地址:https://www.cnblogs.com/yulei126/p/6756666.html
Copyright © 2011-2022 走看看