一个名为不安全的类Unsafe
最近为了更加深入了解NIO的实现原理,学习NIO的源码时,遇到了一个问题。即在WindowsSelectorImpl中的
pollWrapper属性,当我点进去查看它的PollArrayWrapper类型时,发现它和AllocatedNativeObject类型有关,而AllocatedNativeObject继承了NativeObject类,随着又发现了NativeObject是基于一个Unsafe类实现的。不安全的类????
Unsafe
Unsafe,顾名思义,它真是一个不安全的类,那它为什么是不安全的呢?这就要从Unsafe类的功能说起。
学过C#的就可以知道,C#和Java的一个重要区别就是:C#可以直接操作一块内存区域,如自己申请内存和释放,而在Java中这是做不到的。而Unsafe类就可以让我们在Java中像C#一样去直接操作一块内存区域,正因为Unsafe类可以直接操作内存,意味着其速度更快,在高并发的条件之下能够很好地提高效率,所以java中很多并发框架,如Netty,都使用了Unsafe类。
虽然,Unsafe可以提高运行速度,但是因为Java本身是不支持自己直接操作内存的,这就意味着Unsafe类所做的操作不受jvm管理的,所以不会被GC(垃圾回收),需要我们手动GC,稍有不慎就会出现内存泄漏问题。且Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃。这就是为什么Unsafe被称为不安全的原因。Unsafe可以让你全力踩油门,提高自己的速度,但是它会让你的方向盘更难握稳,一不小心就可能导致车毁人亡。
源码查看
初始化
因为Unsafe的构造方法是private类型的,所以无法通过new方式实例化获取,只能通过它的getUnsafe()方法获取。又因为Unsafe是直接操作内存的,为了安全起见,Java的开发人员为Unsafe的获取设置了限制,所以想要获取它只能通过Java的反射机制来获取。
@CallerSensitive
public static Unsafe getUnsafe() {
//通过getCallerClass方法获取Unsafe类
Class var0 = Reflection.getCallerClass();
//如过该var0类不是启动类加载器(Bootstrap),则抛出异常
//正因为该判断,所以Unsafe只能通过反射获取
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
Reflection.getCallerClass():可以返回调用类或Reflection类,或者层层上传
VM.isSystemDomainLoader(ClassLoader var0):判断该类加载器是否是启动类加载器(Bootstrap)。
@CallerSensitive:为了防止黑客通过双重反射来提升权限,所以所有跟反射相关的接口方法都标注上CallerSensitive
所以使用下面的方式是获取不了Unsafe类的:
//使用这样的方式获取会抛出异常,因为是通过系统类加载器加载(AppClassLoader)
public class Test {
public static void main(String[] args) {
Unsafe unsafe = Unsafe.getUnsafe();
}
}
那怎么才用使用启动类加载Unsafe类并获取它呢?在Unsafe类的最下面的static代码块中有这样一段代码:
private static final Unsafe theUnsafe;
//.....
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
theUnsafe = new Unsafe();
//......
}
学过反射机制看过以上代码就可以知道我们可以通过getDeclaredField()返回获取Un safe类的theUnsafe属性,然后通过该属性获取Unsafe类的实例,因为在Unsafe类里的theUnsafe属性已经被new实例化了。
public class Test {
public static void main(String[] args) throws Exception {
//通过getDeclaredField方法获取Unsafe类中名为theUnsafe的属性
//注意,该属性是private类型的,所以不能用getField获取,只能用getDeclaredField
Field field = Unsafe.class.getDeclaredField("theUnsafe");
//将该属性设为可访问
field.setAccessible(true);
//实例该属性并转为Unsafe类型
//因为theUnsafe属性是Unsafe类所在的包的启动类加载的,所以可以成功获得
Unsafe unsafe = (Unsafe)field.get(null);
}
}
获取偏移量方法
偏移量
在实际模式中,内存是被分成段的,如果想要获取内存中的某个储存单元,需知道储存单元的所在段地址(段头)和偏移量,即使你知道该储存单元的实际地址。而偏移量就是实际地址与所在段地址(段头)的距离,偏移量=实际地址-所在段地址(段头)。
举个例子,假设有个书架,我需要找由左到右、由上到下数的第1024本书,那我只能一本本的数,直到数到第1024本,但如果我知道书架的第4层的第一本书是第1000本书,那我只用从第1000本书开始数,数到1024,只需数1024-1000=24本。在这里,书架是内存,要找的书就是储存单元,书架的第4层就是内存段,第4层的第一本书即书架的第1000本书就是段地址(段头),第1024本书就是实际地址,而偏移量的就是第1000本书到第1024本书的距离24.
public native long objectFieldOffset(Field var1);
获取非静态变量var1的偏移量。
public native long staticFieldOffset(Field var1);
获取静态变量var1的偏移量。
public native Object staticFieldBase(Field var1);
获取静态变量var1的实际地址,配合staticFieldOffset方法使用,可求出变量所在的段地址
public native int arrayBaseOffset(Class<?> var1);
获取数组var1中的第一个元素的偏移量,即数组的基础地址。
在内存中,数组的存储是以一定的偏移量增量连续储存的,如数组的第一个元素的实际地址为24,偏移量为4,而数组的偏移量增量为1,那数组的第二个元素的实际地址就是25,偏移量为5.
public native int arrayIndexScale(Class<?> var1);
获取数组var1的偏移量增量。结合arrayBaseOffset(Class<?> var1)方法就可以求出数组中各个元素的地址。
操作属性方法
public native Object getObject(Object var1, long var2);
获取var1对象中偏移量为var2的Object对象,该方法可以无视修饰符限制。相同方法有getInt、getLong、getBoolean等。
public native void putObject(Object var1, long var2, Object var4);
将var1对象中偏移量为var2的Object对象的值设为var4,该方法可以无视修饰符限制。相同的方法有putInt、putLong、putBoolean等。
public native Object getObjectVolatile(Object var1, long var2);
功能与getObject(Object var1, long var2)一样,但该方法可以保证读写的可见性和有序性,可以无视修饰符限制。相同的方法有getIntVolatile、getLongVolatile、getBooleanVolatile等。
public native void putObjectVolatile(Object var1, long var2, Object var4);
功能与putObject(Object var1, long var2, Object var4)一样,但该方法可以保证读写的可见性和有序性,可以无视修饰符限制。相同的方法有putIntVolatile、putLongVolatile、putBooleanVolatile等。
public native void putOrderedObject(Object var1, long var2, Object var4);
功能与putObject(Object var1, long var2, Object var4)一样,但该方法可以保证读写的有序性(不保证可见性),可以无视修饰符限制。相同的方法有putOrderedInt、putOrderedLong等。
操作内存方法
public native int addressSize();
获取本地指针大小,单位为byte,通常值为4或8。
public native int pageSize();
获取本地内存的页数,该返回值会是2的幂次方。
public native long allocateMemory(long var1);
开辟一块新的内存块,大小为var1(单位为byte),返回新开辟的内存块地址。
public native long reallocateMemory(long var1, long var3);
将内存地址为var3的内存块大小调整为var1(单位为byte),返回调整后新的内存块地址。
public native void setMemory(long var2, long var4, byte var6);
从实际地址var2开始将后面的字节都修改为var6,修改大小为var4(通常为0)。
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
从对象var1中偏移量为var2的地址开始复制,复制到var4中偏移量为var5的地址,复制大小为var7。
当var1为空时,var2就不是偏移量而是实际地址,当var4为空时,var5就不是偏移量而是实际地址。
public native void freeMemory(long var1);
释放实际地址为var1的内存。
线程挂起和恢复方法
public native void unpark(Object var1);
将被挂起的线程var1恢复,由于其不安全性,需保证线程var1是存活的。
public native void park(boolean var1, long var2);
当var2等于0时,线程会一直挂起,知道调用unpark方法才能恢复。
当var2大于0时,如果var1为false,这时var2为增量时间,即线程在被挂起var2秒后会自动恢复,如果var1为true,这时var2为绝对时间,即线程被挂起后,得到具体的时间var2后才自动恢复。
CAS方法
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
CAS机制相关操作,对对象var1里偏移量为var2的变量进行CAS修改,var4为期待值,var5为修改值,返回修改结果。相同方法有compareAndSwapInt、compareAndSwapLong。
类加载方法
public native boolean shouldBeInitialized(Class<?> var1);
判断var1类是否被初始。
public native void ensureClassInitialized(Class<?> var1);
确保var1类已经被初始化。
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
定义一个类,用于动态的创建类。var1为类名,var2为类的文件数据字节数组,var3为var2的输入起点,var4为输入长度,var5为加载该类的加载器,var6为保护领域。返回创建后的类。
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
用于动态的创建匿名内部类。var1为需创建匿名内部类的类,var2为匿名内部类的文件数据字节数组,var3为修补对象。返回创建后的匿名内部类。
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
创建var1类的实例,但是不会调用var1类的构造方法,如果var1类还没有初始化,则进行初始化。返回创建实例对象。
内存屏障方法
public native void loadFence();
所有读操作必须在loadFence方法执行前执行完毕。
public native void storeFence();
所有写操作必须在storeFence方法执行前执行完毕。
public native void fullFence();
所有读写操作必须在fullFence方法执行前执行完毕。
疑惑
看到这里可能有人会有一个疑惑,为什么这些方法都没有具体的功能实现代码呢?
在文章开头时就说过,Java不支持直接操作内存,那怎么可能用Java来具体实现功能呢。你可以发现Unsafe类内的大多方法都有native修饰符,native接口可以让你调用本地的代码文件(包括其他语言,如c语言),既然Java实现不了,那就让能实现的人来做,所以Unsafe的底层实现语言其实是C语言,这也是为什么Unsafe类内会有偏移量和指针这些Java中没有的概念了。
(以上为本人自己对Unsafe类的理解,如果有错误,欢迎各位前辈指出)
一个名为不安全的类Unsafe的更多相关文章
- C++创建一个名为Ellipse的椭圆类--练习
题目描述: /*设计名为Ellipse的椭圆类*/ /* 其属性为外接矩形的左上角与右下角两个点的坐标,并能计算出椭圆的面积,并测试该类. */ 代码如下: #include<iostream& ...
- 16.按要求编写Java应用程序。 编写一个名为Test的主类,类中只有一个主方法; 在主方法中定义一个大小为50的一维整型数组,数组名为x,数组中存放着{1, 3,5,…,99}输出这个数组中的所有元素,每输出十个换一行;在主方法中定义一 个大小为10*10的二维字符型数组,数组名为y,正反对角线上存的是‘*’,其余 位置存的是‘#’;输出这个数组中的所有元素。
//分类 package com.bao; public class Shuchu { int[]yi=new int[50]; String[][]er=new String[10][10]; vo ...
- 按要求编写Java应用程序。 编写一个名为Test的主类,类中只有一个主方法; 在主方法中定义一个大小为50的一维整型数组,数组名为x,数组中存放着{1, 3,5,…,99}输出这个数组中的所有元素,每输出十个换一行;在主方法中定义一 个大小为10*10的二维字符型数组,数组名为y,正反对角线上存的是‘*’,其余 位置存的是‘#’;输出这个数组中的所有元素。
int[]x=new int [50]; char[][]y=new char[10][10]; int j=1,w=0; for(int i=0;i<50;i++) { x[i]=j; j+= ...
- 编写一个名为Test的主类,类中只有一个主方法; 在主方法中定义一个大小为50的一维整型数组,数组名为x,数组中存放着{1, 3,5,…,99}输出这个数组中的所有元素,每输出十个换一行;在主方法中定义一 个大小为10*10的二维字符型数组,数组名为y,正反对角线上存的是‘*’,其余 位置存的是‘#’;输出这个数组中的所有元素。
package liu0915; import java.util.Random; public class Test0915sz { public static void main(String[] ...
- 3.实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty 和Staff。
23.实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty 和Staff. 具体要求如下: (1)Person类中的属性有:姓名name(String类型) ...
- 实现一个名为Person的类和它的子类Employee,Manager是Employee的子类,设计一个方法add用于涨工资,普通员工一次能涨10%,经理能涨20%。
1.实现一个名为Person的类和它的子类Employee,Manager是Employee的子类,设计一个方法add用于涨工资,普通员工一次能涨10%,经理能涨20%.具体要求如下:(1)Perso ...
- C# 随机给一个全部信息都未知的类类型,如何获取该类的类名、属性个数、属性名、属性的数据类型、属性值?
一.场景假设 假设现在有一个泛型类T的实例对象t,该T类的全部信息都未知. 要求:打印输出实例对象t的类名.属性个数.属性名.属性的数据类型.属性值. 二.解决问题 1.我们根据输出的内容要求定义一个 ...
- Java中的魔法类-Unsafe
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别.不安全操作的方法,如直接访问系统内存资源.自主管理内存资源等,这些方法在提升Java运行效率.增强Java语言底层资源操作能 ...
- WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。
WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping.请添加一个名为 jquery (区分大小写)的 ScriptRes ...
随机推荐
- P2590 树的统计
一道树剖的模板题 首先,由于本人比较懒,就把单点修改写成了区间修改,其实也没有有多大区别(关键是我不会写单点修改QAQ) 不得不说,树剖的码量比较大,调了一上午才勉强调完. 这道题要求我们支持 单点修 ...
- Jmeter之『多变量循环』
假设存在两个参数a,b,需要在一个循环内,同时遍历a_1,a_2,a_3,b_1,b_2,b_3 添加一个循环控制器,循环次数为变量的大小 添加一个计数器,引用名称为index(用于拼接变量名称) 同 ...
- Lane-Detection 近期车道线检测论文阅读总结
近期阅读的几篇关于车道线检测的论文总结. 1. 车道线检测任务需求分析 1.1 问题分析 针对车道线检测任务,需要明确的问题包括: (1)如何对车道线建模,即用什么方式来表示车道线. 从应用的角度来说 ...
- idea如何新建一个springmvc 工程
java 版本 1.8.0_261 idea 版本2020.1 Tomcat 9 maven 3.6 新建工程 File->new->project 默认会下载springframewo ...
- 解决SpringBoot 定时计划 quartz job 任务重复执行多次(10次)
上一篇:SpringBoot多任务Quartz动态管理Scheduler,时间配置,页面+源 设置了多个 任务,本应该是各司其职的,任务调用多线程处理任务,but这个定时任务竟然同时跑了10次???如 ...
- 多测师讲解unittest介绍及自动化测试实现流程_高级讲师肖sir
unittest框架介绍 unittest框架是python中一个标准的库中的一个模块,该模块包括许多的类如 test case类.test suit类.texttest runner类.textte ...
- 多测师讲解html _表格标签007_高级讲师肖sir
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>表 ...
- docker设置http访问
1 编辑配置文件 vim /etc/docker/daemon.json { "registry-mirrors": ["https://a4fyjv0u.mirr ...
- linux 压缩命令 zip
1.zip命令 例如:zip -r mysql.zip mysql 该句命令的含义是:将mysql文件夹压缩成mysql.zip zip -r abcdef.zip abc def.txt 这句命令的 ...
- js获取页面高度
<script> function getInfo() { var s = ""; s += " 网页可见区域宽:"+ document.body. ...