为什么出现引用类型的区分
因为当执行完成任务后程序需要回收一些资源型对象,例如数据库连接Connection,Socket,IO流资源等对象时,使用try-finally方式或者hook钩子子线程来关闭资源型对象不一定保证其对象会被GC回收机制处理掉,所以用引用类型来保证对象一定会被GC回收掉。
四种引用类型
- 强引用:java程序中常见的引用,把一个对象赋值给一个引用变量时,该引用变量就是强引用。当对象被强引用变量引用时,该对象是不能被GC回收,是造成内存泄漏的主要原因。
- 软引用:需要声明SoftReference来表明对象的引用变量类型是软引用。该对象在内存足够时不会被GC回收,而当发现内存不足时GC会直接回收,存放在对应的ReferenceQueue队列中。
- 弱引用:需要声明WeakReference来表明对象的引用变量类型是弱引用。该对象不管内存是否足够,都会在GC运行时回收,存放在对应的ReferenceQueue队列中。
- 虚引用:需要声明PhantomReference来来实现,构造方法中包含ReferenceQueue(引用队列)。用来触发对象的再次操作。
LRUCache(冷热数据缓存治理)的方式测试引用类型
- 自定义内存大小为1M的引用对象类
/**
* 引用对象类 设置1M的内存
*/
public class Reference {
// 1M
private final byte[] data = new byte[2 << 19];
@Override
protected void finalize() throws Throwable {
System.out.println("GC回收机制触发,回收对象");
}
}
- 定义函数式接口 逻辑执行单元
@FunctionalInterface
public interface CacheLoader<K, V> {
// 定义加载数据的方法
V loader(K k);
}
- 使用LRU思想实现缓存类
/**
* LRU 是数据冷热治理的一种思想 使用频率高的是热数据 使用频率低的是冷数据
* 一般使用双向链表来使用
*/
public class LRUCache<K,V> {
// 用于记录key值的顺序
private final LinkedList<K> keyList = new LinkedList<>();
// 用于存放数据
private final Map<K,V> cache = new HashMap<>();
// 设置数据最大容量
private final int cacheMax;
// cacheLoader 接口提供一种加载数据的方式 函数式接口 逻辑执行单元和runnable接口类似
private final CacheLoader<K,V> cacheLoader;
public LRUCache(int cacheMax, CacheLoader<K, V> cacheLoader) {
this.cacheMax = cacheMax;
this.cacheLoader = cacheLoader;
}
// 添加数据方法
public void put(K key,V value){
// 当元素超过容量时 将最老的数据清除
if (keyList.size() >= cacheMax){
K oldKey = keyList.removeFirst();
cache.remove(oldKey);
}
// 如果数据存在先删除队列中的key 再重新放到队列尾部 不存在则直接放队列尾部
if (keyList.contains(key)){
keyList.remove(key);
}
keyList.addLast(key);
// 数据放入
cache.put(key,value);
}
// 获取数据方法
public V get(K key){
V value;
// 先将key 从keyList中删除 看下是否有值
boolean success = keyList.remove(key);
if (!success){
// 没有对应的key 直接通过cacheLoader接口对数据进行处理
value = cacheLoader.loader(key);
// 放入缓存cache
this.put(key,value);
}else {
// 有对应的可以 直接从缓存中获取数据 并将key放到keyList最后面
value = cache.get(key);
keyList.addLast(key);
}
return value;
}
@Override
public String toString() {
return this.keyList.toString();
}
}
- LRUCache测试类
public class LRUCacheTest {
public static void main(String[] args) {
LRUCache<String,Reference> cache = new LRUCache<>(5,key -> new Reference());
cache.get("A");
cache.get("B");
cache.get("C");
cache.get("D");
cache.get("E");
// 上面5个放进缓存 队列中第一个数据为A
System.out.println("队列信息1:"+cache);
// 再放一个进去A 回被挤出来
cache.get("F");
System.out.println("队列信息2: "+cache);
}
}
- 测试强引用类型
public static void main(String[] args) throws InterruptedException {
// 强引用
LRUCache<Integer,Reference> cache = new LRUCache<>(200,key -> new Reference());
// 软引用
//SoftLRUCache<Integer,Reference> cache = new SoftLRUCache<>(200,key -> new Reference());
for (int i=0;i<Integer.MAX_VALUE;i++){
cache.get(i);
TimeUnit.SECONDS.sleep(1);
System.out.println("数据"+i+"添加到缓存中");
}
}
启动配置JVM参数设置内存最大和内存初始,打印GC参数
-Xmx128M -Xms64M -XX:+PrintGCDetails

测试结果:

- 编写软引用LRUCache类
/**
* 和LRUCache 一样 不过其Value值被软引用 Soft Reference 封装
* @param <K>
* @param <V>
*/
public class SoftLRUCache<K,V> {
private final LinkedList<K> keyList = new LinkedList<>();
// Value值用SoftReference封装
private final Map<K, SoftReference<V>> cache = new HashMap<>();
private final int cacheMax;
private final CacheLoader<K,V> cacheLoader;
public SoftLRUCache(int cacheMax, CacheLoader<K, V> cacheLoader) {
this.cacheMax = cacheMax;
this.cacheLoader = cacheLoader;
}
private void put(K key,V value){
if (keyList.size() >= cacheMax){
K oldKey = keyList.removeFirst();
cache.remove(oldKey);
}
if (keyList.contains(key)){
keyList.remove(key);
}
keyList.addLast(key);
// 保存值也用SoftReference封装
cache.put(key,new SoftReference<>(value));
}
public V get(K key){
V value;
boolean success = keyList.remove(key);
if (!success){
value = cacheLoader.loader(key);
this.put(key,value);
}else {
value = cache.get(key).get();
keyList.addLast(key);
}
return value;
}
@Override
public String toString() {
return this.keyList.toString();
}
}
- 测试软件引用
public static void main(String[] args) throws InterruptedException {
// 强引用
//LRUCache<Integer,Reference> cache = new LRUCache<>(200,key -> new Reference());
// 软引用
SoftLRUCache<Integer,Reference> cache = new SoftLRUCache<>(200,key -> new Reference());
for (int i=0;i<Integer.MAX_VALUE;i++){
cache.get(i);
TimeUnit.SECONDS.sleep(1);
System.out.println("数据"+i+"添加到缓存中");
}
}

弱引用
// 弱应用
Reference ref = new Reference();
WeakReference<Reference> weakReference = new WeakReference<>(ref);
// 当ref=null时 gc触发直接回收内存中的对象
ref = null;
System.gc();
虚引用
/**
* 使用虚引用来再次触发 清理Socket对象操作
*/
public class SocketCleanTracker {
// 定义引用队列
private static final ReferenceQueue<Object> queue = new ReferenceQueue<>();
static {
new Cleaner().start();
}
public static void track(Socket socket){
new Tracker(socket,queue);
}
private static class Cleaner extends Thread{
private Cleaner(){
super("清理Socket的线程");
//设置为守护线程
setDaemon(true);
}
@Override
public void run() {
while (true){
try {
// GC回收后会将对象放入queue中
Tracker tracker = (Tracker) queue.remove();
tracker.close();
}catch (InterruptedException e){
}
}
}
}
// 一个虚引用
private static class Tracker extends PhantomReference<Object>{
private final Socket socket;
public Tracker(Socket socket, ReferenceQueue<? super Object> q) {
super(socket, q);
this.socket =socket;
}
public void close(){
try {
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
使用虚引用再次触发Socket的关闭操作
try {
if (socket != null){
socket.close();
}
}catch (Throwable e){
// 添加虚引用再次触发关闭关闭操作
SocketCleanTracker.track(socket);
}
本文介绍了Java中引用类型的区分,包括强引用、软引用、弱引用和虚引用,详细阐述了它们在内存管理和垃圾回收中的作用。通过LRUCache的案例,展示了不同引用类型在缓存治理中的应用,并通过实际测试验证了各种引用类型的GC行为,特别是对内存限制下的资源回收效果。

被折叠的 条评论
为什么被折叠?



