Java Unsafe类实现原理及测试代码
作者:bf378 发布时间:2023-03-10 21:18:13
Unsafe类介绍
第一次看到这个类时被它的名字吓到了,居然还有一个类自名Unsafe?读完本文,大家也能发现Unsafe类确实有点不那么安全,它能实现一些不那么常见的功能。
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。
Unsafe类提供了以下这些功能:
一、内存管理。包括分配内存、释放内存等。
该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。
利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。
二、非常规的对象实例化。
allocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。
这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。
三、操作类、对象、变量。
这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。
通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。
四、数组操作。
这部分包括了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。
由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。
五、多线程同步。包括锁机制、CAS操作等。
这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。
其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。
Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。
六、挂起与恢复。
这部分包括了park、unpark等方法。
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。
七、内存屏障。
这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的,用于定义内存屏障,避免代码重排序。
loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。
测试代码
import com.User;
import org.junit.Before;
import org.junit.Test;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
class User {
public static String USER_CLASS_NAME = "User.class";
private int age;
private String name;
public int getAge() {
return age;
}
public String getName() {
return name;
}
public User(int age, String name) {
this.age = age;
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
public class LockTests {
Unsafe unSafe;
User u = new User(17, "zhangsan");
@Before
public void before() throws Exception {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
unSafe = (Unsafe) theUnsafeField.get(Unsafe.class);
}
@Test
public void objectFieldOffset() throws Exception {
// unSafe偏底层的一个Java工具类
java.util.List users = new ArrayList();
for (int i = 0; i < 10; i++) {
Field ageField = User.class.getDeclaredField("age");
User u = new User(18, "daxin");
users.add(u);
//使用内存获取User age字段在内存中的 offset
// 是相对地址,不是一个绝对地址
long ageOffset = unSafe.objectFieldOffset(ageField);
// 每次都相同
System.out.println("ageOffset = " + ageOffset);
}
}
@Test
public void compareAndSwapInt() throws Exception {
// unSafe偏底层的一个Java工具类
Field ageField = User.class.getDeclaredField("age");
User u = new User(18, "daxin");
//使用内存获取User age字段在内存中的 offset
long ageOffset = unSafe.objectFieldOffset(ageField);
// 修改之前的值
System.out.println(u.getAge());
// 进行CAS更新, 由于设置18 因此CAS 会成功
unSafe.compareAndSwapInt(u, ageOffset, 18, 20);
System.out.println(u.getAge());
// 由于age设置20 进行CAS失败
unSafe.compareAndSwapInt(u, ageOffset, 18, 22);
System.out.println(u.getAge());
}
@Test
public void ensureClassInitialized() {
System.out.println("==== start ====");
unSafe.ensureClassInitialized(ClassIsLoad.class);
// 再次 确认不会报错
unSafe.ensureClassInitialized(ClassIsLoad.class);
}
/**
* AQS 底层的Node链表就是基于这个工具实现的 。
*
* @throws Exception
*/
@Test
public void getValueByFieldOffset() throws Exception {
for (int i = 0; i < 10; i++) {
User u = new User(18, UUID.randomUUID().toString().substring(i, 20));
int age = unSafe.getInt(u, 12L);
System.out.println("age = " + age);
// 获取名字 field offset
Field nameField = User.class.getDeclaredField("name");
long nameOffset = unSafe.objectFieldOffset(nameField);
System.out.println("nameOffset = " + nameOffset);
String name = unSafe.getObject(u, nameOffset) + "";
System.out.println("name = " + name);
}
}
@Test
public void pageSize() {
System.out.println("unSafe.pageSize() = " + unSafe.pageSize());
}
/**
* AtomicInteger 底层是基于getAndAddInt实现
*/
@Test
public void getAndAddInt() throws InterruptedException {
User u = new User(17, "zhangsan");
CountDownLatch downLatch = new CountDownLatch(10);
System.out.println("u.getAge() = " + u.getAge());
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
downLatch.countDown();
int val = unSafe.getAndAddInt(u, 12L, 1);
System.out.println(Thread.currentThread().getName() + " val = " + val);
}
}).start();
}
Thread.sleep(5000);
System.out.println("u.getAge() = " + u.getAge());
}
@Test
public void getAndSetInt() throws InterruptedException {
User u = new User(17, "zhangsan");
CountDownLatch downLatch = new CountDownLatch(10);
System.out.println("u.getAge() = " + u.getAge());
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
downLatch.countDown();
int val = unSafe.getAndSetInt(u, 12L, 10);
System.out.println(Thread.currentThread().getName() + " val = " + val);
}
}).start();
}
Thread.sleep(5000);
System.out.println("u.getAge() = " + u.getAge());
}
@Test
public void getIntVolatile() {
for (int i = 0; i < 10; i++) {
u.setAge(i);
/**
* @param obj the object containing the field to modify.
* @param offset the offset of the integer field within <code>obj</code>.
* @return
*/
int age = unSafe.getIntVolatile(u, 12L);
System.out.println("age = " + age);
}
}
// 系统负载采样的接口
@Test
public void getLoadAverage() {
double[] nums = new double[8];
int val = unSafe.getLoadAverage(nums, 8);
System.out.println(val);
}
/**
* //内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
* public native void loadFence();
* <p>
* <p>
* 参见:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
*/
@Test
public void loadFence() {
//java.util.concurrent.locks.StampedLock.validate
unSafe.loadFence();
}
/**
* //内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
* public native void storeFence();
* 参见:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
*/
@Test
public void storeFence() {
}
/**
* //内存屏障,禁止load、store操作重排序
* public native void fullFence();
* 参见:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
*/
@Test
public void fullFence() {
}
@Test
public void shouldBeInitialized() {
boolean shouldBeInitialized = unSafe.shouldBeInitialized(String.class);
System.out.println(shouldBeInitialized);
shouldBeInitialized = unSafe.shouldBeInitialized(User.class);
System.out.println(shouldBeInitialized);
}
/**
* synchronized 的一种实现获取锁
*
* @throws InterruptedException
*/
@Test
public void monitorEnter() throws InterruptedException {
unSafe.monitorEnter(u);
new Thread(new Runnable() {
@Override
public void run() {
synchronized (u) {
System.out.println("==u lock got ==");
}
}
}).start();
Thread.sleep(2000);
unSafe.monitorExit(u);
}
@Test
public void compareAndSwap() {
// unSafe.compareAndSwapInt(对象, 对象中的字段偏移, 期望值, 设置值)
// unSafe.compareAndSwapLong(对象, 对象中的字段偏移, 期望值, 设置值)
// unSafe.compareAndSwapObject(对象, 对象中的字段偏移, 期望值, 设置值)
}
@Test
public void t() {
// 方法签名
// public void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset, long bytes)
// unSafe.copyMemory();
}
}
class ClassIsLoad {
static {
System.out.println("ClassIsLoad class Is Load !");
}
}
来源:https://www.cnblogs.com/leodaxin/p/13556615.html
猜你喜欢
- 实践过程效果代码public partial class Frm_Libretto : Form{ public
- 在本文中,我们将介绍二进制搜索相对于简单线性搜索的优势,并介绍它在 Java 中的实现。1. 需要有效的搜索假设我们在wine-sellin
- 概述 ScrollView也是一个容器,它是FrameLayout的子类,它的主要作用就是将超出物理屏幕的内容显示
- 1. 前言今天开始我们来一步步窥探它是如何工作的。我们又该如何驾驭它。本篇将通过 Spring Boot 2.x 来讲解 Spring Se
- 本文实例讲述了C#使用WebClient登录网站并抓取登录后的网页信息实现方法。分享给大家供大家参考,具体如下:C#登录网站实际上就是模拟浏
- 1,什么是字符编码? 字符(Character)是文字与符号的总称,包括文字、图形符号、数学符号等。一组
- 开发环境JDK1.8 eclipse struts2-2.3.31 1.创建web项目 2.导入struts2核心jar包 3.更改web.
- 本文实例为大家分享了Android播放音乐案例的具体实现代码,供大家参考,具体内容如下效果:分析:和上一篇文章的结构是一样的,只不过我们需要
- 泛型将集合中的元素限定为一个特定的类型。术语ArrayList<E> -- 泛型类型ArrayList -- 原始类型E --
- 这篇博客将梳理一下.NET中4个Timer类,及其用法。1. System.Threading.Timerpublic Timer(Time
- 虽然listview是过去式,但由于项目中还是有用listview,百度一番都是scrollview中的悬浮bar,没有看到有listvie
- 本文实例讲述了java实现变更文件查询的方法。分享给大家供大家参考。具体如下:自己经常发布包时需要查找那些文件时上次发包后更新的数据文件,所
- 笔者最近需要上位机与下位机进行数据交互,在广泛参考大佬的资料后,较为完善地使用Textbox控件进行数据输入的功能。程序段主要功能:实现输入
- ViewModel的创建方式在我们项目中, 引入了viewModel 做MVI 设计模式的组成部分,它是JetPack 组件库中的重要成员。
- Android getevent用法实例详解最近在测试设备按键的常用命令,感觉这些命令都有的,但就是不知道怎么查找。翻阅了几篇博
- 一、背景日常开发中,有时候需要根据某个 key 加锁,确保多线程情况下,对该 key 的加锁和解锁之间的代码串行执行。大家可以借助每个 ke
- 本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能。在Spring Boot应用程序中,我们可
- 每个使用Android手机的人应该对Android中的通知不陌生,下面我们就学习一下怎么使用Android中的通知。一、通知的基本用法活动、
- 本文实例讲述了WinForm中comboBox控件数据绑定实现方法。分享给大家供大家参考,具体如下:下面介绍三种对comboBox绑定的方式
- MD5加密MD5是由MD2、MD3、MD4演变过来的,虽然MD5加密算法现在有些人已经将其解开了,但是它的加密机制依然很强大,我想绝大对数还