sun.misc.Unsafe

Java是个安全的编程语言,它可以防止程序员犯一些低级错误,往往这些错误都是基于内存管理的。但是在Java中仍然有办法故意的犯这些错误——使用Unsafe类。 这篇文章将快速的阐述sun.misc.Unsafe类的public API和少许有意思的用法。

Unsafe类的初始化

在使用前,我们都需要创建一个Unsafe类的实例对象,但不能通过Unsafe unsafe = new Unsafe()方式来实现, 因为Unsafe类的构造函数是private的。Unsafe类有一个getUnsafe()的静态方法,但你如果尝试调用Unsafe.getUnsafe()的话,可能会抛出SecurityException异常,因为只有信任的代码才能使用这个静态方法。

上面的代码说明了java是如何校验代码是否是可信任的。它仅校验了我们的代码是否被BootClassloader加载。 要让我们都代码被信任,可以在运行你的程序时使用bootclasspath参数来指定系统类和你要使用Unsafe的类路径。

但是这个方法太麻烦了。 Unsafe类里面有个属性就是一个它的实例对象,即theUnsafe,这个属性是private的。我们可以通过反射方式把这个属性取出来。

注意:忽略你的IDE的error提示。比如:eclipse会提示“Access restriction…”,但你要是运行代码,会发现一切正常。如果你觉得这个提示很烦,可以这样设置来忽略这个提示:

 Unsafe API

sun.misc.Unsafe有105个方法,可以分为多组重要的操作对象属性的方法,这里列举一些:

  • Info. 返回一些底层的内存信息.
    • addressSize
    • pageSize
  • Objects. 提供了操作对象和其属性的方法
    • allocateInstance
    • objectFieldOffset
  • Classes. 提供了操作类和静态属性的方法
    • staticFieldOffset
    • defineClass
    • defineAnonymousClass
    • ensureClassInitialized
  • Arrays. 数组操作
    • arrayBaseOffset
    • arrayIndexScale
  • Synchronization. 底层原始的同步管理
    • monitorEnter
    • tryMonitorEnter
    • monitorExit
    • compareAndSwapInt
    • putOrderedInt
  • Memory. 直接内存访问的方法
    • allocateMemory
    • copyMemory
    • freeMemory
    • getAddress
    • getInt
    • putInt

一些有意思的案例

避免实例化

allocateInstance方法在这几个场景下很有用:跳过对象的实例化阶段(通过构造函数)、忽略构造函数的安全检查(反射newInstance()时)、你需要某类的实例但该类没有public的构造函数。比如下面的类:

通过构造函数、反射方式创建实例与unsafe方式创建实例得到的效果不同:

由此想想你的单例真的能保证单例吗。

内存变更

这对C语言开发者很常用,他同时也是一种常见的跳过安全检查的方法,举个校验访问权限的例子:

客户端代码通过调用giveAccess()来校验访问权限,这种方式是很安全。不幸的是,不论谁调用这个方法,它都返回false。只有通过特殊手段来改变ACCESS_ALLOWED常量的值,从而使客户端获得访问权限。 下面是实例代码:

现在所有的客户端都可以获得无限制的访问权限。 实际上,我们可以通过反射的方式达到相同的效果,但有意思的是,使用unsafe可以让我们修改任意对象,甚至我们没有获取到对象的引用。 比如:在内存中有另外一个Guard对象在当前这个guard对象的下一个内存区块中,我们可以用下面的方式来修改它的ACCESS_ALLOWED属性:

注意,我们并没有这个新的Guard对象的引用,16是Guard对象在32位机器中占用的空间大小。我们可以通过马上要讲到的sizeOf方法来获取对象占用内存的大小。

sizeOf

使用obectFieldOffset方法可以使我们实现C语言的sizeOf功能。下面的实现就可以返回对象占用内存的大小(不包含子对象占用内存大小,暂且把这种内存大小称作“浅内存大小”):

算法是这样的:遍历所有当前类和所有父类的非静态属性,获得每个属性的偏移量,找到最大的偏移量,加上字节对齐所需的内存大小。可能不一定全,但思路是清晰的。 有个更简单的方式实现sizeOf,如果我们仅根据类的结构来读取对象占用的内存大小(该对象在JVM 1.7,32位机器上)分配的偏移量为12:

为了违反内存地址,normalize方法用来把int转成无符号的long:

很巧,这个方法返回的结果和前面的sizeof函数是相同的。 当然,要用更好更安全更精确的sizeof方法,最好使用java.lang.instrument包,但它需要在JVM上挂一个agent。

浅拷贝

既然已经实现了对象浅内存大小的计算功能,我们可以很容易的添加一点功能实现对象拷贝。标准的做法是让你的对象类实现Cloneable接口,或者在你的对象中自定义拷贝功能,但它都不具有通用性。 浅拷贝:

toAddress把对象转换成内存地址,而fromAddress则正好相反:

这种拷贝的方式可以用于任意类型的对象,对象的大小会自动的计算。值得注意的是,在拷贝以后,你需要把返回的对象强制转换为你需要的类型。

隐藏密码

一个更有意思的使用Unsafe直接访问内存的用法是:从内存中移除不想要的对象。 大部分获取用户密码的API都使用byte[]或者char[]类型,为什么是数组类型? 其实原因主要是为了安全,因为我们可以在我们不需要他们时把数组元素置为null。如果我们以String形式获取密码,他将以对象形式存储在内存中,这时把这个对象置为null相当于是使该对象解除引用。但这个对象仍然在内存中,除非它被GC回收掉。 下面的这个坑爹的例子用相同内存大小的String对象替换内存中的原始对象:

这样做,密码就安全了吗? 注意:这个方式仍不安全。因为安全的做法是需要通过反射的方式把String中的char数组中的每个元素置为”?”:

多重继承

要知道,在java中是不能多重继承的。 但是如果我们可以把任意类型转换为其他任意类型时,就另当别论了,比如下面的例子:

这个代码片段给String类添加了Integer作为其父类,所以我们可以做下面的类型转换,而不抛出运行时 异常:

这里为了欺骗编译器,需要先把Integer对象转换为Object类型。

动态类

我可以在运行时创建类,比如根据编译后得到.class文件来创建类。其机制是把.class的内容读取到byte数组中,并传给defineClass方法。

从文件中读取到byte数组中:

当你必须动态的创建类时,这个很管用,比如:一些代理或者基于已有的代码进行面向切面的编程。

抛异常

你是否不喜欢catch异常?没问题,这么干吧:

这个方法抛出一个需要被主动catch的异常,但是你的代码可以不去catch或者重新抛出异常,它就行抛出了一个运行时异常一样。(把IOException()当做运行时一样抛出,但调用方不需要catch)

快速的序列化

这个更有用些。 每个人知道java标准的Serializable功能性能很差,它还需要你的类必须要有一个无参的构造函数。 Externalizable 要好一点,但他需要为类的序列化过程进行定义。 热门高性能的库,例如kryo有依赖,这可能不适合低内存的场景。 但是完整的序列化和反序列过程可以很容易用unsafe来实现。

序列化:

  • 通过反射方式构建对象的schema,只需构架你一次就行了
  • 使用Unsafe的getLong、getInt、getObject等方法来获取实际的属性值
  • 添加一个类用来存储这个对象
  • 把对象写入到文件或者其他输出中

你也可以添加压缩功能来减少空间消耗。

反序列化:

  • 创建一个序列化类的实例。这时候allocateInstance方法就很有用了,因为它不需要任何构造函数
  • 构建schema,和序列化中的步骤一样
  • 从文件或者其他输入中读取所有的属性
  • 使用Unsafe的putLong、putInt、putObject方法等来填充前面创建的对象实例

实际上,有很多细节没有一一展开,但这个思路就是这样的。这个序列化反序列确实很快。顺便说一下,在kryo中有一些使用Unsafe的尝试:http://code.google.com/p/kryo/issues/detail?id=75

大数组

你可能知道Integer.MAX_VALUE是java数组的最大长度。通过直接的内存配分,我们可以创建出很大的java数组,其上限是堆空间的大小。

下面是SuperArray的实现:

可以这样使用SuperArray:

事实上,这种方式使用的是堆外内存空间(off heap memory),在java.nio包中有部分使用。

它并不是分配在java堆中,也不受java gc的管理,要小心使用Unsafe.freeMemory(),因为它不做任何边界检查,任何非法访问都会导致jvm crash。

它在数学计算中还是有用的,可以通过代码操作大数组数据。它对于实时系统开发的程序员来说很有意思,可以打破大数组gc上的限制。

并发

UnsafecompareAndSwap的并发简单来说就是,它说原子操作,可用于实现高并发的无锁数据结构。

例如,在多个线程间共享自增数据的问题:

首先,我们定义简单的接口定义:

然后,我们定义工作线程CounterClient,它使用到了Counter:

测试代码如下:

首先实现一个非同步的counter:

输出为:

运行很快,但是完全没有线程管理,所以结果说不准确的。

下一步,实现一个最简单的java同步counter:

输出为:

基础的同步功能还是起到了作用,但是耗时太长了。

下面让我们尝试使用ReentrantReadWriteLock:

出为:

仍然说正确的,耗时也好一些。如果使用原子操作会怎么样:

输出为:

AtomicCounter效果要更好一些。

最后,看看Unsafe原始的compareAndSwapLong方法是否真那么神奇:

输出为:

嗯,好像跟原子操作差不多,难道原子操作就是用这个这个方式?(答案:是)

实际上,这个例子很简单,但说明了Unsafe的强大之处。

如我所说,原始的CAS(CompareAndSwap)操作可以实现无锁的数据结构,其背后的机制是:

  • 有一定的状态
  • 拷贝一份
  • 修改它
  • 执行CAS
  • 如果执行CAS失败了,则这个过程

事实上,CAS比你想象的复杂得多,因为有很多问题,比如:ABA问题,指令重拍问题等。

如果你真得很感兴趣,可以看看这个牛x的ppt:无锁HashMap

注意:在counter变量上增加volatile,可以避免出现无限循环的风险。

彩蛋

Unsafe文档中park方法的注释有个我见过的最长的语句(。。。我也翻译不了了):

Block current thread, returning when a balancing unpark occurs, or a balancing unpark has already occurred, or the thread is interrupted, or, if not absolute and time is not zero, the given time nanoseconds have elapsed, or if absolute, the given deadline in milliseconds since Epoch has passed, or spuriously (i.e., returning for no “reason”). Note: This operation is in the Unsafe class only because unpark is, so it would be strange to place it elsewhere.

结论

虽然,Unsafe有一堆好用的用法,但永远不要使用它。

Reference

翻译自:http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

 

 

 

 

java中坑爹的getter、setter方法的潜规则

众所周知的java中都会给类的属性写getter和setter方法,以getter方法为例,通常是get+属性的大写首字母+属性剩下的字符组成。比如:有个属性名为name,那么其getter方法名就是getName,如果name是boolean类型,那么getter方法就是isName,当然现在的getter和setter方法都不会自己手写了,一般是通过eclipse或者Intellij idea生成。

javaBean规范

javabean规范文档:http://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/,里面的有以下两个章节降到了具体的命名规则:

  • 8.3.1 Simple properties
  • 8.3.2 Boolean properties
  • 8.8 Capitalization of inferred names.

这个文档里面说明了,从getter和setter方法名如何推倒出propertyName:

  • 一般情况,把除去get或者is(如果是boolean类型)后的部分首字母转成小写即可,比如:getFoo –> foo
  • 如果除去get和is后端的部分,首字母和第二个字母都是大写,不作转换即可,比如:getXPath –> XPath

意想不到的getter和setter生成规则

现在大家都使用ide来帮忙生成getter和setter,那么ide的规则就算是我们的标准了吧,是否把javabean标准反过来推理就行了呢?经测试实际上还有一些场景可能都想不到,比如:首字母不用大写;多个不同的属性(字母大小写不同)的getter方法是相同的。看下面的例子(用eclipse生成getter测试):

看到这里,你是否有种隐隐的蛋疼感~~

什么时候你需要关注getter和setter方法的生成规则?

  1. 想要序列化为json对象时,如果你使用gson的话,基本没啥问题,但要是你使用了低版本的fastjson,那么可能会中枪了
  2. 要自己写代码拼装出getter和setter方法名,以此来通过反射查找Method时

个人建议

  1. 属性的前两个都以小写开头
  2. boolean类型不要用is开头

Disqus用Go取代Python来开发实时系统

翻译自:Update On Disqus: It’s Still About Realtime, But Go Demolishes Python

disqus realtime system
disqus realtime system

我们上一篇关于Disqus的文章:“如何让Disqus在2秒的延迟内,能够每秒处理16.5万条消息”,已经过时了,当时Disqus的码农们曾为忙于开发,不说话,所以他们现在在干什么我们知道的不多,不过我们在 C1MM and NGINX 上由John Watson做了个小的分享,还有一篇文章Trying out this Go thing

 

现在Disqus有了一点提升:

  • 13亿的UV
  • 100亿PV
  • 5亿用户在线用户
  • 300万个社区
  • 2500万条评论

这些都和实时性相关,但在他们的实时系统中,Go语言取代了Python:

  • 最初的实时后端是用轻量级的Python+gevent写的
  • 实时服务混合了CPU密集型任务和大量网络IO。Gevent处理网络IO没有什么问题,只是CPU竞争比较高,成为瓶颈
  • 换成Go语言解决了CPU竞争的这个主要问题
  • 仍然在5台Nginx机器上运行
    • 使用NginxPushStream,它支持EventSource、WebSockt、Long Polling和Forever Iframe
    • 所有的用户都链接到这写机器上
    • 通常一天每台机器承载每秒3200个连接,100万连接,每秒传送15万数据包并接收13万数据包,数据发送速度150mbits/s,数据接收速度80mbits/s,端到端的延迟在15ms内(比javascript选人一条评论还要快)
    • 有许多资源消耗问题。Nginx和操作系统的配置缓解了这些问题,优化了一个场景:连接很多但传输的数据很少
  • 在别的优化前,先把网络带宽用完
    • 用10G的网卡帮助很大
    • 启用gzip压缩作用很大,不过在gzip模式下,Nginx为每个连接预分配不少内存,而评论大都很小,这导致内存消耗更厉害了。因为减少Nginx的缓存大小可避免内存溢出的问题。
  • 随着消息速度的增加,高峰期每秒需处理1万多消息,到了机器的极限,端到端的延迟可能会达到几秒甚至几分钟
  • 切换到Go语言
    • 使用Go是因为它的的性能、天生就支持并发、Python码农易上手
    • 在一周时间里,Go语言版的系统就完成了,并拿到了很好的效果:
      • 端到端延迟平均少于10ms
      • 目前CPU消耗大概在10-20%,降低了很多
    • 没有选择Node是因为Node不适合处理CPU密集型的任务
    • Go不直接访问数据库。它从RabbitMQ队列中消费事件并发布到Nginx前端。
    • 没有使用Go语言相关的框架。这只是一个很小的部分,其他部分仍然是使用Django
  • 要的是把资源利用得更好,而不是堆机器:
    • 做了这么多的工作,但我们不是横向的扩展机器,因为堆很多机器并不总是解决问题最好方案。最后,速度更快的产品也能体现其价值。

 

函数式编程——把函数作为参数

函数式编程
函数式编程

网上不少函数式编程的概念都说得好抽象,看不懂,还不如码点代码来得清晰。本文以go语言为例,实践一个函数式编程中小点:把函数作为参数。这也是函数式语言与其他语言的一个很重要的区别,可以显著减少代码量。

实践:把函数作为参数

先看段段代码:

上面的代码,每个err都要判空,还要根据不同情况打印不同级别的日志。代码显得很冗长,难看。怎么办?把判空和输出日志的部分抽成方法,代码应该会清爽很多,改造后如下:

上面的代码,把冗余逻辑抽成单独的方法,主体代码逻辑就简化了,代码清爽了许多。但问题是原来的一个main函数,变成了4个函数,代码行数明显增加了。

好吧,那把三个抽出来的函数合成一个,这样判空的代码就只需要写一份了。看看这样吧:

嗯,这样确实方法数少了,代码行数少了点,但是新增了3个常量。好吧,来看看函数式编程会怎样吧:

上面的代码把函数作为参数,瞬间将代码行数降到最低,逻辑仍然非常清晰。用什么输出日志都由调用方来决定。

这算是一个函数式编程的实践案例吧。尽管用java面向对象的方式也能实现类似的效果,但是想想写出来的代码行数一定不少。话说代码越短,阅读代码的人接受的信息越集中,越容易看懂代码。有时候用函数式的思维来解决问题,你会发现代码好短。

Reference

函数式编程思想

Go语言出中文书籍啦!

现在比较靠谱的中文的Go语言书籍有3本:

1. 首先推荐一下:《Go语言编程》——由一个做云存储的公司“七牛”的两位大牛:许世伟和吕桂花 编著

这本书我第一时间买了,很适合阅读,书不厚,但是讲得很全面,很精练,适合有一点编程基础的同学阅读,而且这本书还可以作为手册来参考。

2. 还有一本开源的免费书籍:《Go Web 编程》——由一位乐于分享知识的朋友写的,目前还在编写阶段,但是已经可以直接在线阅读了,也可以把书下载下来,生成html来阅读。非常欣赏这位gofans的精神,乐于分享,共同进步,同时也希望有更多的人能参与到这本书的编写中来,不论你是熟悉go的还是不熟悉的go的。

这本书主要是从web开发的角度来讲解go语言的,内容讲得很基础,适合初学者学习,想用go来做web开发的同学看看这本书是很不错的。

3. 第三本书是:《GO语言 云动力》,这本书是最早的中文go语言书籍,但是正因为出来得较早,所以书的质量可能不是最好的。

windows下Go语言开发环境搭建

Golang是Google的第二门编程语言,但是Golang与simple和Dart不同的是,Golang是一门系统级编程语言,也就是说他和C++、Java是同类型的语言。学习Golang是我自己的爱好和兴趣,我个人对这个语言比较看好,毕竟这个语言是在已经有了C++、Java、Javascript、Python等强大并极其流行的语言下诞生的,他就是为了补偿现有编程语言的不足,提取他们的精髓。就此创造出了一门能适应当代快速开发和迭代、拥抱变化、大数据、多核高并发的场景。该语言的被号称是:互联网时代的C。这就意味着:1.具有互联网web开发所需的特点,即开发高效;2.具有C语言一样的运行速度,即运行高效。相信这两个特点会让你找到归宿了吧。希望Golang能流行起来,让大家见到它的魅力。下面介绍下Go语言开发环境搭建的步骤:

1. 安装

Windows上的安装步骤如下:

  • 下载Go开发环境:http://code.google.com/p/go/downloads/list
  • 如果你下载的是exe或者msi的安装包,就直接安装了,不需要配置环境变量
  • 如果下载的zip,就将其解压;配置环境变量
    • GOROOT = D:Program Files\go
    • GOBIN = %GOROOT%\bin (现在的go1.5已经不需要配置这个环境变量了)
    • GOARCH = 386                   (如果你的系统是32位则是386,如果是64位则是amd64(现在的go1.5已经不需要配置这个环境变量了)
    • GOOS = windows                (如果你的操作系统是windows,如果是linux请写linux)(现在的go1.5已经不需要配置这个环境变量了)
    • path:如果末尾如有没有分号,则添加之,然后再末尾添加%GOROOT%\bin
  • 安装好了后,在cmd中运行go,如果出现go命令的帮助信息,则go开发环境安装正确。

Linux、FreeBSD、OS X上的安装请看http://ioio.name/golang-install.html或者官网的介绍:http://golang.org/doc/install.html

2. 测试(Windows下)

在任何地方创建一个文件:goTest.go

写入如下代码后保存:

在cmd中cd到该文件的路径下,用一下命令进行编译、链接和执行

go run goTest.go

如果输出hello world!表示开发环境安装成功

Go 1的性能也很喜人,比较新的一个对Go 1和Node.js的性能对比测试:http://www.cnblogs.com/QLeelulu/archive/2012/08/12/2635261.html 

3. 开发工具