java是一门安全的编程语言,防止程序员犯很多愚蠢的错误,它们大部分是基于内存管理的。但是,有一种方式可以有意的执行一些不安全、容易犯错的操作,那就是使用<code>unsafe</code>类。
本文是<code>sun.misc.unsafe</code>公共api的简要概述,及其一些有趣的用法。
在使用unsafe之前,我们需要创建unsafe对象的实例。这并不像<code>unsafe unsafe = new unsafe()</code>这么简单,因为<code>unsafe的</code>构造器是私有的。它也有一个静态的<code>getunsafe()</code>方法,但如果你直接调用<code>unsafe.getunsafe()</code>,你可能会得到<code>securityexception异常。只能从受信任的代码中使用这个方法。</code>
<code>1</code>
<code>public</code> <code>static</code> <code>unsafe getunsafe() {</code>
<code>2</code>
<code> </code><code>class cc = sun.reflect.reflection.getcallerclass(</code><code>2</code><code>);</code>
<code>3</code>
<code> </code><code>if</code> <code>(cc.getclassloader() != </code><code>null</code><code>)</code>
<code>4</code>
<code> </code><code>throw</code> <code>new</code> <code>securityexception(</code><code>"unsafe"</code><code>);</code>
<code>5</code>
<code> </code><code>return</code> <code>theunsafe;</code>
<code>6</code>
<code>}</code>
这就是java如何验证代码是否可信。它只检查我们的代码是否由主要的类加载器加载。
我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath 选项,指定系统类路径加上你使用的一个unsafe路径。
<code>java -xbootclasspath:/usr/jdk1.</code><code>7.0</code><code>/jre/lib/rt.jar:. com.mishadoff.magic.unsafeclient</code>
但这太难了。
<code>unsafe</code>类包含一个私有的、名为<code>theunsafe的实例</code>,我们可以通过java反射窃取该变量。
<code>field f = unsafe.</code><code>class</code><code>.getdeclaredfield(</code><code>"theunsafe"</code><code>);</code>
<code>f.setaccessible(</code><code>true</code><code>);</code>
<code>unsafe unsafe = (unsafe) f.get(</code><code>null</code><code>);</code>
注意:忽略你的ide。比如:eclipse显示”access restriction…”错误,但如果你运行代码,它将正常运行。如果这个错误提示令人烦恼,可以通过以下设置来避免:
<code>preferences -> java -> compiler -> errors/warnings -></code>
<code>deprecated and restricted api -> forbidden reference -> warning</code>
sun.misc.unsafe类包含105个方法。实际上,对各种实体操作有几组重要方法,其中的一些如下:
info.仅返回一些低级的内存信息
<code>addresssize</code>
<code>pagesize</code>
objects.提供用于操作对象及其字段的方法
<code>allocateinstance</code>
<code>objectfieldoffset</code>
classes.提供用于操作类及其静态字段的方法
<code>staticfieldoffset</code>
<code>defineclass</code>
<code>defineanonymousclass</code>
<code>ensureclassinitialized</code>
arrays.操作数组
<code>arraybaseoffset</code>
<code>arrayindexscale</code>
synchronization.低级的同步原语
<code>monitorenter</code>
<code>trymonitorenter</code>
<code>monitorexit</code>
<code>compareandswapint</code>
<code>putorderedint</code>
memory.直接内存访问方法
<code>allocatememory</code>
<code>copymemory</code>
<code>freememory</code>
<code>getaddress</code>
<code>getint</code>
<code>putint</code>
避免初始化
当你想要跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类,<code>allocateinstance</code>方法是非常有用的。考虑以下类:
<code>class</code> <code>a {</code>
<code> </code><code>private</code> <code>long</code> <code>a; </code><code>// not initialized value</code>
<code> </code><code>public</code> <code>a() {</code>
<code> </code><code>this</code><code>.a = </code><code>1</code><code>; </code><code>// initialization</code>
<code> </code><code>}</code>
<code>7</code>
<code>8</code>
<code> </code><code>public</code> <code>long</code> <code>a() { </code><code>return</code> <code>this</code><code>.a; }</code>
<code>9</code>
使用构造器、反射和unsafe初始化它,将得到不同的结果。
<code>a o1 = </code><code>new</code> <code>a(); </code><code>// constructor</code>
<code>o1.a(); </code><code>// prints 1</code>
<code>a o2 = a.</code><code>class</code><code>.newinstance(); </code><code>// reflection</code>
<code>o2.a(); </code><code>// prints 1</code>
<code>a o3 = (a) unsafe.allocateinstance(a.</code><code>class</code><code>); </code><code>// unsafe</code>
<code>o3.a(); </code><code>// prints 0</code>
想想所有单例发生了什么。
内存崩溃(memory corruption)
这对于每个c程序员来说是常见的。顺便说一下,它是绕过安全的常用技术。
考虑下那些用于检查“访问规则”的简单类:
<code>class</code> <code>guard {</code>
<code> </code><code>private</code> <code>int</code> <code>access_allowed = </code><code>1</code><code>;</code>
<code> </code><code>public</code> <code>boolean</code> <code>giveaccess() {</code>
<code> </code><code>return</code> <code>42</code> <code>== access_allowed;</code>
<code> </code><code>}</code>
客户端代码是非常安全的,并且通过调用<code>giveaccess()</code>来检查访问规则。可惜,对于客户,它总是返回false。只有特权用户可以以某种方式改变<code>access_allowed</code>常量的值并且得到访问(giveaccess()方法返回true,译者注)。
实际上,这并不是真的。演示代码如下:
<code>guard guard = </code><code>new</code> <code>guard();</code>
<code>guard.giveaccess(); </code><code>// false, no access</code>
<code>// bypass</code>
<code>unsafe unsafe = getunsafe();</code>
<code>field f = guard.getclass().getdeclaredfield(</code><code>"access_allowed"</code><code>);</code>
<code>unsafe.putint(guard, unsafe.objectfieldoffset(f), </code><code>42</code><code>); </code><code>// memory corruption</code>
<code>guard.giveaccess(); </code><code>// true, access granted</code>
现在所有的客户都拥有无限制的访问权限。
实际上,反射可以实现相同的功能。但值得关注的是,我们可以修改任何对象,甚至没有这些对象的引用。
例如,有一个guard对象,所在内存中的位置紧接着在当前guard对象之后。我们可以用以下代码来修改它的<code>access_allowed</code>字段:
<code>unsafe.putint(guard, </code><code>16</code> <code>+ unsafe.objectfieldoffset(f), </code><code>42</code><code>); </code><code>// memory corruption</code>
注意:我们不必持有这个对象的引用。16是<code>guard</code>对象在32位架构上的大小。我们可以手工计算它,或者通过使用<code>sizeof</code>方法(它的定义,如下节)。
<code>01</code>
<code>public</code> <code>static</code> <code>long</code> <code>sizeof(object o) {</code>
<code>02</code>
<code> </code><code>unsafe u = getunsafe();</code>
<code>03</code>
<code> </code><code>hashset<field> fields = </code><code>new</code> <code>hashset<field>();</code>
<code>04</code>
<code> </code><code>class c = o.getclass();</code>
<code>05</code>
<code> </code><code>while</code> <code>(c != object.</code><code>class</code><code>) {</code>
<code>06</code>
<code> </code><code>for</code> <code>(field f : c.getdeclaredfields()) {</code>
<code>07</code>
<code> </code><code>if</code> <code>((f.getmodifiers() & modifier.static) == </code><code>0</code><code>) {</code>
<code>08</code>
<code> </code><code>fields.add(f);</code>
<code>09</code>
<code> </code><code>}</code>
<code>10</code>
<code> </code><code>}</code>
<code>11</code>
<code> </code><code>c = c.getsuperclass();</code>
<code>12</code>
<code>13</code>
<code>14</code>
<code> </code><code>// get offset</code>
<code>15</code>
<code> </code><code>long</code> <code>maxsize = </code><code>0</code><code>;</code>
<code>16</code>
<code> </code><code>for</code> <code>(field f : fields) {</code>
<code>17</code>
<code> </code><code>long</code> <code>offset = u.objectfieldoffset(f);</code>
<code>18</code>
<code> </code><code>if</code> <code>(offset > maxsize) {</code>
<code>19</code>
<code> </code><code>maxsize = offset;</code>
<code>20</code>
<code>21</code>
<code>22</code>
<code>23</code>
<code> </code><code>return</code> <code>((maxsize/</code><code>8</code><code>) + </code><code>1</code><code>) * </code><code>8</code><code>; </code><code>// padding</code>
<code>24</code>
算法如下:通过所有非静态字段(包含父类的),获取每个字段的偏移量(offset),找到偏移最大值并填充字节数(padding)。我可能错过一些东西,但思路是明确的。
如果我们仅读取对象的类结构大小值,sizeof的实现可以更简单,这位于<code>jvm 1.7 32 bit</code>中的偏移量12。
<code>public</code> <code>static</code> <code>long</code> <code>sizeof(object object){</code>
<code> </code><code>return</code> <code>getunsafe().getaddress(</code>
<code> </code><code>normalize(getunsafe().getint(object, 4l)) + 12l);</code>
<code>normalize</code>是一个为了正确内存地址使用,将有符号的int类型强制转换成无符号的long类型的方法。
<code>private</code> <code>static</code> <code>long</code> <code>normalize(</code><code>int</code> <code>value) {</code>
<code> </code><code>if</code><code>(value >= </code><code>0</code><code>) </code><code>return</code> <code>value;</code>
<code> </code><code>return</code> <code>(~0l >>> </code><code>32</code><code>) & value;</code>
真棒,这个方法返回的结果与我们之前的sizeof方法一样。
实际上,对于良好、安全、准确的sizeof方法,最好使用 java.lang.instrument包,但这需要在jvm中指定<code>agent</code>选项。
浅拷贝(shallow copy)
为了实现计算对象自身内存大小,我们可以简单地添加拷贝对象方法。标准的解决方案是使用<code>cloneable</code>修改你的代码,或者在你的对象中实现自定义的拷贝方法,但它不会是多用途的方法。
浅拷贝:
<code>static</code> <code>object shallowcopy(object obj) {</code>
<code> </code><code>long</code> <code>size = sizeof(obj);</code>
<code> </code><code>long</code> <code>start = toaddress(obj);</code>
<code> </code><code>long</code> <code>address = getunsafe().allocatememory(size);</code>
<code> </code><code>getunsafe().copymemory(start, address, size);</code>
<code> </code><code>return</code> <code>fromaddress(address);</code>
<code>toaddress和</code><code>fromaddress</code>将对象转换为其在内存中的地址,反之亦然。
<code>static</code> <code>long</code> <code>toaddress(object obj) {</code>
<code> </code><code>object[] array = </code><code>new</code> <code>object[] {obj};</code>
<code> </code><code>long</code> <code>baseoffset = getunsafe().arraybaseoffset(object[].</code><code>class</code><code>);</code>
<code> </code><code>return</code> <code>normalize(getunsafe().getint(array, baseoffset));</code>
<code>static</code> <code>object fromaddress(</code><code>long</code> <code>address) {</code>
<code> </code><code>object[] array = </code><code>new</code> <code>object[] {</code><code>null</code><code>};</code>
<code> </code><code>getunsafe().putlong(array, baseoffset, address);</code>
<code> </code><code>return</code> <code>array[</code><code>0</code><code>];</code>
这个拷贝方法可以用来拷贝任何类型的对象,动态计算它的大小。注意,在拷贝后,你需要将对象转换成特定的类型。
在<code>unsafe</code>中,一个更有趣的直接内存访问的用法是,从内存中删除不必要的对象。
检索用户密码的大多数api的签名为<code>byte[]</code>或<code>char[],</code>为什么是数组呢?
这完全是出于安全的考虑,因为我们可以删除不需要的数组元素。如果将用户密码检索成字符串,这可以像一个对象一样在内存中保存,而删除该对象只需执行解除引用的操作。但是,这个对象仍然在内存中,由gc决定的时间来执行清除。
创建具有相同大小、假的string对象,来取代在内存中原来的string对象的技巧:
<code>string password = </code><code>new</code> <code>string(</code><code>"l00k@myhor$e"</code><code>);</code>
<code>string fake = </code><code>new</code> <code>string(password.replaceall(</code><code>"."</code><code>, </code><code>"?"</code><code>));</code>
<code>system.out.println(password); </code><code>// l00k@myhor$e</code>
<code>system.out.println(fake); </code><code>// ????????????</code>
<code>getunsafe().copymemory(</code>
<code> </code><code>fake, 0l, </code><code>null</code><code>, toaddress(password), sizeof(password));</code>
<code>system.out.println(password); </code><code>// ????????????</code>
感觉很安全。
修改:这并不安全。为了真正的安全,我们需要通过反射删除后台char数组:
<code>field stringvalue = string.</code><code>class</code><code>.getdeclaredfield(</code><code>"value"</code><code>);</code>
<code>stringvalue.setaccessible(</code><code>true</code><code>);</code>
<code>char</code><code>[] mem = (</code><code>char</code><code>[]) stringvalue.get(password);</code>
<code>for</code> <code>(</code><code>int</code> <code>i=</code><code>0</code><code>; i < mem.length; i++) {</code>
<code> </code><code>mem[i] = </code><code>'?'</code><code>;</code>
感谢peter verhas指定出这一点。
java中没有多继承。
这是对的,除非我们可以将任意类型转换成我们想要的其他类型。
<code>long</code> <code>intclassaddress = normalize(getunsafe().getint(</code><code>new</code> <code>integer(</code><code>0</code><code>), 4l));</code>
<code>long</code> <code>strclassaddress = normalize(getunsafe().getint(</code><code>""</code><code>, 4l));</code>
<code>getunsafe().putaddress(intclassaddress + </code><code>36</code><code>, strclassaddress);</code>
这个代码片段将string类型添加到integer超类中,因此我们可以强制转换,且没有运行时异常。
<code>(string) (object) (</code><code>new</code> <code>integer(</code><code>666</code><code>))</code>
有一个问题,我们必须预先强制转换对象,以欺骗编译器。
我们可以在运行时创建一个类,比如从已编译的.class文件中。将类内容读取为字节数组,并正确地传递给<code>defineclass</code>方法。
<code>byte</code><code>[] classcontents = getclasscontent();</code>
<code>class c = getunsafe().defineclass(</code>
<code> </code><code>null</code><code>, classcontents, </code><code>0</code><code>, classcontents.length);</code>
<code> </code><code>c.getmethod(</code><code>"a"</code><code>).invoke(c.newinstance(), </code><code>null</code><code>); </code><code>// 1</code>
从定义文件(class文件)中读取(代码)如下:
<code>private</code> <code>static</code> <code>byte</code><code>[] getclasscontent() </code><code>throws</code> <code>exception {</code>
<code> </code><code>file f = </code><code>new</code> <code>file(</code><code>"/home/mishadoff/tmp/a.class"</code><code>);</code>
<code> </code><code>fileinputstream input = </code><code>new</code> <code>fileinputstream(f);</code>
<code> </code><code>byte</code><code>[] content = </code><code>new</code> <code>byte</code><code>[(</code><code>int</code><code>)f.length()];</code>
<code> </code><code>input.read(content);</code>
<code> </code><code>input.close();</code>
<code> </code><code>return</code> <code>content;</code>
当你必须动态创建类,而现有代码中有一些代理, 这是很有用的。
不喜欢受检异常?没问题。
<code>getunsafe().throwexception(</code><code>new</code> <code>ioexception());</code>
该方法抛出受检异常,但你的代码不必捕捉或重新抛出它,正如运行时异常一样。
这更有实用性。
大家都知道,标准java的<code>serializable的序列化能力是非常慢的。它同时要求类必须有一个公共的、无参数的构造器。</code>
<code>externalizable</code>比较好,但它需要定义类序列化的模式。
流行的高性能库,比如kryo具有依赖性,这对于低内存要求来说是不可接受的。
unsafe类可以很容易实现完整的序列化周期。
序列化:
使用反射构建模式对象,类只可做一次。
使用<code>unsafe</code>方法,如<code>getlong</code>、<code>getint</code>、<code>getobject</code>等来检索实际字段值。
添加类标识,以便有能力恢复该对象
将它们写入文件或任意输出
你也可以添加压缩(步骤)以节省空间。
反序列化:
创建已序列化对象实例,使用<code>allocateinstance</code>协助(即可),因为不需要任何构造器。
构建模式,与序列化的步骤1相同。
从文件或任意输入中读取所有字段。
使用<code>unsafe</code>方法,如<code>putlong</code>、<code>putint</code>、<code>putobject</code>等来填充该对象。
实际上,在正确的实现过程中还有更多的细节,但思路是明确的。
这个序列化将非常快。
正如你所知,java数组大小的最大值为<code>integer.max_value</code>。使用直接内存分配,我们创建的数组大小受限于堆大小。
<code>superarray的实现</code>:
<code>class</code> <code>superarray {</code>
<code> </code><code>private</code> <code>final</code> <code>static</code> <code>int</code> <code>byte = </code><code>1</code><code>;</code>
<code> </code><code>private</code> <code>long</code> <code>size;</code>
<code> </code><code>private</code> <code>long</code> <code>address;</code>
<code> </code><code>public</code> <code>superarray(</code><code>long</code> <code>size) {</code>
<code> </code><code>this</code><code>.size = size;</code>
<code> </code><code>address = getunsafe().allocatememory(size * byte);</code>
<code> </code><code>public</code> <code>void</code> <code>set(</code><code>long</code> <code>i, </code><code>byte</code> <code>value) {</code>
<code> </code><code>getunsafe().putbyte(address + i * byte, value);</code>
<code> </code><code>public</code> <code>int</code> <code>get(</code><code>long</code> <code>idx) {</code>
<code> </code><code>return</code> <code>getunsafe().getbyte(address + idx * byte);</code>
<code> </code><code>public</code> <code>long</code> <code>size() {</code>
<code> </code><code>return</code> <code>size;</code>
简单用法:
<code>long</code> <code>super_size = (</code><code>long</code><code>)integer.max_value * </code><code>2</code><code>;</code>
<code>superarray array = </code><code>new</code> <code>superarray(super_size);</code>
<code>system.out.println(</code><code>"array size:"</code> <code>+ array.size()); </code><code>// 4294967294</code>
<code>for</code> <code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i < </code><code>100</code><code>; i++) {</code>
<code> </code><code>array.set((</code><code>long</code><code>)integer.max_value + i, (</code><code>byte</code><code>)</code><code>3</code><code>);</code>
<code> </code><code>sum += array.get((</code><code>long</code><code>)integer.max_value + i);</code>
<code>system.out.println(</code><code>"sum of 100 elements:"</code> <code>+ sum); </code><code>// 300</code>
实际上,这是堆外内存(<code>off-heap memory</code>)技术,在<code>java.nio</code>包中部分可用。
这种方式的内存分配不在堆上,且不受gc管理,所以必须小心<code>unsafe.freememory()的使用。它也不执行任何边界检查,所以任何非法访问可能会导致jvm崩溃。</code>
这可用于数学计算,代码可操作大数组的数据。此外,这可引起实时程序员的兴趣,可打破gc在大数组上延迟的限制。
几句关于<code>unsafe</code>的并发性。<code>compareandswap</code>方法是原子的,并且可用来实现高性能的、无锁的数据结构。
比如,考虑问题:在使用大量线程的共享对象上增长值。
首先,我们定义简单的<code>counter</code>接口:
<code>interface</code> <code>counter {</code>
<code> </code><code>void</code> <code>increment();</code>
<code> </code><code>long</code> <code>getcounter();</code>
然后,我们定义使用counter的工作线程<code>counterclient</code>:
<code>class</code> <code>counterclient </code><code>implements</code> <code>runnable {</code>
<code> </code><code>private</code> <code>counter c;</code>
<code> </code><code>private</code> <code>int</code> <code>num;</code>
<code> </code><code>public</code> <code>counterclient(counter c, </code><code>int</code> <code>num) {</code>
<code> </code><code>this</code><code>.c = c;</code>
<code> </code><code>this</code><code>.num = num;</code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>void</code> <code>run() {</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i < num; i++) {</code>
<code> </code><code>c.increment();</code>
测试代码:
<code>int</code> <code>num_of_threads = </code><code>1000</code><code>;</code>
<code>int</code> <code>num_of_increments = </code><code>100000</code><code>;</code>
<code>executorservice service = executors.newfixedthreadpool(num_of_threads);</code>
<code>counter counter = ... </code><code>// creating instance of specific counter</code>
<code>long</code> <code>before = system.currenttimemillis();</code>
<code>for</code> <code>(</code><code>int</code> <code>i = </code><code>0</code><code>; i < num_of_threads; i++) {</code>
<code> </code><code>service.submit(</code><code>new</code> <code>counterclient(counter, num_of_increments));</code>
<code>service.shutdown();</code>
<code>service.awaittermination(</code><code>1</code><code>, timeunit.minutes);</code>
<code>long</code> <code>after = system.currenttimemillis();</code>
<code>system.out.println(</code><code>"counter result: "</code> <code>+ c.getcounter());</code>
<code>system.out.println(</code><code>"time passed in ms:"</code> <code>+ (after - before));</code>
第一个无锁版本的计数器:
<code>class</code> <code>stupidcounter </code><code>implements</code> <code>counter {</code>
<code> </code><code>private</code> <code>long</code> <code>counter = </code><code>0</code><code>;</code>
<code> </code><code>public</code> <code>void</code> <code>increment() {</code>
<code> </code><code>counter++;</code>
<code> </code><code>public</code> <code>long</code> <code>getcounter() {</code>
<code> </code><code>return</code> <code>counter;</code>
输出:
<code>counter result: </code><code>99542945</code>
<code>time passed in ms: </code><code>679</code>
运行快,但没有线程管理,结果是不准确的。第二次尝试,添加上最简单的java式同步:
<code>class</code> <code>synccounter </code><code>implements</code> <code>counter {</code>
<code> </code><code>public</code> <code>synchronized</code> <code>void</code> <code>increment() {</code>
<code>counter result: </code><code>100000000</code>
<code>time passed in ms: </code><code>10136</code>
激进的同步有效,但耗时长。试试<code>reentrantreadwritelock</code>:
<code>class</code> <code>lockcounter </code><code>implements</code> <code>counter {</code>
<code> </code><code>private</code> <code>writelock lock = </code><code>new</code> <code>reentrantreadwritelock().writelock();</code>
<code> </code><code>lock.lock();</code>
<code> </code><code>lock.unlock();</code>
<code>time passed in ms: </code><code>8065</code>
仍然正确,耗时较短。atomics的运行效果如何?
<code>class</code> <code>atomiccounter </code><code>implements</code> <code>counter {</code>
<code> </code><code>atomiclong counter = </code><code>new</code> <code>atomiclong(</code><code>0</code><code>);</code>
<code> </code><code>counter.incrementandget();</code>
<code> </code><code>return</code> <code>counter.get();</code>
<code>time passed in ms: </code><code>6552</code>
<code>atomiccounter的运行结果更好。最后,试试</code><code>unsafe</code>原始的<code>compareandswaplong</code>,看看它是否真的只有特权才能使用它?
<code>class</code> <code>cascounter </code><code>implements</code> <code>counter {</code>
<code> </code><code>private</code> <code>volatile</code> <code>long</code> <code>counter = </code><code>0</code><code>;</code>
<code> </code><code>private</code> <code>unsafe unsafe;</code>
<code> </code><code>private</code> <code>long</code> <code>offset;</code>
<code> </code><code>public</code> <code>cascounter() </code><code>throws</code> <code>exception {</code>
<code> </code><code>unsafe = getunsafe();</code>
<code> </code><code>offset = unsafe.objectfieldoffset(cascounter.</code><code>class</code><code>.getdeclaredfield(</code><code>"counter"</code><code>));</code>
<code> </code><code>long</code> <code>before = counter;</code>
<code> </code><code>while</code> <code>(!unsafe.compareandswaplong(</code><code>this</code><code>, offset, before, before + </code><code>1</code><code>)) {</code>
<code> </code><code>before = counter;</code>
<code>time passed in ms: </code><code>6454</code>
看起来似乎等价于atomics。atomics使用<code>unsafe</code>?(是的)
实际上,这个例子很简单,但它展示了<code>unsafe</code>的一些能力。
如我所说,cas原语可以用来实现无锁的数据结构。背后的原理很简单:
有一些状态
创建它的副本
修改它
执行cas
如果失败,重复尝试
实际上,现实中比你现象的更难。存在着许多问题,如aba问题、指令重排序等。
修改:给counter变量添加<code>volatile</code>关键字,以避免无限循环的风险。
即使<code>unsafe</code>对应用程序很有用,但(建议)不要使用它。