废话

  个人理解:java 出现的原因之一,就是对内存的管理;在c/c++,内存可以随心使用,超高的性能也伴有极高的风险;java极大的规避了这种风险,却也降低了程序运行的性能;那么java是否提供直接操作内存的方法呢?当然:Unsafe 类就是java提供的,对系统硬件级别的底层操作;

1,Unsafe 的获取方法:

  Unsafe 位于sun.misc包下,通常eclipse限制了对该类的直接使用,并且也不能通过Unsafe提供的getUnsafe() 方法获取到该类的实例,因为你的类不被该类所信任;具体到源码:

  1. @CallerSensitive
  2. public static Unsafe getUnsafe() {
  3. Class var0 = Reflection.getCallerClass();
  4. if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
  5. throw new SecurityException("Unsafe");
  6. } else {
  7. return theUnsafe;
  8. }
  9. }

  在方法上有一个@CallerSensitive注解,该注解表示该方法的调用,需要调用者被该方法信任;那么怎么获取到Unsafe的实例呢?解决方法如下:

  利用反射机制 ,Unsafe中有一个字段名为“theUnsafe”,该字段保存有一个Unsafe的实例,只要获取在该字段上的Unsafe实例就好了,代码如下:

  1. @SuppressWarnings("restriction")
  2. static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
  3. Class<?> cls = sun.misc.Unsafe.class;
  4. Field[] fields = cls.getDeclaredFields();
  5. for(Field f : fields) {
  6. if("theUnsafe".equals(f.getName())) {
  7. f.setAccessible(true);
  8. return (sun.misc.Unsafe) f.get(null);
  9. }
  10. }
  11. throw new IllegalAccessException("no declared field: theUnsafe");
  12. }

2,Unsafe 获取对象字段偏移量,及修改偏移量对应字段的值,代码如下:

  1. import java.lang.reflect.Field;public class TestUnsafe {
  2.  
  3. static private int number = 5;
  4.  
  5. private String c;
  6.  
  7. @SuppressWarnings({ "restriction" })
  8. public static void main(String[] args) throws Throwable {
  9.  
  10. TestUnsafe t = new TestUnsafe();
  11.  
  12. sun.misc.Unsafe unsafe = getUnsafe();
  13.  
  14. //对象的操作
  15. //1,获取对象的字段相对该对象地址的偏移量;
  16.  
  17. //1.1 静态字段获取 ;说明:静态字段的偏移量相对于该类的内存地址,即相对于 className.class 返回的对象;
  18. long staticFieldOffset = unsafe.staticFieldOffset(TestUnsafe.class.getDeclaredField("number"));
    //1.2 非静态字段 ;说明:该偏移量相对于该类的实例化对象的内存地址,即 new 返回的对象; 这里相对于上面实例化的 t对象
  19. long unstaticFieldOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("c"));
  20.  
  21. System.out.println("静态变量相对于类内存地址的偏移量 = " + staticFieldOffset);
  22. System.out.println("非静态变量相对于实例化对象的偏移量 = " + unstaticFieldOffset);
  23.  
  24. //修改对象字段的值;
  25. //1.3 修改非基本数据类型的值,使用:putObject(object , offset , value); 这里修改 实例化对象t对应偏移地址字段的值;
  26. unsafe.putObject(t, unstaticFieldOffset, "b");
  27.  
  28. //1.3 修改基本数据类型的值,使用对应类型的put方法,如:int 使用 putInt(object , offset , value);
  29. unsafe.putInt(TestUnsafe.class, staticFieldOffset, 4);
  30.  
  31. System.out.println("静态变量被修改后的值 = " + TestUnsafe.number);
  32. System.out.println("非静态变量被修改后的值 = " + t.c);
  33. }

  34.    //利用反射获取Unsafe的实例
  35. @SuppressWarnings("restriction")
  36. static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
  37. Class<?> cls = sun.misc.Unsafe.class;
  38. Field[] fields = cls.getDeclaredFields();
  39. for(Field f : fields) {
  40. if("theUnsafe".equals(f.getName())) {
  41. f.setAccessible(true);
  42. return (sun.misc.Unsafe) f.get(null);
  43. }
  44. }
  45. throw new IllegalAccessException("no declared field: theUnsafe");
  46. }
  47. }

3,Unsafe 内存的使用:申请allocateMemory(long)、扩展reallocateMemory(long,long)、销毁freeMemory(long)、插入值putXXX()、获取值getXXX(),示例代码如下:

  1. //内存使用
  2. //说明:该内存的使用将直接脱离jvm,gc将无法管理以下方式申请的内存,以用于一定要手动释放内存,避免内存溢出;
  3. //2.1 向本地系统申请一块内存地址; 使用方法allocateMemory(long capacity) ,该方法将返回内存地址的起始地址
  4. long address = unsafe.allocateMemory(8);
  5. System.out.println("allocate memory address = " + address);
  6.  
  7. //2.2 向内存地址中设置值;
  8. //2.2 说明: 基本数据类型的值的添加,使用对应put数据类型方法,如:添加byte类型的值,使用:putByte(内存地址 , 值);
  9. unsafe.putByte(address, (byte)1);
  10.  
  11. //2.2 添加非基本数据类型的值,使用putObject(值类型的类类型 , 内存地址 , 值对象);
  12. unsafe.putObject(Hello.class, address+2, new Hello());
  13.  
  14. //2.3 从给定的内存地址中取出值, 同存入方法基本类似,基本数据类型使用getXX(地址) ,object类型使用getObject(类类型,地址);
  15. byte b = unsafe.getByte(address);
  16. System.out.println(b);
  17.  
  18. //2.3 获取object类型值
  19. Hello h = (Hello) unsafe.getObject(Hello.class, address+2);
  20. System.out.println(h);
  21.  
  22. //2.4 重新分配内存 reallocateMemory(内存地址 ,大小) , 该方法说明 :该方法将释放掉给定内存地址所使用的内存,并重新申请给定大小的内存;
  23. // 注意: 会释放掉原有内存地址 ,但已经获取并保存的值任然可使用,原因:个人理解:使用unsafe.getXXX方法获取的是该内存地址的值,
  24. //并把值赋值给左边对象,这个过程相当于是一个copy过程--- 将系统内存的值 copy 到jvm 管理的内存中;
  25. long newAddress = unsafe.reallocateMemory(address, 32);
  26. System.out.println("new address = "+ newAddress);
  27. //再次调用,内存地址的值已丢失; 被保持与jvm中的对象值不被丢失;
  28. System.out.println("local memory value =" + unsafe.getByte(address) + " jvm memory value = "+ b);
  29.  
  30. //2.5 使用申请过的内存;
  31. //说明: 该方法同reallocateMemory 释放内存的原理一般;
  32. unsafe.freeMemory(newAddress);
  33.  
  34. //2.5 put 方法额外说明
  35. //putXXX() 方法中存在于这样的重载: putXXX(XXX ,long , XXX) ,如:putInt(Integer ,long , Integer) 或者 putObject(Object ,long ,Object)
  36. //个人理解 : 第一个参数相当于作用域,即:第三个参数所代表的值,将被存储在该域下的给定内存地址中;(此处疑惑:
  37. //如果unsafe是从操作系统中直接获取的内存地址,那么该地址应该唯一,重复在该地址存储数据,后者应该覆盖前者,但是并没有;应该是jvm有特殊处理,暂未研究深入,所以暂时理解为域;)
  38. //以下示例可以说明,使用allocateMemory申请的同一地址,并插入不同对象所表示的值,后面插入的值并没有覆盖前面插入的值;
  39. //
  40. long taddress = unsafe.allocateMemory(1);
  41. Hello l = new Hello("l");
  42. Hello l1 = new Hello("l1");
  43. unsafe.putObject(l, taddress, l);
  44. System.out.println(unsafe.getObject(l, taddress));
  45. unsafe.putObject(l1, taddress, l1);
  46. System.out.println(unsafe.getObject(l1, taddress));
  47. System.out.println(unsafe.getObject(l, taddress));
  48. unsafe.putObject(Hello.class, taddress, new Hello("33"));
  49. System.out.println(unsafe.getObject(Hello.class, taddress));
  50.  
  51. unsafe.freeMemory(taddress);

  重要的事情说n遍::::Unsafe申请的内存的使用将直接脱离jvm,gc将无法管理Unsafe申请的内存,所以使用之后一定要手动释放内存,避免内存溢出!!!

4,CAS 操作(CAS,compare and swap的缩写,意:比较和交换):硬件级别的原子性更新变量;在Unsafe 中主要有三个方法:CompareAndSwapInt() ,CompareAndSwapLong() ,CompareAndSwapObject();具体操作,代码如下:

  1. //3.0关于并发对变量的原子操作,请查看其它资料;unsafe 提供硬件级别的原子操作CAS方法,如:compareAndSwapInt(Object ,long ,int ,int)
  2. //说明: 第一个参数:需要更新的对象;第二个参数:偏移地址; 第三个对象:预期在该偏移地址上的当前值,即:getInt(obj,偏移地址) == 预期值; 第四个参数:需要更新的值
  3. //此类方法,当且仅当当前偏移量的值等于预期值时,才更新为给定值;否则不做任何改变;
  4. //compareAndSwapObject 和 compareAndSwapLong 与下述示例类似;
  5. long offset = unsafe.allocateMemory(1);
  6. unsafe.putInt(Integer.class, offset, 1);
  7. System.out.println(unsafe.getInt(Integer.class, offset));
    boolean updateState = unsafe.compareAndSwapInt(Integer.class, offset, 1, 5);
  8. System.out.println("update state = "+ updateState +" ; value = " + unsafe.getInt(Integer.class,offset));
  9.  
  10. unsafe.freeMemory(offset);

5,线程挂起和恢复,part()、unpart(),代码如下:

  1. //4.1 unsafe提供线程挂起和恢复的原语;
  2. /* 挂起线程,方法如下
  3. * part(boolean abs,long timeout)
  4. * 方法说明:将当前线程挂起,直到当期时间“到达”(1)timeout描述的时间点,或者等待线程中断或unpark;
  5. * (1):注意:这里使用的是到达,即给定的timeout时间是一个时间点,该时间点从1970计数开始;
  6. * 参数说明:
  7. * abs 为false 时,表示timeout以纳秒为单位 ;当为false是,可设置timeout为0,表示永远挂起,直到interrupt 或则 unpart
  8. * abs 为true 时,表示timeout以毫秒为单位;注意,经测试在abs为true时,将timeout设置为0,线程会立即返回;
  9. * timeout : 指定线程挂起到某个时间点,该时间点从1970计数开始;
  10. */
  11. //ex1 :
  12. Thread thread = new Thread(()->{
  13. unsafe.park(false, 0);//永远挂起
  14. });
  15. thread.start();
  16.  
  17. /*
  18. * 4.2 恢复线程,方法如下:
  19. * unpark(Object thread);
  20. * 方法说明: 给与传入对象一个运行的许可,即将给定的线程从挂起状态恢复到运行状态;
  21. * 参数说明:thread :通常是一个线程对象;
  22. * 特殊说明:unpark 可以在park之前使用,但不论在park方法之前,进行了多少次的调用unpark方法,对于作为参数的thread线程始终将只获得一个运行许可;
  23. * 即:当park方法调用时,检测到该线程存在一个运行许可,park方法也会立即返回;这种方式在多线程中虽然很灵活,相对于notify/wait的方式,但不建议如此使用;
  24. */
  25. //ex2:
  26. unsafe.unpark(thread);//恢复线程

Unsafe API介绍及其使用的更多相关文章

  1. 常用ArcGIS for Silverlight 开发API介绍

    1.API介绍 2.Map对象  3.Layer对象 4.Symbol对象 5.Task对象

  2. Servlet基础(一) Servlet简介 关键API介绍及结合源码讲解

    Servlet基础(一) Servlet基础和关键的API介绍 Servlet简介 Java Servlet是和平台无关的服务器端组件,它运行在Servlet容器中. Servlet容器负责Servl ...

  3. python学习笔记(win32print API介绍)

    最近博主在研究用python控制打印机 这里整理下win32print的API介绍,官网地址http://timgolden.me.uk/pywin32-docs/win32print.html Op ...

  4. 使用html5中video自定义播放器必备知识点总结以及JS全屏API介绍

    一.video的js知识点: controls(控制器).autoplay(自动播放).loop(循环)==video默认的: 自定义播放器中一些JS中提供的方法和属性的记录: 1.play()控制视 ...

  5. Commons-lang API介绍

    4.1 Commons-lang API介绍 4.1.1 StringUtils 4.1.2 StringEscapeUtils 4.1.3 ArrayUtils 4.1.4 DateUtils 4. ...

  6. APP自动化框架LazyAndroid使用手册(3)--核心API介绍

    作者:黄书力 概述 在前一篇博文中,简要介绍了一款安卓UI自动化测试框架LazyAndroid (http://blog.csdn.net/kaka1121/article/details/53204 ...

  7. Spring Boot 2.x 编写 RESTful API (一) RESTful API 介绍 & RestController

    用Spring Boot编写RESTful API 学习笔记 RESTful API 介绍 REST 是 Representational State Transfer 的缩写 所有的东西都是资源,所 ...

  8. FastDFS api介绍

    1. 命令行api介绍 FastDFS提供了可用于运维测试的命令行api,下面进行介绍: 1.1 fastdfs服务管理 tracker进程服务管理脚本 /etc/init.d/fdfs_tracke ...

  9. ElasticSearch的API介绍

    ElasticSearch的API介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.ES是基于Restful风格 1>ES是基于Restful风格 Elasticsea ...

随机推荐

  1. JavaWeb 分层设计、MVC

    M:Model,JavaBean. V:View,JSP. C:Controller,Servlet. Servlet: 接受用户请求,把请求参数封装为一个JavaBean,调用service来处理业 ...

  2. Github标星过万,Python新手100天学习计划。

    大数据文摘编辑部出品 作为目前最火也是最实用的编程语言,Python不仅是新手入门程序界的首选,也逐渐成为了从大厂到小厂,招牌需求list的必要一条. 当然,学Python这件事情,你可能也和文摘菌一 ...

  3. 彻底搞懂JVM类加载器:基本概念

    本文阅读时间大约9分钟. 写在前面 在Java面试中,在考察完项目经验.基础技术后,我会根据候选人的特点进行知识深度的考察,如果候选人简历上有写JVM(Java虚拟机)相关的东西,那么我常常会问一些J ...

  4. php导出数据到多个csv并打包压缩

    1.不压缩直接下载 // 测试php导出大量数据到csv public function actionExportData() { // 设置不超时 set_time_limit(0); // 设置最 ...

  5. 'root'@'localhost'不能登录问题

    鉴于我在遇到这个问题时,在网上搜索到几十个答案都是进行更改密码解决该问题,然并没有卵用.最后还是让小编找到了解决方法,希望大家遇到该问题时能够节省时间.解决方法如下:   #mysql -u root ...

  6. linux系统盘扩容操作

    linux操作系统原来的50硬盘空间不够用了,如果再加一块60G硬盘,怎样扩容呢?今天我参考了前辈门的文档实际操作了一下,涉及到PV/VG/LV的相关操作. 当50G系统硬盘不够,再挂载一块60G,就 ...

  7. Centos6.5硬盘故障修复

    以企业Centos6.5Linux为案例来修复系统,步骤如下: (1)远程备份本地其他重要数据,出现只读文件系统,需要先备份其他重要数据基于rsync|scp远程备份,其中/data为源目录,/dat ...

  8. springBoot配置druid监控报错Failed to bind properties under 'spring.datasource.druid' to javax.sql.DataSource

    报错信息: Description: Failed to bind properties under 'spring.datasource.druid' to javax.sql.DataSource ...

  9. 代数&数论趣题集萃

    暑假总不能只学习平面几何.所以这里也收集一些有趣的代数题或数论题,同时记下解法的一些提示.给未来的自己复习参考用. 多图片预警(请注意流量) 目录: Part 0:其他(8) Part 1:不等式(1 ...

  10. 如何将MultipartFile转换成based4

    public String test(MultipartFile file) throws Exception{ BASE64Encoder base64Encoder =new BASE64Enco ...