Java中为了简化对象的释放,引入了自动的垃圾回收(Garbage Collection简称GC)机制。通过垃圾回收器来对不在使用的对象完成自动的回收,垃圾回收器主要负责对堆上的内存的回收。使用自动垃圾回收器,优点是:降低程序员的编写难度,降低对象回收bug的可能性(并不是不会出现);缺点:程序员无法控制内存回收及时性。
自动垃圾回收器是不考虑 程序计数器、Java虚拟机栈、本地方法栈的。因为这部分不共享,都是伴随着线程的创建而创建,线程的销毁而销毁。而方法区的栈帧在执行完方法之后就会自动弹出栈并释放掉对应的内存。
方法区的回收
方法区能回收的内容主要是不再使用的类。判定一个类可以被卸载,需要同时满足三个条件:
1、此类所有实例对象都被回收,在堆中不存在任何该类的实例对象以及子类对象。
2、加载该类的类加载器已经被回收。
3、该类对应的java.lang.Class对象没有在任何地方被引用。
下面我们通过代码来测试一下:我们新建一个类加载器,用这个类加载器去加载一个类,然后在把该类实例化。依次对实例对象没有全被回收、类加载器没被回收、Class对象被引用进行测试,看看能不能被回收。
这个新建的类加载器必须是自定义的类加载器,由于系统类加载器加载的类不会被卸载,并且只加载一次,所以普通项目很难获取到类卸载的日志。所以想要看到跟踪类卸载的日志,我们需要使用自定义的类加载器。
我使用的类加载器是前几篇介绍类加载器的文章中写过的breaClassLoader1自定义类加载器,下面是该自定义类加载器的代码:
package com.example.principle.classLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
//打破双亲委派机制
public class BreakClassLoader1 extends ClassLoader{
private byte[] loadClassData(String name){
// 将包名中的"."替换成文件系统的路径分隔符"/"
// 假设类文件存放在某个固定路径下,这里是"classes"目录
//这里的路径,是把文件保存到哪,随便找个能快速找到的位置就行。
String filePath = "your paths";
File classFile = new File(filePath);
long len = classFile.length();
byte[] raw = new byte[(int) len];
try (FileInputStream fis = new FileInputStream(classFile)) {
// 读取类文件的字节码
int r = fis.read(raw);
if (r != len) {
throw new IOException("无法读取全部的类文件:" + name);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return raw;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("java.")) {
return super.loadClass(name);
}
byte[] data=loadClassData(name);
return defineClass(name,data,0,data.length);
}
}
下面是测试方法区有没有被回收的代码:
package com.example.principle.garbage;
import com.example.principle.classLoader.BreakClassLoader1;
import java.util.ArrayList;
public class ClassUnload {
public static void main(String[] args) {
try {
ArrayList<Class<?>> classes=new ArrayList<>();
ArrayList<ClassLoader> loaders=new ArrayList<>();
ArrayList<Object> objects=new ArrayList<>();
while (true){
//创建一个新的类加载器
BreakClassLoader1 loader=new BreakClassLoader1();
//通过这个类加载器去加载一个类
Class<?> clazz = loader.loadClass("com.example.principle.text");
//实例化堆上的这个对象
Object o=clazz.newInstance();
//依次引用对象实例,类加载器,Class对象,测试回收情况。
//实例对象没有全被回收
//objects.add(o);
//加载该类的类加载器没有被回收
//loaders.add(loader);
//该类的class对象被引用
//classes.add(clazz);
//手动触发垃圾回收
System.gc();
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
还有设置该测试类的虚拟机参数:
-XX:+TraceClassLoading 参数是为了跟踪类的加载。 -XX:+TraceClassUnloading 参数是为了跟踪类的卸载。
这是我用该自定义类加载器加载的类:
package com.example.principle;
public class text {
static {
System.out.println("静态代码块被加载");
}
}
结果:
从打印结果可以看到,有Unloading 和Loaded俩种打印的日志。就分别代表着卸载和加载该类。
然后依次打开这三个条件之一,进行测试
//实例对象没有全被回收
//objects.add(o);
//加载该类的类加载器没有被回收
//loaders.add(loader);
//该类的class对象被引用
//classes.add(clazz);
结果都是只有加载类,没有卸载类。