为什么存在sun.misc.Unsafe,如何在现实世界中使用它?[关闭]


267

前几天,我遇到了sun.misc.Unsafe软件包,并对它可以做什么感到惊讶。

当然,该类是未记录的,但是我想知道是否有充分的理由使用它。在需要使用它的地方会出现什么情况?在现实世界中如何使用它?

此外,如果您这样做需要它,这是否表示您的设计可能存在问题?

为什么Java甚至包括此类?


7
JDK开发人员当前正在审查此API,以将其转换为Java 9中的公共API。如果使用它,则值得花5分钟时间填写调查表:Surveymonkey.com/s/sun-misc-Unsafe
安迪·林奇

2
:这篇文章是对正在讨论荟萃meta.stackoverflow.com/questions/299139/...
乔恩·克莱门茨

Answers:


159

例子

  1. VM“化身”。即无锁哈希表中使用的CAS(比较和交换),例如:sun.misc.Unsafe.compareAndSwapInt,它可以对包含针对CAS的特殊指令的本机代码进行真正的JNI调用

    在此处阅读有关CAS的更多信息http://en.wikipedia.org/wiki/Compare-and-swap

  2. 主机VM的sun.misc.Unsafe功能可用于分配未初始化的对象,然后将构造函数调用解释为任何其他方法调用。

  3. 可以从本机地址跟踪数据。可以使用java.lang.Unsafe类检索对象的内存地址,并通过不安全的get / put方法直接在其字段上进行操作!

  4. JVM的编译时间优化。使用“魔术”的高性能VM,需要低级操作。例如:http : //en.wikipedia.org/wiki/Jikes_RVM

  5. 分配内存sun.misc.Unsafe.allocateMemory,例如:-调用ByteBuffer.allocateDirect时,DirectByteBuffer构造函数在内部对其进行调用

  6. 跟踪调用堆栈并重播由sun.misc实例化的值。不安全,对检测很有用

  7. sun.misc.Unsafe.arrayBaseOffset和arrayIndexScale可用于开发arraylet,该技术可将大型数组有效地分解为较小的对象,从而限制了对大型对象进行扫描,更新或移动操作的实时成本

  8. http://robaustin.wikidot.com/how-to-write-to-direct-memory-locations-in-java

有关参考的更多信息-http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html


1
如果使用“不安全”获取字段的地址,则GC始终可以更改该地址,因此该操作是否真的没有用?
pdeva 2011年

获取您已分配地址的地址
zudokod 2011年

所分配的是什么意思?这似乎用在使用“ new”运算符创建对象的地方,因此是我的问题。
pdeva

1
unsafe.allocateMemory并输入值
zudokod 2011年

1
关于第二点,我想知道如何像其他方法调用一样调用构造函数?因为除非找到字节码,否则我找不到任何方法。
Miguel Gamboa 2012年

31

通过在某些代码搜索引擎中运行搜索,我得到以下示例:

用于访问{@link Unsafe}对象的简单类。{@link Unsafe} *是必需的,以允许对阵列进行有效的CAS操作。请注意,{@ link java.util.concurrent.atomic}中的版本,例如{@link java.util.concurrent.atomic.AtomicLongArray},需要额外的内存排序保证,这些算法通常不需要这些保证,而且价格昂贵在大多数处理器上。

  • SoyLatte -Java 6 for osx javadoc摘录

/ **静态字段的基于sun.misc.Unsafe的FieldAccessors的基类。从反射代码的角度来看,观察到只有九种类型的字段:八种基本类型和对象。使用Unsafe类代替生成的字节码可以节省动态生成的FieldAccessor的内存和加载时间。* /

  • SpikeSource

/ *通过导线发送的FinalFields ..如何在接收端解组和重新创建对象?我们不想调用构造函数,因为它将为最终字段建立值。我们必须完全像发送方一样重新创建最终字段。sun.misc.Unsafe为我们做到了。* /

还有很多其他示例,只需点击上面的链接即可...


25

有趣的是,我什至从未听说过这堂课(这确实是一件好事)。

让人想起的一件事是使用Unsafe#setMemory将包含敏感信息(密码,键等)的缓冲区归零。您甚至可以对“不可变”对象的字段执行此操作(然后再次,我认为普通的老式反射也可以在此处完成此操作)。我不是安全专家,所以请带一点盐。


4
I'd never even heard of this class...我已经告诉过你很多次了!叹息 + :(
蒂姆·本德尔

7
没有任何意义,因为Java使用了复制世代的垃圾收集器,并且您的敏感信息很可能已经位于“空闲”内存中的其他位置,等待被覆盖。
丹尼尔·卡西迪

39
也从未听说过它,但是我喜欢他们的park()文档:“阻塞当前线程,在发生平衡解除停车,或者已经发生平衡解除停车或线程中断时返回,或者,如果不是绝对的并且时间不为零,则返回给定的时间(以毫微秒为单位)过去了,或者如果是绝对的话,则是自Epoch过去以来的给定截止时间(以毫秒为单位),或者是虚假的(即,没有“理由”返回)。几乎与“程序退出时释放内存,或以随机间隔,以先到者为准”释放内存一样好。
aroth

1
@Daniel,有趣,我没有考虑过。现在您可以了解为什么我不是安全专家。:)
Mike Daniels

22

基于对Java 1.6.12库的简要分析(使用eclipse进行引用跟踪),似乎Eclipse的每个有用功能 Unsafe都以有用的方式公开。

CAS操作通过Atomic *类公开。内存操作功能通过DirectByteBuffer公开。Sync指令(park,unpark)通过AbstractQueuedSynchronizer公开,而LockQueue实现则使用它。


AtomicXXXUpdaters太慢了,当您真正需要它们时:CAS-您负担不起实际使用它们的费用。如果要制作金属,则不会使用抽象级别和大量检查。CAS失败,尤其是在循环中是不好的。当硬件决定对分支进行错误预测时(由于争用较高),但是再进行比较/分支操作会很痛苦。停放/取消停放LockSupport不是通过AQS 进行公开(后者比停放/
取消

21

Unsafe.throwException-允许抛出已检查的异常而无需声明它们。

在处理反射或AOP的某些情况下,这很有用。

假设您为用户定义的接口构建通用代理。用户可以通过在接口中声明异常来指定在特殊情况下实现会抛出哪个异常。这是我知道的唯一方法,可以在接口的动态实现中引发受检查的异常。

import org.junit.Test;
/** need to allow forbidden references! */ import sun.misc.Unsafe;

/**
 * Demonstrate how to throw an undeclared checked exception.
 * This is a hack, because it uses the forbidden Class {@link sun.misc.Unsafe}.
 */
public class ExceptionTest {

    /**
     * A checked exception.
     */
    public static class MyException extends Exception {
        private static final long serialVersionUID = 5960664994726581924L;
    }

    /**
     * Throw the Exception.
     */
    @SuppressWarnings("restriction")
    public static void throwUndeclared() {
        getUnsafe().throwException(new MyException());
    }

    /**
     * Return an instance of {@link sun.misc.Unsafe}.
     * @return THE instance
     */
    @SuppressWarnings("restriction")
    private static Unsafe getUnsafe() {
        try {

            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);

        } catch (IllegalArgumentException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (SecurityException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (NoSuchFieldException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (IllegalAccessException e) {
            throw createExceptionForObtainingUnsafe(e);
        }
    }

    private static RuntimeException createExceptionForObtainingUnsafe(final Throwable cause) {
        return new RuntimeException("error while obtaining sun.misc.Unsafe", cause);
    }


    /**
     * scenario: test that an CheckedException {@link MyException} can be thrown
     * from an method that not declare it.
     */
    @Test(expected = MyException.class)
    public void testUnsingUnsaveToThrowCheckedException() {
        throwUndeclared();
    }
}

14
您可以执行相同的w / Thread.stop(Throwable)不需要不安全的操作,在同一线程中可以进行任何操作(没有编译检查)
bestsss 2011年

您可以完全通过字节码执行此操作(或使用Lomboc帮您完成此操作)

1
@bestsss从UnsupportedOperationExceptionJava 8开始,该方法已被拔出并在当前线程中抛出。但是,抛出该参数的无参数版本ThreadDeath仍然有效。
gparyani

@damryfbfnetsi,我已经有一段时间没有关注jdk核心讨论了,也没有计划迁移到Java8。但是,这是一个令人费解的想法,因为无论如何它都是通过字节码生成来实现的,除非现在验证者实际上检查它们是否方法声明了throwables ...,但可能向后不兼容,因为有关抛出异常的元数据可以随意丢弃。
bestsss 2014年

10

不安全

一组用于执行低级,不安全操作的方法。尽管该类和所有方法都是公共的,但是由于只有可信代码才能获取该类的实例,因此此类的使用受到限制。

它的一种用法是在java.util.concurrent.atomic班级:


6

有效的内存复制(对于短块而言,比System.arraycopy()的复制速度更快);由Java LZFSnappy编解码器使用。他们使用“ getLong”和“ putLong”,这比逐字节进行复制要快。复制16/32/64字节块之类的文件时特别有效。


1
卫生署,arraycopy使用SSE环路上的x86-64这比好getLong/putLong(你要计算的地址,以及)
bestsss

您实际测量了吗?对于较短的块,当结合使用getLong/ 时,putLong我会在x86-64上始终看到更好的性能:理想情况下,我会更倾向于System.arraycopy()简单和全部;但是对于我测试过的情况,实际测试却显示出其他含义。
StaxMan 2014年

是的,使用不安全,我无法通过deflate impl获得任何有意义的性能。对于大型数组上几个字节的长副本,当编译器必须检查长度时,get / putLong确实可以工作。一些暗示。在System.arrayCopy之后添加内存屏障(尽管可以禁用/启用),所以这可能是真正的罪魁祸首。
bestsss 2014年

好。较新的JDK可能已经改变了这一点。最初,当我确实观察到更快的操作(使用JDK 1.6)时,我也感到惊讶。也许我忘记了用法上的某些特定差异。即使它们确实起作用,这些优化也是棘手的(并且可能是不稳定的)优化,因此测量效果至关重要。
StaxMan 2014年

5

我最近正在重新实现JVM,发现在方面实现了数量惊人的类Unsafe。该类主要是为Java库实现者设计的,并且包含本质上不安全的功能,但对于构​​建快速基元而言是必需的。例如,存在一些获取和写入原始字段偏移量,使用硬件级同步,分配和释放内存等的方法。普通Java程序员不打算使用它。它是无证的,特定于实现的,并且本质上是不安全的(因此得名!)。而且,我认为SecurityManager在几乎所有情况下,都将禁止使用它。

简而言之,主要是允许库实现者访问底层计算机,而不必在某些类(例如AtomicIntegernative)中声明每个方法。您不需要在常规的Java编程中使用或担心它,因为重点是使其余库足够快,以至于您不需要这种访问。


实际上,仅当禁用反射功能时,SecurityManager才禁止访问它
amara 2012年

@ sparkleshy-您能详细说明一下吗?
templatetypedef

同时获得来自getUnsafe一个实例确实有相当严格的要求,Unsafe.class.getDeclaredField("theUnsafe").setAccessible(true)然后.get(null)将得到它太
阿马拉

@ sparkleshy-我很惊讶它的工作原理-安全经理应该对此进行标记。
templatetypedef

5

使用它可以有效地访问和分配大量内存,例如在您自己的体素引擎中!(即Minecraft风格的游戏。)

以我的经验,JVM经常无法消除在真正需要它的地方进行边界检查。例如,如果您要遍历一个大数组,但是实际的内存访问位于循环中的非virtual *方法调用之下,则JVM可能仍会对每个数组访问执行边界检查,而不是仅在执行一次之前循环。因此,为了获得可能的较大性能,您可以通过使用sun.misc.Unsafe直接访问内存的方法来消除循环内的JVM边界检查,从而确保在正确的位置进行任何边界检查。(你要去边界检查在一定程度上,对吧?)
*通过非虚拟方式,我的意思是JVM不必动态地解析您的特定方法,因为您已经正确地保证了类/方法/实例是静态/最终/您所需要的某种组合。

对于我自己开发的体素引擎,这在块生成和序列化(现在是我一次读取/写入整个阵列的位置)期间带来了显着的性能提升。结果可能会有所不同,但是如果您遇到的问题是缺少消除边界的方法,则可以解决此问题。

这有一些潜在的主要问题:特别是,当您提供无限制地检查接口客户端的访问内存的功能时,他们可能会滥用它。(别忘了黑客也可以成为您接口的客户端……尤其是在用Java编写的体素引擎的情况下。)因此,您应该以不滥用内存访问的方式设计接口,或者你应该非常小心,以验证用户的数据,才可以永远,永远与你的危险接口打成一片。考虑到黑客可以对未经检查的内存访问进行灾难性的处理,最好同时采用两种方法。


4

堆外收集对于分配大量内存并在使用后立即使用而不受到GC干扰可能很有用。我编写了一个库,用于基于来处理堆外数组/列表sun.misc.Unsafe


4

我们已经使用Unsafe实现了庞大的集合,例如Arrays,HashMaps,TreeMaps。
为了避免/最小化碎片,我们在不安全的情况下使用dlmalloc的概念实现了内存分配器。
这帮助我们获得了并发性能。


3

Unsafe.park()以及Unsafe.unpark()用于定制并发控制结构和协作调度机制的构建。


24
java.util.concurrent.locks.LockSupport
-bestsss

1

我自己还没有使用过它,但是我想如果您有一个仅偶尔被一个以上线程读取的变量(因此,您真的不想使其变得易变),则可以putObjectVolatile在主线程中使用它时使用readObjectVolatile从其他线程进行稀有读取时。


1
但根据下面的线程上的讨论,uncontented挥发性几乎一样快,不挥发物反正stackoverflow.com/questions/5573782/...
pdeva

您不能用普通写入和易失性读取替换易失性语义...这是灾难的秘诀,因为它可能在一种设置中起作用,而在另一种设置中不起作用。如果你正在寻找有一个写线程可以写入线程上使用AtomicReference.lazySet和get()的读者可变语义(见本岗位有关该主题的讨论)。易读内容相对便宜,但并非免费,请参阅此处
Nitsan Wakart

“ ...在编写它时可以使用putObjectVolatile ...”我并不是在建议普通写。
Matt Crinklaw-Vogt

1

如果需要替换当前使用它的类之一提供的功能,则需要它。

这可以是自定义/更快/更紧凑的序列化/反序列化,更快/更大的Buffer /可调整大小的ByteBuffer版本,或添加原子变量,例如当前不支持的原子变量。

我有时将其用于所有这些。



0

该对象似乎可以在比Java代码通常所允许的级别低的级别上工作。如果您要编写高级应用程序,则JVM会从代码级抽象出内存处理和其他操作,从而使其易于编程。通过使用Unsafe库,您可以有效地完成通常为您完成的低级操作。

正如woliveirajr所说,“ random()”使用Unsafe进行播种,就像许多其他操作将使用Unsafe中包含的allocateMemory()函数一样。

作为程序员,您可能不需要使用该库就可以摆脱麻烦,但是对底层元素进行严格控制确实派上用场(这就是为什么主要产品中仍然存在汇编语言和(程度较小的)C代码的原因)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.