单例模式介绍:
Ensure a class has only one instance,and provide a global point of access to it.(确保一个类只有一个实例,向整个系统提供这个实例。)
单例模式优点:
- 单例模式在内存中只有一个实例,减少内存开支;
- 省去了频繁的实例化对象,减轻了GC压力;
- 单例模式可在系统设置全局访问点,优化和共享资源访问。
单例模式缺点:
- 扩展困难,单例模式一般没有接口,若要扩展,除了修改代码基本没有第二种途径实现;
- 与单一职责原则冲突,一个类应该只实现一个逻辑,而不关心其是否为单例的,是不是单例取决于环境,单例模式把 要单例 和业务逻辑融合在一个类中。
单例模式使用场景:
- 要求生成唯一序列号环境;
- 在项目中需要一个共享访问点或共享数据,比如一个Web页面上的计数器,使用单例模式保持计数器的值,并确保是线程安全的;
- 创建一个对象需要消耗的资源过多,例如访问IO和数据库资源;
- 需要定义大量工具类。
单例模式代码:
方式1
在加载类时初始化对象
public class Singleton1 {
public static final Singleton1 singleton1 = new Singleton1();//初始化一个对象
private Singleton1() {
}
public static Singleton1 getInstance() {
return singleton1;
}
public String toString() {
return "Singleton1{}";
}
}
方式2
通过枚举实现
public enum Singleton2 {
INSTANCE;
@Override
public String toString() {
return "Singleton2{}";
}
}
方式3
懒加载方式,在使用时实例化对象
public class Singleton3 {
//使用volatile关键字,使INSTANCE参数在改变时内存可见
private static volatile Singleton3 INSTANCE;
public static Singleton3 getInstance() {
Singleton3 result = INSTANCE;
if (result == null) {
synchronized (Singleton3.class) {//加锁防止线程竞争导致实例化多个对象
result = INSTANCE;
if (result == null) {//再次判断类是否实例化
INSTANCE = result = new Singleton3();
}
}
}
return result;
}
public String toString() {
return "Singleton3{}";
}
}
在加载单例类之后单例对象是否会被JVM回收
测试代码:
@Test
public void test2() {
System.out.println(Singleton1.getInstance().toString());
while (true) {
byte[] a = new byte[3 * 1024 * 1024];
}
}
在加载完单例类之后,通过不断实例化对象时JVM对内存进行回收。
jdk版本:1.8.0_66
vm参数:-verbose:gc -Xms20M -Xmx20M(每次jvm进行垃圾回收时显示内存信息,JVM内存固定设置为20M)
运行结果:
Singleton1{}
[GC (Allocation Failure) 5017K->1255K(19840K), 0.0026116 secs]
[GC (Allocation Failure) 4327K->1254K(19840K), 0.0015265 secs]
[GC (Allocation Failure) 4326K->1254K(19840K), 0.0002786 secs]
[GC (Allocation Failure) 4326K->1254K(19840K), 0.0002693 secs]
每次回收内存在3M,故垃圾回收没有回收单例对象。
单例对象为什么不被回收?
目前JVM虚拟机垃圾收集算法使用根搜索算法;所以,如果从根开始搜索,经过一些列的路径,可以到达java堆中的对象,那么这个对象就是活的。
根对象有:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中类的静态属性引用的对象;
- 方法区中的常量引用的对象;
- 本地方法栈中JNI的引用的对象。
单例模式中对象被方法区中类的静态属性所引用,故不被回收。
既然单例对象不会被垃圾回收,但单例类本身如果长时间不使用会不会被收集呢?
JVM卸载类的判断条件如下:
- 该类所有的实例都被回收,java堆中不存在该类任何实例;
- 加载该类的ClassLoader已经被回收;
- 该类对应的java.lang.Class对象没有任何地方被引用,在任何地方没有通过反射访问该类的方法。
单例类不满足条件1,故不会被回收。