内存管理技术

任何语言都会涉及到内存的管理和使用,很多语言要求开发人员自己进行所有内存的管理工作,如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行的结果,是因为当线程运行结束后,成为死线程而被回收。结果显示:

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

时间: 2024-10-08 19:27:04

内存管理技术的相关文章

iOS开发ARC内存管理技术要点

本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验.详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:) 本文的主要内容: ARC的本质 ARC的开启与关闭 ARC的修饰符 ARC与Block ARC与Toll-Free Bridging 技术交流新QQ群:41

(转)iOS开发ARC内存管理技术要点

转自:http://www.cnblogs.com/flyFreeZn/p/4264220.html 本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验.详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:) 本文的主要内容: ARC的本质 ARC的开启与关闭 A

[转载]linux段页式内存管理技术

原始博客地址: http://blog.csdn.net/qq_26626709/article/details/52742470 一.概述 1.虚拟地址空间 内存是通过指针寻址的,因而CPU的字长决定了CPU所能管理的地址空间的大小,该地址空间就被称为虚拟地址空间,因此32位CPU的虚拟地址空间大小为4G,这和实际的物理内存数量无关.Linux内核将虚拟地址空间分成了两部分: 一部分是用户进程可用的,这部分地址是地址空间的低地址部分,从0到TASK_SIZE,称为用户空间 一部分是由内核保留使

详谈内存管理技术(二)、内存池

嗯,这篇讲可用的多线程内存池. 零.上期彩蛋:不要重载全局new 或许,是一次很不愉快的经历,所以在会有这么一个"认识".反正,大概就是:即使你足够聪明,也不要自作聪明:在这就是不要重载全局new,无论你有着怎样的目的和智商.因为: class XXX{ public: XXX* createInstance(); }; 这是一个不对称的接口:只告诉了我们如何创建一个[堆]对象,但是释放呢??! 很无奈,只能假设其使用全局默认的delete来删除(除此之外,没有其他选择):这时,我为了

详谈内存管理技术(三)、线程模型

一.为什么需要线程模型? 记得几年前,自己写高精度算法时,因为需要一个线程安全的后台(用来保存一些信息),便手动写了一个线程本地存储(TLS)(虽然,后来因为改了计算模型,弃用了):再后来,因为内存池的需要,亦手动再写了一个线程本地存储(TLS):很好,这样一来同一个库里,竟然有两套相同的TLS:于是,意识到了什么地方不对. 不只是代码重复的问题(其实重复的不多):更重要的是,TLS应该是线程模型本身,来提供的功能:但,很可惜,C++并没有这样的东西(线程模型).(PS:我无视了系统提供的TLS

Linux用户态和内核态内存管理技术

通常程序访问的地址都是虚拟地址,用32位操作系统来讲,访问的地址空间为4G,linux将4G分为两部分.如图1所示,其中0~3G为用户空间,3~4G为内核空间.通过MMU这两部分空间都可以访问到实际的物理内存. 进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G *进程通过系统调用进入内核态 *每个进程虚拟空间的3G~4G部分是相同的 *进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变 图1 1 虚拟地址和物理地址之间的映射关系 页作为基本的映射单元,一页的大小一般为4K

指针与数组的对比(——选自:C++内存管理技术内幕)

数组: 数组要么是在静态存储区上创建(如全局数组),要么是在栈上创建的.数组名代表着 段连续的内存,其地址和容量在生命周期内是不会改变的,而只能改变其数组内容. 指针: 指针是一种指针类型的变量,变量为一个内存的首地址,可以改变的,所以一般可以用指针 来指向动态开辟的内存. 下面以字符串为例来比较数组和指针: 1. 修改内容 char a[] = “ hello” ;    a[0] = ‘ X’ ;    cout << a << endl;//输出的是Xello    char

iOS开发中的ARC内存管理de技术要点

本文旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验.详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:) 本文的主要内容: ARC的本质 ARC的开启与关闭 ARC的修饰符 ARC与Block ARC与Toll-Free Bridging ARC的本质 ARC是编译器(时)特性,而不是运行时

linux内存管理

一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程序可调用它.假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段.     2) 数据段:存放已初始化的全局变量.静态变量(包括全局和局部的).常量.static全局变量和static函数只能在当前文件中被调用.     3) 未初始化数据区(uninitializeddata s