Java的结构之美【2】——销毁对象

先来看一段代码:

import java.util.Arrays;
import java.util.EmptyStackException;

/**
 * 2014年6月28日09:31:59
 * @author 阳光小强
 *
 */
public class Stack {
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT_INITAL_CAPACITY = 15;
	
	public Stack(){
		elements = new Object[DEFAULT_INITAL_CAPACITY];
	}
	
	public void push(Object obj){
		ensureCapacity();
		elements[size++] = obj;
	}
	
	public Object pop(){
		if(size == 0){
			throw new EmptyStackException();
		}
		return elements[--size];
	}
	
	/**
	 * 如果长度超出了默认长度则加倍
	 */
	private void ensureCapacity(){
		if(elements.length == size){
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
	}
}
这段程序表面上看是没有任何错误的,但是它隐藏着一个“内存泄露”问题,随然每次都有pop()从栈里弹出对象,但是栈中的对象还是被引用着,所以不能够及时释放。将上面代码修改如下:

	public Object pop(){
		if(size == 0){
			throw new EmptyStackException();
		}
		Object result = elements[--size];
		elements[size] = null;
		return result;
	}
再来看一段代码:

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;


public class IOTest {
	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("test.xml");
			InputStreamReader isr = new InputStreamReader(fis);
			BufferedReader br = new BufferedReader(isr);
			if(br.ready()){
				System.out.println(br.readLine());
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
成功输出结果如下:

这段代码看似没有任何问题,但是却存在着内存泄露问题,我们没有关闭相应的资源


再来思考一个问题,代码如下:

public class IOTest {
	public static void main(String[] args) {
		IOTest test = new IOTest();
		IOTest.MyThread myThread = test.new MyThread();
		myThread.start();
		test = null; //这个对象能释放吗?
		myThread = null; //线程能自动结束吗?为什么?
	}
	
	class MyThread extends Thread{
		private int i = 0;
		@Override
		public void run() {
			while(true){
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(i++);
			}
		}
	}
}

上面对象能释放吗?线程能自动结束吗?

在搞清楚上面问题之前,我们将上面代码进行修改,先看如下代码:

public class IOTest {
	private String data = "阳光小强";
	
	public static void main(String[] args) {
		IOTest test = new IOTest();
		/*IOTest.MyThread myThread = test.new MyThread();
		myThread.start();*/
		test = null; //这个对象能释放吗?
		//myThread = null; //线程能自动结束吗?为什么?
		System.gc(); //启动垃圾回收器
		while(true){
			
		}
	}
	
	class MyThread extends Thread{
		private int i = 0;
		@Override
		public void run() {
			while(true){
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(i++);
				System.out.println(data);
			}
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("对象销毁了");
	}
}
在上面代码中我们重写了IOTest对象的finalize()方法,该方法是Object类的方法(protected),作用是当对象的垃圾回收器执行的时候回调该方法。

上面我们使用了System.gc()方法来通知垃圾回收器进行垃圾回收,运行的结果是输出了“对象销毁了".下面我们将上面代码中的注释去掉,启动线程后再来运行一次。

public class IOTest {
	private String data = "阳光小强";
	
	public static void main(String[] args) {
		IOTest test = new IOTest();
		IOTest.MyThread myThread = test.new MyThread();
		myThread.start();
		test = null; //这个对象能释放吗?
		myThread = null; //线程能自动结束吗?为什么?
		System.gc(); //启动垃圾回收器
		while(true){
			
		}
	}
	
	class MyThread extends Thread{
		private int i = 0;
		@Override
		public void run() {
			while(true){
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(i++);
				System.out.println(data);
			}
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("对象销毁了");
	}
}
可以看到”对象销毁了“这句话没有被输出到控制台,说明我们创建的IOTest对象没有被销毁,在很多书上都会说,给一个对象赋null值,这个对象就会成为无主对象,就会被垃圾回收器回收,但是为什么这个线程和IOTest对象还是存在的?如果是这样的话,我们应该考虑如何节省我们的内存?再来看一段代码:

public class IOTest {
	private String data = "阳光小强";
	
	public static void main(String[] args) {
		IOTest test = new IOTest();
		IOTest.MyThread myThread = test.new MyThread();
		myThread.start();
		test = null; //这个对象能释放吗?
		myThread = null; //线程能自动结束吗?为什么?
		
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.gc(); //启动垃圾回收器
		}
	}
	
	class MyThread extends Thread{
		private int i = 0;
		@Override
		public void run() {
			while(i<5){
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(i++);
				System.out.println(data);
			}
		}
		
		@Override
		protected void finalize() throws Throwable {
			super.finalize();
			System.out.println("线程对象销毁了");
		}
	}
	
	public void testUsed(){
		while(true){
			
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("外部类对象销毁了");
	}
}

输出结果:


从上面结果中我们可以看到,只有当线程结束后,线程对象和启动线程的对象才能真正的被垃圾回收器回收,所以在内部类中定义线程类,启动线程,会一直持有外部类的引用。现在我们又会产生一个疑问,是不是所有的内部类都持有外部类的引用呢?下面我们再来做个试验:

public class IOTest {
	private String data = "阳光小强";
	
	public static void main(String[] args) {
		IOTest test = new IOTest();
		IOTest.MyThread myThread = test.new MyThread();
		//myThread.start();
		test = null; //这个对象能释放吗?
		//myThread = null; //线程能自动结束吗?为什么?
		
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.gc(); //启动垃圾回收器
		}
	}
	
	class MyThread extends Thread{
		private int i = 0;
		@Override
		public void run() {
			while(i<5){
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(i++);
				System.out.println(data);
			}
		}
		
		@Override
		protected void finalize() throws Throwable {
			super.finalize();
			System.out.println("线程对象销毁了");
		}
	}
	
	public void testUsed(){
		while(true){
			
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("外部类对象销毁了");
	}
}
在上面的代码中我没有启动线程,所以此时的MyThread就可以当成一个普通的内部类了,我将外部类的引用置为空,会发现没有任何结果输出(说明外部类没有被销毁)

public class IOTest {
	private String data = "阳光小强";
	
	public static void main(String[] args) {
		IOTest test = new IOTest();
		IOTest.MyThread myThread = test.new MyThread();
		//myThread.start();
		test = null; //这个对象能释放吗?
		myThread = null; //线程能自动结束吗?为什么?
		
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.gc(); //启动垃圾回收器
		}
	}
	
	class MyThread extends Thread{
		private int i = 0;
		@Override
		public void run() {
			while(i<5){
				try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(i++);
				System.out.println(data);
			}
		}
		
		@Override
		protected void finalize() throws Throwable {
			super.finalize();
			System.out.println("线程对象销毁了");
		}
	}
	
	public void testUsed(){
		while(true){
			
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("外部类对象销毁了");
	}
}
我再将内部类的引用和外部类的引用都置为空,则输出了以下结果:


至少我可以从上面现象中这样认为,所有内部类都持有外部类的引用。这个结论还有待进一步的验证。。。

感谢你对“阳光小强"的关注,我的另一篇博文很荣幸参加了CSDN举办的博文大赛,如果你觉的小强的博文对你有帮助,请为小强投上你宝贵的一票,投票地址http://vote.blog.csdn.net/Article/Details?articleid=30101091

原文地址:https://www.cnblogs.com/lanzhi/p/6468885.html