网站建设需要多久,wordpress woocommerce 单位,网页制作代码html,网站建设发信息文章目录 1. 基本概念2. 代码演示2.1 软引用代码演示2.2 弱引用代码演示2.3 弱引用引用队列代码演示2.4 虚引用代码演示2.5 虚引用引用队列代码演示 3. 实战样例3.1 利用软引用实现资源对象缓存3.2 利用弱引用实现临时行为数据缓存3.3 利用虚引用引用队列实现资源释放 本次实验… 文章目录 1. 基本概念2. 代码演示2.1 软引用代码演示2.2 弱引用代码演示2.3 弱引用引用队列代码演示2.4 虚引用代码演示2.5 虚引用引用队列代码演示 3. 实战样例3.1 利用软引用实现资源对象缓存3.2 利用弱引用实现临时行为数据缓存3.3 利用虚引用引用队列实现资源释放  本次实验Java版本JDK 1.8.0_152_release 
1. 基本概念 
本节先了解一下概念看不懂没关系后续会详解。 
Java中的引用可以按强弱程度分为四种JVM对不同程度的引用回收策略不同 
强引用Strong Reference我们平时用的都是强引用。例如MyObject myObj  new MyObject(); 
回收只要有引用就不会被回收。 
软引用Soft Reference使用SoftReference显式声明。 
回收当JVM内存不够时会对软引用对象进行回收。应用场景做资源类缓存。使用样例 MyObject myObject  new MyObject(Amy);  // 从数据库中获取数据SoftReferenceMyObject reference  new SoftReference(myObject);  // 增添软引用// do something ...myObject  reference.get(); // 尝试获取myObject对象if (myObject  null) {// 没获取到说明已经被JVM回收了myObject  new MyObject(Amy);  // 重新从数据库中获取数据} else {// 没有被JVM回收}弱引用Weak Reference使用WeakReference显式声明。 
回收当JVM下次执行垃圾回收时就会立刻回收。应用场景做临时行为类数据缓存。使用样例和上面SoftReference一样把SoftReference改成WeakReference即可。 
虚引用Phantom Reference也称为“幽灵引用”、“幻影引用”等。单纯的将其标记一下配合引用队列ReferenceQueue进行回收前的一些操作 
回收当JVM执行垃圾回收时就会立刻回收应用场景资源释放用来代替finalize方法特殊点虚引用的reference.get()方法一定会返回null源码就是直接return null这也是为什么叫虚引用的原因。使用样例建后续引用队列。 
引用队列在定义软/弱/虚引用时可以传个引用队列虚引用必须传这样对象在被回收之前会进入引用队列可以显式的对其进行一些操作。 
作用可以在该对象被回收时主动收到通知来对其进行一些操作。应用场景资源释放 
2. 代码演示 
本节是对软/弱/虚引用和引用队列的作用机制代码演示。 
2.1 软引用代码演示 
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;public class SoftReferenceTest {public static void main(String[] args) {// 这里定义了obj对象为正常的强引用String obj  new String(myObj);// 为该对象增加软引用// 此时它同时拥有两种引用即obj同时拥有强引用和软引用ReferenceString softReference  new SoftReference(obj);// 解除obj的强引用此时obj只剩下了软引用obj  null;// 调用GC进行垃圾回收。只是通知并不一定真的做垃圾回收这里假设做了System.gc();// 尝试从软引用中获取obj对象// 若没有获取到说明该对象已经被JVM释放// 若获取到了说明还没有被释放String tryObj  softReference.get();System.out.println(try obj:  tryObj);}}输出结果 
try obj:myObj解释 
上述样例中我们为obj对象绑定了软引用在解除了强引用后尝试进行GC。可以看到GC并没有回收obj对象。这是因为软引用的回收规则为当JVM内存不够时才会进行回收上面的简单例子中JVM内存显然不会不够用因此我们可以通过reference对象拿到obj对象。 
2.2 弱引用代码演示 
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;public class WeakReferenceTest {public static void main(String[] args) {// 这里定义了obj和obj2对象为正常的强引用String obj1  new String(myObj1);String obj2  new String(myObj2);// 为它们增加弱引用// 此时它们同时拥有两种引用即obj同时拥有强引用和弱引用ReferenceString reference1  new WeakReference(obj1);ReferenceString reference2  new WeakReference(obj2);// 解除obj1的强引用然后执行GCobj1  null;System.gc();// 解除obj2的强引用不执行GCobj2  null;/*** 尝试从若引用中获取obj1对象* 若没有获取到说明该对象已经被JVM释放* 若获取到了说明还没有被释放*/String tryObj1  reference1.get();// 这里tryObj1将会返回null因为obj解除强引用后执行了GC操作// 因此obj1被释放了所以获取不到System.out.println(try obj1:  tryObj1);String tryObj2  reference2.get();// 这里obj2不是null因为obj2解除了强引用后并没有执行GC// 因此obj2并没有被释放所以可以获取到System.out.println(try obj2:  tryObj2);}}输出结果 
try obj1:null
try obj2:myObj2解释弱引用的释放规则为“下次执行GC时会将对象进行释放”。在上述代码中obj1执行了GC而obj2没有执行GC因此obj1为null而obj2不为null 
2.3 弱引用引用队列代码演示 
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;public class WeakReference2Test {public static void main(String[] args) throws InterruptedException {// 定义引用队列ReferenceQueueString referenceQueue  new ReferenceQueue();// 这里定义了obj为正常的强引用String obj1  new String(myObj1);// 为它增加弱引用// 此时它同时拥有两种引用即obj同时拥有强引用和弱引用// 在为obj对象绑定弱引用时同时传入了引用队列。// 这样“obj对象”在被回收之前其对应的“引用对象”就会进入引用队列ReferenceString reference1  new WeakReference(obj1, referenceQueue);  // 这里传入引用队列System.out.println(reference1:   reference1);// 解除obj1强引用然后执行GCobj1  null;// 尝试从引用队列中获取引用对象。ReferenceString pollRef1  (ReferenceString) referenceQueue.poll();// 这里会返回null因为obj1虽然没有了强引用但是还没有被标记为“可回收”// 因此“obj1”的引用对象并没有进入到引用队列中System.out.println(pollRef1:   pollRef1);// 执行GC操作System.gc();// 给些时间让reference对象进入引用队列Thread.sleep(100);// 再次尝试从引用队列中获取“引用对象”ReferenceString pollRef2  (ReferenceString) referenceQueue.poll();// 这次就不为null了因为obj1已经被标记为“可回收”// 因此其对应的“引用对象”就会进入引用队列System.out.println(pollRef2:   pollRef2);// 尝试从引用对象中获取obj对象// 这里两种可能①null② myObj1// 因为当obj1被标记为“可回收”时其引用对象就会进入引用队列但此时obj1是否被回收并不知道// ① 若obj1已经被回收了这里就会返回null。(也是最常见的)// ② 若Obj1暂时还没被回收这里就会返回“myObj1”这个很难复现System.out.println(pollRef2 obj:   pollRef2.get());}}输出 
reference1: java.lang.ref.WeakReference49c2faae
pollRef1: null
pollRef2: java.lang.ref.WeakReference49c2faae
pollRef2 obj: null上述代码中一共进行了两次从引用队列中获取“引用对象”。第一次由于还没有执行GC操作obj1没有被标记为“可回收”状态所以其对应的引用对象reference1也就没有进入引用队列。第二次由于执行了GC操作obj1被标记为了“可回收”因此可以拿到reference1对象但由于obj1已经被回收了因此使用reference1.get()却拿不到obj1对象了。 
这里再额外说明几点大家可能疑惑的点 
将“①对象标记为是否可回收”和“②释放对象”两个动作基本上是同时发生的执行完动作①就会执行动作②。所以一般我们都只能从引用队列中拿到“引用对象”但是再用引用对象去拿对象就拿不到了因为已经被释放了。只要对象被标记为“可释放”那么该对象的“引用对象”就会进入引用队列后续该对象随时可能会被释放。因此有可能会出现以下情况ReferenceString pollRef1  (ReferenceString) referenceQueue.poll();
// 不为null因为这个时候还没被释放
System.out.println(pollRef1:   pollRef1.get()); 
// 为null被释放了
System.out.println(pollRef1:   pollRef1.get());2.4 虚引用代码演示 
虚引用必须要配合引用队列要不然没有任何意义 
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;public class PhantomReferenceTest {public static void main(String[] args) {ReferenceQueueString queue  new ReferenceQueue();// 这里定义了obj对象为正常的强引用String obj  new String(myObj);// 为该对象增加虚引用// 此时它同时拥有两种引用即obj同时拥有强引用和虚引用// 虚引用构造方法要求必须要传引用队列ReferenceString phantomReference  new PhantomReference(obj, queue);// obj的强引用还在因此其一定不会被释放// 尝试使用虚引用获取obj对象String tryObj  phantomReference.get();// tryObj为null因为phantomReference.get()的实现就是return null;System.out.println(try obj:  tryObj);}}输出 
try obj:null上面的代码中虽然obj没有被释放但用虚引用依然无法获取obj对象。这也是为什么人们说“虚引用是虚幻的必须要配合引用队列一起用要不然就没意义了” 
2.5 虚引用引用队列代码演示 
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;public class PhantomReferenceTest2 {public static void main(String[] args) {ReferenceQueueString queue  new ReferenceQueue();// 这里定义了obj对象为正常的强引用String obj  new String(myObj);// 为该对象增加虚引用// 此时它同时拥有两种引用即obj同时拥有强引用和虚引用// 虚引用构造方法要求必须要传引用队列ReferenceString phantomReference  new PhantomReference(obj, queue);System.out.println(phantomReference:  phantomReference);// 解除obj的强引用obj  null;System.gc();// 从引用队列中获取引用对象。// 由于obj已经是“可释放或已释放”状态因此可以获取到phantomReferenceSystem.out.println(referenceQueue:  queue.poll());}}3. 实战样例 
3.1 利用软引用实现资源对象缓存 
场景假设我们有一个资源类Resource它占用100M内存。加载过程很慢我们既不想因为导致OOM又想让它缓存在JVM里我们就可以用软引用实现。 
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;public class SoftReferenceAppTest {static class Resource {private byte[] data  new byte[1024 * 1024 * 100]; // 该资源类占用100M内存private static int num  0;private static SoftReferenceResource resourceHolder  null;public static Resource getInstance() {if (resourceHolder ! null) {// 从缓存中获取Resource resource  resourceHolder.get();if (resource ! null) {return resource;}}// 缓存中没有重新new一个System.out.println(从数据库中获取资源类  num);num ;Resource resource  new Resource();resourceHolder  new SoftReference(resource);return resource;}public String getData() {return data;}}public static void main(String[] args) {// 使用资源类System.out.println(Resource.getInstance().getData());// 执行各种各样的事情最终jvm内存满了然后回收了resource。int i  0;Listbyte[] list  new ArrayList();while(true) {list.add(new byte[1024*1024*50]);i;// 使用资源类Resource.getInstance().getData();if (i  10000) {break;}}}}输出 
从数据库中获取资源类0
data
从数据库中获取资源类1
Exception in thread main java.lang.OutOfMemoryError: Java heap spaceat jvm.SoftReferenceAppTest$Resource.init(SoftReferenceAppTest.java:11)at jvm.SoftReferenceAppTest$Resource.getInstance(SoftReferenceAppTest.java:30)at jvm.SoftReferenceAppTest.main(SoftReferenceAppTest.java:52)上述例子中我们的Resource类使用了软引用SoftReference来进行缓存。 
之后在模拟中执行过程如下 
首先获取了资源类对象并将这个对象绑定了软引用但并不存在强引用使用资源类正常从软引用中获取缓存的资源类对象不断的往JVM中添加数据list.add(new byte[1024*1024*50]);最终导致内存不够用由于内存不够用因此GC回收时回收了资源类对象因此重新生成了资源类对象最后JVM内存不够了最终OOM 
3.2 利用弱引用实现临时行为数据缓存 
抽象场景在某些场景中用户的操作会产生一些后台数据而这些数据用户可能会在短时间内再次使用但也可能不使用。这种时候就可以使用弱引用WeakReference进行缓存。 
样例场景后台提供了一个word转PDF的功能但由于某些用户可能会连点或由于网络不好导致短时间内再次对同一文档进行转换。而转换过程又比较耗资源因此使用WeakReference进行缓存。 
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;public class WeakReferenceAppTest {// 缓存对象static MapString, WeakReferenceString cacheMap  new HashMap();// 将word数据转为pdf数据不使用缓存public static String wordToPDF(String wordData) {return pdf   wordData;}// 将word数据转为pdf数据使用缓存public static String wordToPDFWithCache(String wordData) {// 先看缓存里有没有if (cacheMap.keySet().contains(wordData)) {String pdfData  cacheMap.get(wordData).get();if (pdfData ! null) {System.out.println(使用缓存数据);return pdfData;}}String pdfData  pdf   wordData;System.out.println(无缓存执行转换);cacheMap.put(wordData, new WeakReference(pdfData));return pdfData;}public static void main(String[] args) throws InterruptedException {// 程序A不使用WeakReference做缓存{System.out.println(---程序A不用缓存----);// 用户A将word1转为了pdfSystem.out.println(wordToPDF(word1));// 用户A因为手快连点了两次由于没有进行缓存因此又执行了一遍wordToPDFSystem.out.println(wordToPDF(word1));}System.out.println();// 程序B使用WeakReference做缓存{System.out.println(---程序B使用缓存----);// 用户A将word1转为了pdfSystem.out.println(wordToPDFWithCache(word1));// 用户A因为手快连点了两次由于有缓存直接返回System.out.println(wordToPDFWithCache(word1));// 过了若干时间System.gc();Thread.sleep(1000);// 用户A再次对word1进行转pdfSystem.out.println(wordToPDFWithCache(word1));}}}输出 
---程序A不用缓存----
pdf word1
pdf word1---程序B使用缓存----
无缓存执行转换
pdf word1
使用缓存数据
pdf word1
无缓存执行转换
pdf word13.3 利用虚引用引用队列实现资源释放 
应用场景替代finalize因为其有很多缺点 
finalize的缺点 
1无法保证什么时间执行。 2无法保证执行该方法的线程优先级。 3无法保证一定会执行。 4如果在终结方法中抛出了异常并且该异常未捕获处理则当前对象的终结过程会终止且该对象处于破坏状态。 5影响GC的效率特别是在finalize方法中执行耗时较长的逻辑。 6有安全问题可以进行终结方法攻击。 
最优解决方案使用try-with-resource。 
其次解决方案使用虚引用引用队列。 
最好两种同时用。 
样例场景我们现在有一个比较耗资源的Resource类也可以是IO等我们没有办法保证try-with-resource和finalize能正确释放。因此我们还要启一个后台线程来监控目前是否有未释放的资源。如果有则释放资源。若释放失败则发送通知消息。 
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Map;public class PhantomReferenceAppTest {// 引用队列专门负责对Resource对象的释放static ReferenceQueueResource referenceQueue  new ReferenceQueue();static MapPhantomReference, String /*资源ID*/ referenceIdMap  new HashMap();static class Resource {public static Resource getInstance(String resourceID) {Resource resource  new Resource();// 将resource与一个虚引用绑定并传入公共引用队列// 这样当resource资源不再使用时就可以释放资源。PhantomReference phantomReference  new PhantomReference(resource, referenceQueue);referenceIdMap.put(phantomReference, resourceID); // 记录这个引用对象对应的资源ID用于后续释放资源return resource;}// 注意这个必须是静态方法。因为PhantomReference根本拿不到Resource的实例。// 假设使用软/弱引用也不一定能拿到Resource因为Resource对象很有可能已经被JVM释放了public static void realise(String resourceID) {System.out.println(释放资源  resourceID);}}public static void main(String[] args) throws InterruptedException {// 定义一个专门用于资源释放的线程new Thread(new Runnable() {SneakyThrowsOverridepublic void run() {while (true) {Thread.sleep(100);// 尝试从引用队列中获取引用对象PhantomReference phantomReference  (PhantomReference) referenceQueue.poll();if (phantomReference  null) {continue;}String resourceId  referenceIdMap.get(phantomReference); // 获取resourceIDResource.realise(resourceId);referenceIdMap.remove(phantomReference);}}}).start();Resource resource1  Resource.getInstance(res1);Resource resource2  Resource.getInstance(res2);Resource resource3  Resource.getInstance(res3);resource3  null; // resource3使用完毕执行了GCSystem.gc();Thread.sleep(1000);resource1  null; // resource1使用完毕没有执行GCresource2  null; // resource2使用完毕执行GCSystem.gc();}
}输出 
释放资源res3
释放资源res2
释放资源res1