作者:Grey

原文地址:Java中的引用类型和使用场景

Java中的引用类型有哪几种?

Java中的引用类型分成强引用软引用, 弱引用, 虚引用

强引用

没有引用指向这个对象,垃圾回收会回收

package git.snippets.juc;

import java.io.IOException;

public class NormalRef {
public static void main(String[] args) throws IOException {
M m = new M();
m = null;
System.gc();
System.in.read();
}
static class M {
M() {}
@Override
protected void finalize() throws Throwable {
System.out.println("finalized");
}
}
}

软引用

当有一个对象被一个软引用所指向的时候,只有系统内存不够用的时候,才会被回收,可以用做缓存(比如缓存大图片)

示例如下代码:注:执行以下方法的时候,需要把VM options设置为-Xms20M -Xmx20M

package git.snippets.juc;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit; /**
* heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
* 软引用,适合做缓存
* 示例需要把Vm options设置为:-Xms20M -Xmx20M
*/
public class SoftRef {
public static void main(String[] args) throws IOException {
SoftReference<byte[]> reference = new SoftReference<>(new byte[1024 * 1024 * 10]);
System.out.println(reference.get());
System.gc();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(reference.get());
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(reference.get());
System.in.read();
}
}

上述代码在第一次执行System.out.println(reference.get())时候,由于堆的最大最小值都是20M,而我们分配的byte数组是10M,没有超过最大堆内存,所以执行垃圾回收,软引用不被回收,后续又调用了byte[] bytes = new byte[1024 * 1024 * 10];再次分配了10M内存,此时堆内存已经超过设置的最大值,会进行回收,所以最后一步的System.out.println(reference.get());无法get到数据。

弱引用

只要垃圾回收,就会回收。如果有一个强引用指向弱引用中的这个对象,如果这个强引用消失,这个对象就应该被回收。一般用在容器里面。

代码示例如下:

package git.snippets.juc;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.concurrent.TimeUnit; /**
* 弱引用遭到gc就会回收
* ThreadLocal应用,缓存应用,WeakHashMap
*/
public class WeakRef {
public static void main(String[] args) {
WeakReference<T> reference = new WeakReference<>(new T());
System.out.println(reference.get());
System.gc();
System.out.println(reference.get());
}
static class T {
T() {}
@Override
protected void finalize() {
System.out.println("finalized");
}
}
}

如果执行了一次GCreference.get() 获取到的值即为空。

弱引用的使用场景

弱引用的一个典型应用场景就是ThreadLocal,以下是ThreadLocal的的简要介绍

set方法

    public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

get方法

    public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

ThreadLocalMap是当前线程的一个成员变量,所以,其他线程无法读取当前线程设置的ThreadLocal值。

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal的主要应用场景

场景一:每个线程需要一个独享的对象:假设有100个线程都需要用到SimpleDateFormat类来处理日期格式,如果共用一个SimpleDateFormat,就会出现线程安全问题,导致数据出错,如果加锁,就会降低性能,此时使用ThreadLocal,给每个线程保存一份自己的本地SimpleDateFormat,就可以同时保证线程安全和性能需求。

场景二:每个线程内部保存全局变量,避免传参麻烦:假设一个线程的作用是拿到前端用户信息,逐层执行Service1Service2Service3Service4层的业务逻辑,其中每个业务层都会用到用户信息,此时一个解决办法就是将User信息对象作为参数层层传递,但是这样会导致代码冗余且不利于维护。此时可以将User信息对象放入当前线程的Threadlocal中,就变成了全局变量,在每一层业务层中,需要使用的时候直接从Threadlocal中获取即可。

场景三:Spring的声明式事务,数据库连接写在配置文件,多个方法可以支持一个完整的事务,保证多个方法是用的同一个数据库连接(其实就是放在ThreadLocal里面)

了解了ThreadLocal简要介绍以后,我们可以深入理解一下ThreadLocal的一个内部原理,前面提到,ThreadLocalset方法实际上是往当前线程的一个threadLocals表中插入一条记录,而这个表中的记录都存在一个Entry对象中,这个对象有一个key和一个value,key就是当前线程的ThreadLocal对象。

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

这个Entry对象继承了WeakReference, 且构造函数调用了super(k), 所以Entry中的key是通过一个弱引用指向的ThreadLocal,所以,我们在主方法中调用

ThreadLocal<Object> tl = new ThreadLocal<>();

tl是通过强引用指向这个ThreadLocal对象。

当前线程的threadLocalMap中的key是通过弱引用指向ThreadLocal对象,这样就可以保证,在tl指向空以后,这个ThreadLocal会被回收,否则,如果threadLocalMap中的key是强引用指向ThreadLocal对象话,这个ThreadLocal对象永远不会被回收。就会导致内存泄漏。

但是,即便key用弱引用指向ThreadLocal对象,key值被回收后,Entry中的value值就无法被访问到了,且value是通过强引用关联,所以,也会导致内存泄漏,所以,每次在ThreadLocal中的对象不用了,记得要调用remove方法,把对应的value也给清掉。

虚引用

用于管理堆外内存回收

虚引用关联了一个对象,以及一个队列,只要垃圾回收,虚引用就被回收,一旦虚引用被回收,虚引用会被装到这个队列,并会收到一个通知(如果有值入队列,会得到一个通知)所以,如果想知道虚引用何时被回收,就只需要不断监控这个队列是否有元素加入进来了。

虚引用里面关联的对象用get方法是无法获取的。

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List; // 配置 -Xms20M -Xmx20M
public class PhantomRef {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<P> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) {
PhantomReference<P> phantomReference = new PhantomReference<>(new P(), QUEUE);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}).start(); new Thread(() -> {
while (true) {
Reference<? extends P> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
}
}
}).start(); try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} } static class P {
@Override
protected void finalize() throws Throwable {
System.out.println("finalized");
}
}
}

虚引用的应用场景

JDK的NIO包中有一个DirectByteBuffer, 这个buffer指向的是堆外内存,所以当这个buffer设置为空的时候,Java的垃圾回收无法回收,所以,可以用虚引用来管理这个buffer,当我们检测到这个虚引用被垃圾回收器回收的时候,可以做出相应的处理,去回收堆外内存。

Java中的引用类型和使用场景的更多相关文章

  1. java中的引用类型:强软弱虚

    java中的引用类型共4种:强软弱虚,具体每种类型的特点和应用场景.记录下.本文是看了马士兵老师的视频后记录整理的.加深印象. 基本概念 1. 强引用 强引用是使用最普遍的引用.如果一个对象具有强引用 ...

  2. Java中的引用类型(强引用、弱引用)和垃圾回收

    Java中的引用类型和垃圾回收 强引用Strong References 强引用是最常见的引用: 比如: StringBuffer buffer = new StringBuffer(); 创建了一个 ...

  3. java 中的引用类型

    GC基本原理 GC (Garbage Collection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征 ...

  4. java四种引用类型以及使用场景详解

    每种编程语言都有自己操作内存中元素的方式,例如在 C 和 C++ 里是通过指针,而在 Java 中则是通过“引用”.在 Java 中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(re ...

  5. Java中的引用类型Scanner类和随机类型Random

    Scanner类 我们要学的Scanner类是属于引用数据类型,我们先了解下引用数据类型.   引用数据类型的使用 与定义基本数据类型变量不同,引用数据类型的变量定义及赋值有一个相对固定的步骤或格式. ...

  6. java中的引用类型 部分讲解

    所谓的引用类型 类--接口--数组--枚举 [01--Scanner类] Scanner 这个类是用于键盘输入 它的格式为 类型  对象名称 =  new  类型(): 它的操作格式  对象名.nex ...

  7. Java - 四种引用类型及应用场景

    1. 强引用 new 一个对象的时候,就是强引用 Object object = new Object(); 只要强引用存在,垃圾回收就不会回收该对象,内存不足时会抛出OOM. 2. 软引用 定义:非 ...

  8. java中的引用类型的对象存放在哪里

    根据上下文来确定.比如void func(){    Object obj = new Object();//这个obj在函数的栈里.}class Test{   private Object obj ...

  9. java中unmodifiableList方法的应用场景

    java对象中primitive类型变量可以通过不提供set方法保证不被修改,但对象的List成员在提供get方法后,就可以随意add.remove改变其结构,这不是希望的结果.网上看了下,发现Col ...

随机推荐

  1. Mybatis轻松入门(仅要求会用看着一个就够了,略过源码分析部分即可)

    文章目录 ==一.概念== 二.快速入门 1.开发步骤 2.环境搭建 2.1.导入Mybatis的坐标和其他坐标 2.2.创建User表 2.3.编写实体 2.4.编写UserMapper配置文件 2 ...

  2. SpringBoot学习之thymeleaf的使用

    thymeleaf介绍 简单说, Thymeleaf 是一个跟 Velocity.FreeMarker 类似的模板引擎,它可以完全替代 JSP .相较与其他的模板引擎,它有如下三个极吸引人的特点: 1 ...

  3. 不用调整Nginx,SpringBoot也能解决前端访问的跨域问题

    1.什么情况下会出现跨域问题 通常,在前端工程师的开发过程中,往往在本地机器启动前端服务, 而调用的后端接口服务是在另外一台机器运行,这时就会出现跨域问题,让接口无法调通. 而到了测试环境和生产环境, ...

  4. MySQL主库手动复制至从库

    原文转自:https://www.cnblogs.com/itzgr/p/10233932.html作者:木二 目录 一 主库手动复制至从库 1.1 Master主库锁表 1.2 主库备份 1.3 从 ...

  5. MySQL-SQL基础-子查询

    #子查询-某些情况下,当进行查询的时候,需要的条件是另外一个select语句的结果,这个时候就要用到子查询.用于子查询的关键字主要包括: in.not in.=.!=.exists.not exist ...

  6. 如何获取 Android CPU 核心数 (Java/C++)

    1 前言 最近学习Power HAL方面相关知识,透过Power HAL 去配置CPU的Freq需要先确定 CPU 核数.便先了解如何获取 Android CPU 核数. 2 Java层获取方式 // ...

  7. Jenkins 使用PowerShell插件部署Net5项目

    Jenkins安装 PowerShell plugin 插件 新建自由项目 拖到 构建 处,添加 PowerShell 构建 贴入下方脚本即可 # 变量 $ProjectPath = "E: ...

  8. Java程序中使用Spire Jar包报java.lang.NoSuchMethodError类型错误的解决方法

    Jar包功能概述 使用Spire系列的Jar包可以操作Word.Excel.PPT.PDF.Barcode等格式的文件,分别对应使用的jar包是Spire.Doc for Java.Spire.XLS ...

  9. Linux从头学11:理解了这三个概念,才能彻底理解任务管理和任务切换

    作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...

  10. TCP协议中的TIME_WAIT详细说明

    文章目录 4.3设置TIME_WAIT状态的目的 4.3.1 实现TCP全双工连接的关闭 4.3.2 使过时的重复报文段失效 4.3.3 TIME_WAIT状态的自结束 4.3.4 TIME_WAIT ...