返回顶部
首页 > 资讯 > 后端开发 > Python >Java基础之Unsafe内存操作不安全类详解
  • 144
分享到

Java基础之Unsafe内存操作不安全类详解

2024-04-02 19:04:59 144人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

简介 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着 1、不受JVM管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏

简介

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着

1、不受JVM管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏。

2、Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。

3、直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。

Unsafe 类


public final class Unsafe

Unsafe类是"final"的,不允许继承。

Unsafe 属性


private static final Unsafe theUnsafe;
public static final int INVALID_FIELD_OFFSET = -1;
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
public static final int ARRAY_BYTE_BASE_OFFSET;
public static final int ARRAY_SHORT_BASE_OFFSET;
public static final int ARRAY_CHAR_BASE_OFFSET;
public static final int ARRAY_INT_BASE_OFFSET;
public static final int ARRAY_LONG_BASE_OFFSET;
public static final int ARRAY_FLOAT_BASE_OFFSET;
public static final int ARRAY_DOUBLE_BASE_OFFSET;
public static final int ARRAY_OBJECT_BASE_OFFSET;
public static final int ARRAY_BOOLEAN_INDEX_SCALE;
public static final int ARRAY_BYTE_INDEX_SCALE;
public static final int ARRAY_SHORT_INDEX_SCALE;
public static final int ARRAY_CHAR_INDEX_SCALE;
public static final int ARRAY_INT_INDEX_SCALE;
public static final int ARRAY_LONG_INDEX_SCALE;
public static final int ARRAY_FLOAT_INDEX_SCALE;
public static final int ARRAY_DOUBLE_INDEX_SCALE;
public static final int ARRAY_OBJECT_INDEX_SCALE;
public static final int ADDRESS_SIZE;

这些属性都是在类加载时初始化,它们都是一些类型数组指针。

Unsafe 静态加载


static {
	reGISterNatives();
	Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
	theUnsafe = new Unsafe();
	ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
	ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
	ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
	ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
	ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
	ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
	ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
	ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
	ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
	ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
	ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
	ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
	ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
	ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
	ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
	ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
	ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
	ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
	ADDRESS_SIZE = theUnsafe.addressSize();
}
private static native void registerNatives();

Unsafe 构造函数


private Unsafe() {
}

Unsafe 对象不能直接通过 new Unsafe(),它的构造函数是私有的。

Unsafe 实例化方法


public static Unsafe getUnsafe() {
	Class var0 = Reflection.getCallerClass();
	if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
		throw new SecurityException("Unsafe");
	} else {
		return theUnsafe;
	}
}

getUnsafe 只能从引导类加载器(bootstrap class loader)加载,非启动类加载器直接调用 Unsafe.getUnsafe() 方法会抛出 SecurityException 异常。解决办法:

1、可以令代码 " 受信任 "。运行程序时,通过 JVM 参数设置 bootclasspath 选项,指定系统类路径加上使用的一个 Unsafe 路径。


java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.Test

2、通过 Java 反射机制,暴力获取。


Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

Unsafe 内存管理


// 获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法。
public native int addressSize();
// 获取本地内存的页数,此值为2的幂次方。
public native int pageSize();
// 分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址。
public native long allocateMemory(long var1);
// 通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)。
public native long reallocateMemory(long var1, long var3);
// 将给定内存块中的所有字节设置为固定值(通常是0)
public native void setMemory(Object var1, long var2, long var4, byte var6);
// 内存复制
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 清除内存
public native void freeMemory(long var1);

注意:allocateMemory方法申请的内存,将直接脱离jvm,gc将无法管理该方式申请的内存,用完一定要手动释放内存,防止内存溢出;
JDK中示例:ByteBuffer.allocateDirect(int capacity)使用DirectByteBuffer,DirectByteBuffer中就是用allocateMemory申请堆外内存。

Unsafe 获取偏移量


// 返回指定变量所属类中的内存偏移量
public native long objectFieldOffset(Field var1);
// 获取数组中第一个元素的地址
public native int arrayBaseOffset(Class<?> var1);
// 获取静态变量地址偏移值
public native long staticFieldOffset(Field var1);
// 其实就是数据中元素偏移地址的增量,数组中的元素的地址是连续的
public native int arrayIndexScale(Class<?> var1);

Unsafe 检查类初始化


// 检测给定的类是否需要初始化。
// 当ensureClassInitialized方法不生效的时候才返回false
public native boolean shouldBeInitialized(Class<?> c);
// 检测给定的类是否已经初始化。
public native void ensureClassInitialized(Class<?> c);

Unsafe 从指定位置读取


// 从指定内存地址处开始读取一个byte
public native byte getByte(long var1);
// 从指定内存地址处开始读取一个short 
public native short getShort(long var1);
// 从指定内存地址处开始读取一个char 
public native char getChar(long var1);
// 从指定内存地址处开始读取一个int 
public native int getInt(long var1);
// 从指定内存地址处开始读取一个long 
public native long getLong(long var1);
// 从指定内存地址处开始读取一个float 
public native float getFloat(long var1);
// 从指定内存地址处开始读取一个double 
public native double getDouble(long var1);

Unsafe 向指定位置写值


// 向指定位置写入一个int 
public native void putInt(long var1, int var3);
// 向指定位置写入一个char 
public native void putChar(long var1, char var3);
// 向指定位置写入一个byte 
public native void putByte(long var1, byte var3);
// 向指定位置写入一个short 
public native void putShort(long var1, short var3);
// 向指定位置写入一个long 
public native void putLong(long var1, long var3);
// 向指定位置写入一个float 
public native void putFloat(long var1, float var3);
// 向指定位置写入一个double 
public native void putDouble(long var1, double var3);

Unsafe 对象操作

从指定偏移量处读取对象属性(非主存)


public native int getInt(Object var1, long var2);
public native Object getObject(Object var1, long var2);
public native boolean getBoolean(Object var1, long var2);
public native byte getByte(Object var1, long var2);
public native short getShort(Object var1, long var2);
public native char getChar(Object var1, long var2);
public native long getLong(Object var1, long var2);
public native float getFloat(Object var1, long var2);
public native double getDouble(Object var1, long var2);

向指定偏移量处修改对象属性(非主存)


public native void putInt(Object var1, long var2, int var4);
public native void putObject(Object var1, long var2, Object var4);
public native void putBoolean(Object var1, long var2, boolean var4);
public native void putByte(Object var1, long var2, byte var4);
public native void putShort(Object var1, long var2, short var4);
public native void putChar(Object var1, long var2, char var4);
public native void putLong(Object var1, long var2, long var4);
public native void putFloat(Object var1, long var2, float var4);
public native void putDouble(Object var1, long var2, double var4);

向指定偏移量处修改对象属性(主存)


public native Object getObjectVolatile(Object var1, long var2);
public native int getIntVolatile(Object var1, long var2);
public native boolean getBooleanVolatile(Object var1, long var2);
public native byte getByteVolatile(Object var1, long var2);
public native short getShortVolatile(Object var1, long var2);
public native char getCharVolatile(Object var1, long var2);
public native long getLongVolatile(Object var1, long var2);
public native float getFloatVolatile(Object var1, long var2);
public native double getDoubleVolatile(Object var1, long var2);

向指定偏移量处修改对象属性(主存)


public native void putObjectVolatile(Object var1, long var2, Object var4);
public native void putIntVolatile(Object var1, long var2, int var4);
public native void putBooleanVolatile(Object var1, long var2, boolean var4);
public native void putByteVolatile(Object var1, long var2, byte var4);
public native void putShortVolatile(Object var1, long var2, short var4);
public native void putCharVolatile(Object var1, long var2, char var4);
public native void putLongVolatile(Object var1, long var2, long var4);
public native void putFloatVolatile(Object var1, long var2, float var4);
public native void putDoubleVolatile(Object var1, long var2, double var4);
public native void putOrderedObject(Object var1, long var2, Object var4);
public native void putOrderedObject(Object var1, long var2, Object var4);
public native void putOrderedInt(Object var1, long var2, int var4);
public native void putOrderedLong(Object var1, long var2, long var4);

Unsafe CAS操作


public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapint(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

针对对象进行CAS操作,本质更新对象中指定偏移量的属性,当原值为var4时才会更新成var5并返回true,否则返回false。
举例:volatile i=0;有多个线程修改i的值,A线程只有在i=1时修改为2,如果代码如下


if (i == 1) {i = 2;} 

这样时有问题的,if比较完i可能已经被别人修改了,这种场景特别适合CAS,使用CAS代码如下


boolean isUpdate =  compareAndSwapInt(object, offset, 1, 2)

相当于读->判断->写一次搞定(实在不能理解CAS,可以这么理解)

Unsafe 线程的挂起和恢复


public native void park(boolean var1, long var2);

阻塞当前线程直到一个unpark方法出现(被调用)、一个用于unpark方法已经出现过(在此park方法调用之前已经调用过)、线程被中断或者time时间到期(也就是阻塞超时)。在time非零的情况下,如果isAbsolute为true,time是相对于新纪元之后的毫秒,否则time表示纳秒。这个方法执行时也可能不合理地返回(没有具体原因)。并发包java.util.concurrent中的框架对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe#park()方法。


public native void unpark(Object var1);

释放被park创建的在一个线程上的阻塞。这个方法也可以被使用来终止一个先前调用park导致的阻塞。这个操作是不安全的,因此必须保证线程是存活的(thread has not been destroyed)。从Java代码中判断一个线程是否存活的是显而易见的,但是从native代码中这机会是不可能自动完成的。

Unsafe 内存屏障


public native void loadFence();

在该方法之前的所有读操作,一定在load屏障之前执行完成。


public native void storeFence();

在该方法之前的所有写操作,一定在store屏障之前执行完成


public native void fullFence();

在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能。

Unsafe 其他


public native int getLoadAverage(double[] loadavg, int nelems);

获取系统的平均负载值,loadavg这个double数组将会存放负载值的结果,nelems决定样本数量,nelems只能取值为1到3,分别代表最近1、5、15分钟内系统的平均负载。如果无法获取系统的负载,此方法返回-1,否则返回获取到的样本数量(loadavg中有效的元素个数)。实验中这个方法一直返回-1,其实完全可以使用JMX中的相关方法替代此方法。


public native void throwException(Throwable ee);

绕过检测机制直接抛出异常。

到此这篇关于Java基础之Unsafe内存操作不安全类详解的文章就介绍到这了,更多相关Unsafe内存操作不安全类内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java基础之Unsafe内存操作不安全类详解

本文链接: https://lsjlt.com/news/127691.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java基础之Unsafe内存操作不安全类详解
    简介 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着 1、不受jvm管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏...
    99+
    2024-04-02
  • Java基础详解之内存泄漏
    目录一、什么是内存泄漏二、Java内存泄漏引起的原因三、内存泄漏的危害一、什么是内存泄漏 内存泄漏是指你向系统申请分配内存进行使用(new/malloc),然后系统在堆内存中给这个对...
    99+
    2024-04-02
  • Java 基础之内部类详解及实例
     Java 基础之内部类详解及实例内部类不是很好理解,但说白了其实也就是一个类中还包含着另外一个类如同一个人是由大脑、肢体、器官等身体结果组成,而内部类相当于其中的某个器官之一,例如心脏:它也有自己的属性和行为(血液、跳动)显然,...
    99+
    2023-05-31
    java 内部类 ava
  • Java基础之Object类详解
    object类的介绍 object是所有类的直接父类或者是间接父类,为什么这么说呢? 可以查询java8的API帮助文档: 可见在这样的一个类树中,所有的类的根还是Object类 ...
    99+
    2024-04-02
  • Java基础之内存泄漏与溢出详解
    目录一、浅析二、Java内存泄露三、Java内存溢出一、浅析 内存泄露( memory leak):是指程序在申请内存后,无法释放已申请的内存空间,多次内存泄露堆积后果很严重,内存迟...
    99+
    2024-04-02
  • Java基础类之ArrayUtils工具类详解
    目录1、引入pom依赖2、ArrayUtils中的方法:3、举例总结1、引入pom依赖 <!-- https://mvnrepository.com/artifact/o...
    99+
    2024-04-02
  • Java基础类学习之String详解
    目录1 String不可变性2 不可变的好处3 String+和StringBuilder效率差异4 String, StringBuffer and StringBuilder5 ...
    99+
    2022-12-27
    Java String类 Java String
  • Java基础类库之StringBuffer类用法详解
    StringBuffer类 String类是在所有开发项目开发之中一定会使用的一个功能类,并且这个类拥有如下功能 每个字符串的常量都属于一个String类匿名对象,并且不能修改;St...
    99+
    2024-04-02
  • Node.js基础入门之缓存区与文件操作详解
    目录缓存区1. 什么是缓存区?2. 创建指定长度的缓存区3. 通过数组创建缓存区4. 通过字符串创建缓存区5. 读写缓存区6. 复制缓存区文件操作1. 异步直接读取2. 同步直接读取...
    99+
    2024-04-02
  • Java基础之堆内存溢出的解决
    目录一、实战-内存溢出二、实战-堆内存溢出三、堆内存溢出演示代码四、Eclipse Memory Analyzer 分析流程五、堆内存溢出的场景六、商城项目可能存在堆内存溢出的方法一...
    99+
    2024-04-02
  • Git基础学习之分支基本操作详解
    目录1、创建分支(1)创建分支(2)图示理解2、查看分支列表3、分支切换4、查看所有分支的最后一个提交5、删除分支1、创建分支 (1)创建分支 Git 是怎么创建新分支的呢? 很简单...
    99+
    2022-11-13
    Git分支基本操作 Git分支操作 Git基础学习
  • Java基础之枚举Enum类案例详解
    目录一、文章序言二、代码实践三、面试相关一、文章序言 Java中引用类型:数组、类、接口、枚举、注解 枚举这个既熟悉又陌生的东西具体再哪里可以使用呢? 什么是枚举? 枚举是一个引用...
    99+
    2024-04-02
  • django基础之数据库操作方法(详解)
    Django 自称是“最适合开发有限期的完美WEB框架”。本文参考《Django web开发指南》,快速搭建一个blog 出来,在中间涉及诸多知识点,这里不会详细说明,如果你是第一次接触Django ,本文...
    99+
    2022-06-04
    详解 操作方法 数据库
  • Git基础学习之tag标签操作详解
    目录共享标签推送本地的指定标签推送本地所有为推送的标签查看结果删除标签删除本地标签删除远程标签修改标签指定提交的代码标签在.git目录中的位置本文中所使用到的命令共享标签 默认情况下...
    99+
    2022-11-13
    Git 标签tag使用 Git 标签tag Git 标签
  • MySQL数据库基础学习之JSON函数各类操作详解
    目录前言一、jsON语法规则二、JSON函数1.JSON_CONTAINS(json_doc,value)函数2.JSON_SEARCH()函数 3.JSON_PRETTY(json_doc)函数4.JSON_...
    99+
    2023-02-17
    MySQL JSON函数操作 MySQL JSON函数 MySQL JSON
  • web安全php基础_php之string对象详解
    PHP 字符串 字符串变量用于包含有字符的值。 在创建字符串之后,我们就可以对它进行操作了。您可以直接在函数中使用字符串,或者把它存储在变量中。 在下面的实例中,我们创建一个名为 txt 的字符串变量...
    99+
    2023-08-31
    php android 开发语言 phpstring
  • Linux系统诊断之内存基础深入详解
    1.背景 谈及linux内存,很多时候,我们会关注free,top等基础命令。当系统遇到异常情况时,内存问题的根因追溯,现场诊断时,缺乏深层次的debug能力。本篇幅不做深层讨论,能把当前系统的问题描述清楚,是每个SRE...
    99+
    2022-06-04
    Linux系统诊断 Linux内存基础
  • Java基础之详解基本数据类型的使用
    一、整型 主要扩展一下不同进制的整型 二进制、八进制、十进制、十六进制 * 二进制 : 0B(数字零+B) 0b(数字零+b) * 八进制 :0(数字零开头) * 十进制 :正常写...
    99+
    2024-04-02
  • Java基础知识之I/O流和File类文件操作
    目录♒I/O流原理及流的分类I/O原理I/O流的分类️文件(File)概念✍️常用操作(File类)总结♒I/O流原理及流的分类 I/O原理 I/O是Input和Output的缩写,...
    99+
    2024-04-02
  • Linux网络设置之基础操作命令详解
    目录查看网络配置查看网络接口信息---ifconfig查看路由表---route查看网络连接情况---netstat获取 socket 统计信息---ss测试网络连接测试网络连通性------ping测试DNS域名解析-...
    99+
    2022-06-04
    Linux系统的网络设置 Linux网络设置命令
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作