• Java中的指针---Unsafe应用解析


    注:本文出自"美团技术团队"微信公众号

    Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。

    本文对sun.misc.Unsafe公共API功能及相关应用场景进行介绍。

    基本介绍:

    如下Unsafe源码所示,Unsafe类为一单例实现,提供静态方法getUnsafe()获取Unsafe实例,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常。

    pubic class Unsafe{
    	//单例对象
    	private static final Unsafe theUnsafe;
    
    	private Unsafe(){
    
    	}
    
    	@CallerSensitive
    	public static Unsafe getUnsafe() {
            Class var0 = Reflection.getCallerClass();
            //仅在引导类加载器BootstrapClassLoader加载时才合法
            if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
                throw new SecurityException("Unsafe");
            } else {
                return theUnsafe;
            }
        }
    }

    那如若想使用这个类,该如何获取其实例?有如下两个可行方案。

    其一,从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。

    java -Xbootclasspath/a:${path} // 其中path为调用Unsafe相关方法的类所在jar包路径 

    其二,通过反射获取单例对象theUnsafe。

    private static Unsafe reflectionGetUnsafe(){
    	try{
    		Field field = Unsafe.class.getDeclaredField("theUnsafe");
    		field.setAccessible(true);
    		return (Unsafe) field.get(null);
    	}catch(Exception e){
    		logger.error(e.getMesage(),e);
    		return null;
    	}
    }

    功能介绍:

     如上图所示,Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类,下面将对其相关方法和应用场景进行详细介绍。

    内存操作:

    这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。

    //分配内存, 相当于C++的malloc函数
    public native long allocateMemory(long bytes);
    //扩充内存
    public native long reallocateMemory(long address, long bytes);
    //释放内存
    public native void freeMemory(long address);
    //在给定的内存块中设置值
    public native void setMemory(Object o, long offset, long bytes, byte value);
    //内存拷贝
    public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
    //获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等
    public native Object getObject(Object o, long offset);
    //为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
    public native void putObject(Object o, long offset, Object x);
    //获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
    public native byte getByte(long address);
    //为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)
    public native void putByte(long address, byte x);

    通常,我们在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内内存。与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法。

    使用堆外内存的原因:

    • 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
    • 提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。

    典型应用:

    DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。

    CAS相关:

    线程调度:

    Class相关:

    对象操作:

    数组相关:

    内存屏障:

    系统相关:

  • 相关阅读:
    利用百度云盘API上传文件至百度云盘
    测试Centos硬盘读写速度
    into outfile 生成sql脚本
    Nginx设置Js、Css等静态文件的缓存过期时间
    mysql查询区分大小写
    Table './mysql/proc' is marked as crashed and should be repaired 解决方法
    CentOS 6.6 下配置软RAID5
    管道限流利器pv
    mydumper使用
    RAID详解
  • 原文地址:https://www.cnblogs.com/i-hard-working/p/10390052.html
Copyright © 2020-2023  润新知