Integer、Long等自动拆箱类型为啥会抛NullPointerException

通常的NullPointerException异常其实是很好判断,一定是有某个对象是null导致的。但是java中数字都有自动装箱和拆箱功能,它简化了我们的代码,隐藏了其背后的执行步骤,导致程序抛出异常时我们会有意识的忽略了数字类型也可能会抛NullPointerException。 其实自动装箱和拆箱在字面上很容易使人对其理解的不准确或者不完整,大家都知道装箱就是从基础类型自动转换成对应的对象类型,拆箱就是从对象类型自动转换成基础类型;字面上很好理解,但是背后到底是如何实现的,其实有一种道不清说不明的感觉。自动装箱是不会抛异常的,但是自动拆箱则有可能会,所以本文从三种代码场景,以字节码的角度,还原自动拆箱背后的逻辑。

场景一:赋值时自动拆箱抛NullPointerException

上面的代码在把变量a赋值给b时就会抛出NullPointerException,使用javap Test.class命令查看其对应的字节码如下:

可以看到main方法行号为3的字节码调用了Long.longValue()方法,行号6的字节码才是把变量a赋值给b,显然由于a是null,所以调用longValue()方法自然会抛空指针了。

场景二:方法传参时自动拆箱抛NullPointerException

上面把变量a传参给方法increment时就会抛NullPointerException,还是用javap Test.class查看其字节码如下:

可以看到main方法行号为3的字节码调用了Long.longValue()方法,行号6的字节码才是真正的执行increment,显然由于a是null,所以调用longValue()方法也会抛空指针了。

场景三:用于大小比较时拆箱抛NullPointerException

同样用javap Test.class查看字节码内容如下:

同样的在行号8的字节码通过Long.longValue()方法把变量n1从Long转换成了long,然后行号8为加载变量n2,行号9才是比较两个long(注意不是比较Long)。

上面提到的字节码中的行号其实是字节码指令占用空间的偏移量,为了便于理解,暂以行号来理解

总结

拆箱的过程其实是调用了对应的XXX.xxxValue()方法,比如:Integer.intValue(), Long.longValue(), Short.shortValue(), Byte.byteValue(), Float.floatValue(), Double.doubleValue(),如果这个对象是null,那么就会抛出空指针异常。

怎样让你的代码更好的被JVM JIT Inlining

JVM JIT编译器优化技术有近100中,其中最最重要的方式就是内联(inlining)。方法内联可以省掉方法栈帧的创建,同时增加了CPU指令cache的命中率,方法内联还使让JIT编译器更多更深入的优化变成可能。本人在fastxml(速度比XPP3(基于xmlpull)还快的xml解析器)开源项目中针对方法内联进行了很多学习和实践,这里总结一下,介绍一下怎么让你的代码更好的被JVM JIT Inlining。

Inlining相关的启动参数

上一篇博客《Java JIT性能调优》中介绍了inlining相关的几个参数,这里copy下: jvm可以通过两个启动参数来控制字节码大小为多少的方法可以被内联:

  • -XX:MaxInlineSize:能被内联的方法的最大字节码大小,默认值为35B,这种方法不需要频繁的调用。比如:一般pojo类中的getter和setter方法,它们不是那种调用频率特别高的方法,但是它们的字节码大小非常短,这种方法会在执行后被内联。
  • -XX:FreqInlineSize:调用很频繁的方法能被内联的最大字节码大小,这个大小可以比MaxInlineSize大,默认值为325B(和平台有关,我的机器是64位mac)

可见,要想inlining就要让你的方法的字节码变得尽可能的小,默认情况下,你的方法要么小于35B(针对普通方法),要么小于325B(针对调用频率很高的方法)。

Inlining调优的工具

同样的上一篇博客《Java JIT性能调优》中也介绍了非常牛x的JIT优化工具JITWatch,该工具的详细文档请看这里:https://github.com/AdoptOpenJDK/jitwatch

为Inlining减少方法字节码

通过JITWatch中提示或者在启动命令中添加-XX:+PrintCompilation  -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining来输出提示信息,我们可以很快定位哪些方法需要优化,从而针对性的调优那些执行频率很高但没有inlining的方法,或者那些很小但不足以小到35Byte的方法。减少方法字节码的方法有很多,下面介绍我在fastxml项目中实践过的9种方法。

方法1. 把分支语句中的代码段抽取到方法中

我们的代码里面常常会有分支语句,比如:if-else、switch。但是我仔细想想,是不是所有的分支都有均等的执行机会,是不是很多时候,某些分支能进入的概率很小或者几乎没有。如果某个分支执行的概率很小,而且这个分支中的代码量不算小,那把这个分支中的代码抽取成一个方法就可以大大缩减当前这个方法的字节码大小。下面举一个著名NIO框架Netty中的一个例子,看优化前的代码:

代码中的config().isAutoRead()默认情况是false,也就是说,在没有专门配置的情况下,这个分支不会进去,而且一般人不会把这个配置为true,所以可以把这个if分支中的代码块抽取为方法,这样方法就变小了一些,而代码的功能没有减少。修改后的代码如下:

这样就很容易的把方法的字节码大小减少了一些,这样read()方法就可以inlining了。详见这篇文章:http://normanmaurer.me/blog_in_progress/2013/11/07/Inline-all-the-Things/

方法2.移除无用的代码

现在IDE(idea、eclipse等)功能很强大,往往能在开发阶段就能提示我们哪些代码是冗余无用的代码,请及时的删除那些无用代码,因为它们会占用方法的字节大小。此处我要说的不是这种IDE能提示我们的场景,而是隐含在我们编写的代码中的无用代码,比如下面的例子:

在方法parseTrimedString的开头处对length进行判断,乍一看,似乎没有什么不合理的,如果要处理的数组长度为0就直接返回null。逻辑上没有问题,IDE也不会提示这一段代码是无用的,但是仔细想想,要是length==0时,要是没有这个判断会怎么样呢? 第一个for循环中有begin<last的判断,避免了循环体中的bytes[begin]数组越界;同样的,第二个for循环里数组下标也被last>begin保护起来了,防止越界;再看看parseString()方法中正好有对length的判断,也就是说方法parseTrimString内部不再需要对length等于0的验证了。这一段代码可以移除。 类似这种非0、非null的判断在我们日常的代码中也是很常见的,其实可以通过一定的约定来减少这种判断,比如:按照约定保证每次方法的返回都不为null,若返回类型为String数组,则返回new String[0];若返回类型为Map<String, String>,则返回new HashMap<String, String>()。

方法3. 抽取重复的代码

很多时候,程序员因为懒惰不愿修改别人的代码,怕承担风险,而是有时候偏向于复制代码,这可能会导致一个方法体内部出现重复的表达式或者代码段。这时候应该把相同的幂等的表达式抽取为临时变量,把相同的代码段抽取为成员方法(不仅可以减少当前方法的字节码,还可能增加方法的复用率,为JIT优化提供了更多可能)。 通过抽取重复的代码来减少字节码,这种方式的效果是显而易见的。这里重点讲解一下,相同的幂等的表达式抽取为临时变量前后的字节码大小差异,看下面的代码:

这段代码中重复出现了docBytes[cursor],通常大家可能会认为这种数组取下标的操作没有几行字节码,应该不耗时,只有这种objA.getField1().getSubField2().getSubSubField3()的字节码多。其实不然,先看看仅docBytes[cursor]一句话的字节码有多少:

这里看出docBytes[cursor]对应9个字节的5行字节码,而这个表达式在上面的语句块中出现了3次,所以一共占用了9*3=27个字节。如果抽取成临时变量那么第二次和第三次使用临时变量即可,而使用临时变量仅占用1个字节,这样又可以减少近18字节。当一个方法里面重复出现的变量越多,优化效果就越明显。

方法4. 优化常量加减运算

先看代码:

上面的代码很简单,其中i=i+2这种写法也很常见,那么看看这行代码对应的字节码是什么样的:

这行代码已经很简单了,只占用4个字节,还有优化空间吗?当然有,我们还有一种常见的写法:

再看看这种写法的字节码:

这一行字节码占用3个字节的空间,而且只有一行字节码。这样一个微小的改动就可以节省1个字节,缩减3条字节码的指令条数。

方法5. 移除无用的初始化赋值

现在一些高级IDE也支持无用初始化的提示,比如:idea(eclipse还不支持这个功能)。如果IDE提示,请尽量移除这些无用的初始化,比如下面的例子:

显然对临时变量sum的初始化是没有作用的,上面的java代码产生的字节码有这些:

如果把对sum的初始化移除,java代码如下:

代码变化很小,改后的字节码如下:

通过对比可以看出移除对sum的初始化后, 字节码少了两行(2个字节)。有时候为了减少字节码就是需要一个字节一个字节的扣,没有办法。

方法6. 尽量复用临时变量

临时变量的初始化是会占用字节码的,减少不必要的临时变量无形之中也减少了临时变量的初始化。看下面的例子:

这个例子中,习惯性的使用临时变量i来作为for循环数组的下标,但是这个临时变量i真的有必要吗? 变量begin虽然是方法的参数,但是它也是这个方法的临时变量,而且它是java中的原始类型,改变begin的值不回对调用toString方法的调用方有任何影响,而且变量begin在方法中没有其他作用,很自然可以使用begin来代替i的下标作用。修改后的代码如下:

可以看到代码中少了一句:int i=begin; 这将减少2字节的2行字节码。 当你很需要这个方法被inlining时,每个字节的减少都来之不易。

方法7. 减少传参个数

有时我们写代码时,为了把方法的输入表达得更准确,会给出一个精确的参数列表,其中可能有多个参数来自相同的对象。比如下面的例子:

这代代码中,可以看到有两处抛出异常,每次创建异常都需要传三个参数:message、row、column。而上面的这个例子,很显然row和column都来自当前对象this,我在优化fastxml时,发现这个地方也可以优化一下,毕竟this.getRow()和this.getColumn()这两个表达式分别占用了4个字节,如果把formatError方法的参数减少为message、parser(即this),就可以减少7个字节的字节码,因为传this仅占用1个字节。

方法8. 把多个if判断转变成map的contain或者数组取下标操作

举个实际的案例,fastxml中为了解析xml中的标签名中的字符是否符合xml规范,我需要做如下的判断:

这一长串的条件判断其对应的字节码如下:

上面的代码中冒号前的数组为当前行字节码的第一个字节为整个方法体字节码中的位置。可以看到这里面的有11个的if判断,而每次判断都需要加载if判断的两个操作数,第一个操作数为变量b,第二个操作数为常量(比如:“:”),由于代码太长,此处以b == ‘.’为例,其字节码对应58~61处,共7个字节,三行字节码。 由于这个方法相比较的字符串都在ASCII码0~128范围内,所以我这里可以把比较转换为数组下标的方式,数组下标为byte的数值,数组的元素值为当前下标是否是符合xml规范的字符,修改后的代码如下:

可以看到方法变短来很多,字节码也减少了很多,执行速度也快很多。当然Java中原始类型比较适合用数组的方式解决这种众多if判断的问题,如果是对象的话,也可以用Map的方式。

方法9. 必要时把JDK中的类重写成简单的版本

一般而言JDK中的API都是久经考验的、非常完备的、通用的、众多高手智慧的结晶,但是也正是因为它的完备性和通用性,就导致其必然的损失了针对性。当优化到了一定程度后,就需要针对数据场景或者业务场景来进行针对性的优化来,举个例子:给一个基本排好序的数组进行排序时,冒泡比快速排序要快得多,因为这种数组最适合冒泡排序(O(n)时间复杂度),而快速排序时是O(nlog(n))。 看一个实际案例,下面的代码在fastxml的性能测试输出的编译和内联的log中看到StringBuilder的append方法太大了,导致无法内联到下面的方法中。

看看StringBuilder的append方法内容是什么样子:

可以看到append方法一层层调用了几层方法,每个方法不算太大,用JITWatch可以看到优化建议:

The call at bytecode 2 to

Class: java.lang.AbstractStringBuilder

Member: public AbstractStringBuilder append(String)

was not inlined for reason: ‘callee is too large’

The callee method is greater than the max inlining size at the C1 compiler level.

Invocations: 1031

Size of callee bytecode: 50

可以看到public AbstractStringBuilder append(String)这个方法稍大了一点,导致没有内联。这会导致多出一次方法栈,而这个方法可能在程序运行中调用频率很高,如果这个方法不能inlining,势必降低了性能。jdk的源码我们改不了,那我们可以选择不使用jdk的类,而是根据自己的实际场景写一个更简单的类,比如下面这个:

这个代码把安全检查都移除了,也把冗长繁杂的校验和数组扩容都直接移除了,代码变得简洁高效。这么做不会出错么?当然不会,因为在fastxml中,调用方已经保证了其不会出现数组越界,而且保证了初始长度length就是最大长度,所以多一层的安全检查是没有必要的,也没有必要考虑去扩容和复制数组了。在fastxml中,使用这个简化后的代码,使性能提升了10%。

总结

上面这9种方法都是在使用了JITWatch后,根据其提示绞尽脑汁想到的一些办法,通过这些优化,fastxml的性能提升了近1倍,现在fastxml的性能已经是XPP3的1倍左右。优化方法还有很多,针对不同的场景,会有很多不可思议的优化方法,这需要不断的挖掘。欢迎指正。 最后引用大牛Donald Knuth的一句话:

过早的优化是万恶之本

 

Java JIT性能调优

JVM自动监控这所有方法的执行,如果某个方法是热点方法,JVM就计划把该方法的字节码代码编译成本地机器代码,同时还会在后续的执行过程中进行可能的更深层次的优化,编译成机器代码的过程是在独立线程中执行的,不会影响程序的执行;除次以外,JVM还对热点方法和很小的方法内联到调用方的方法中,减少方法栈的创建。这些就是JIT(just in time)。

JIT编译器有近100种优化方式

JIT编译器有近百种优化方式
JIT编译器有近百种优化方式

其中以下三种方式效果非常明显:

  • 把bytecode编译成本地代码(native code):编译后的代码保存在一个特殊的堆上,除非相关的类被卸载,或者本地代码的优化被取消。这个cache有一定的大小限制(可通过启动参数-XX:ReservedCodeCacheSize来修改cache的大小),如果这个cache被装满,则JVM无法编译出更多的本地代码,但通常说不会碰到这种情况的。
    • hot method:默认情况,方法执行次数超过10000次的方法,jvm会编译成本地二进制代码,这个数值可以通过设置启动参数-XX:CompileThreshold=10000来修改。
    • On Stack Replacement (OSR):如果某个循环执行的次数非常多,那么这个循环体代码也可能会编译为本地代码
  • 分支预测(Branch Prediction):降低分支条件判断的结果的随机性,使CPU指令流水线缓存命中率提升
  • 方法内联(inlining,对性能的提升很大):方法内联可以减少方法调用,从而减少方法栈的创建。相信大家都知道循环的速度比递归快很多,就是这个原因,另外方法内联后,还使得一些JIT更深入的优化变成可能。jvm可以通过两个启动参数来控制字节码大小为多少的方法可以被内联:
    • -XX:MaxInlineSize:能被内联的方法的最大字节码大小,默认值为35Byte,这种方法不需要频繁的调用。比如:一般pojo类中的getter和setter方法,它们不是那种调用频率特别高的方法,但是它们的字节码大小非常短,这种方法会在执行后被内联。
    • -XX:FreqInlineSize:调用很频繁的方法能被内联的最大字节码大小,这个大小可以比MaxInlineSize大,默认值为325Byte(和平台有关,我的机器是64位mac)。

这些优化方法通常是层层依赖的,所以当JIT优化后的代码被JVM应用,就会开始尝试进行更上一层次的优化。因此我们写代码的时候,应该尽量往这些优化方式上面靠。

输出JIT编译和内联过的方法

在JVM启动参数中添加三个启动参数,比如下面的命令,把编译信息输出到inline.log文件中,便于后续使用grep命令分析:

inline.log中内容类似这样:

  • 第1列  31:为JVM启动后到该方法被编译相隔的时间,单位为毫秒
  • 第2列  23:编译ID,用来跟踪一个方法的编译、优化、深度优化
  • 第3列  s!:s是指该方法是synchronized,感叹号是指该方法有对异常的处理
  • 第4列  sun.misc.URLClassPath::getLoader:被编译的方法和类名
  • 第5列  (136 bytes):方法的字节码大小
  • 第6列 inline(hot):表示该方法被内联了,且调用频率很高,这一列还有其他值,比如:
    • inline (hot): 该方法被内联了,且调用频率很高
    • too big: 该方法没有被内联,因为方法字节码比-XX:MaxInlineSize的值大
    • hot method too big: 该方法被调用的频率很高,但是方法的字节码比-XX:FreqInlineSize的值大

inline.log文件内容中的方法还以tab缩进的方式来体现方法调用链的层次结构,非常易懂。

输出JIT编译的细节信息

通过添加参数-XX:+PrintCompilation,可以看到的信息其实并不具体,比如:那些方法进行了内联,内联后的二进制代码是怎么样的都没有。而要输出JIT编译的细节信息,就需要在JVM启动参数中添加这个参数:

输出的编译信息,默认情况是在启动JVM的目录下一个名为:hotspot_pid<PID>.log的文件

如果想指定文件路径和文件名的话,可以再添加一个启动参数:

输出的是一个很大的xml文件,可能有几十上百兆,下面摘出部分内容如下(文件中的汇编代码太长,就不贴了):

这些内容很难读懂,建议使用JITWatch(https://github.com/AdoptOpenJDK/jitwatch/)的可视化界面来查看JIT编译的细节信息。同时JITWatch还可以给出很多优化建议,给我们有效的优化代码提供参考,详见下文。

JIT编译模式

上面的输出的细节编译信息inline.log文件中,有个字段上“compiler=C2”,这里的C2就是JIT的编译模式,C2表示这个方法进行了深度优化。下面介绍下JIT的编译模式

C1: 通常用于那种快速启动的GUI应用,对应启动参数:-client

C2: 通常用于长时间允许的服务端应用,对应启动参数:-server

分层编译模式(tiered compilation):这是自从Java SE 7以后的新特性,可通过添加启动参数来开启:

这个特性在应用启动阶段使用C1模式以达到快速启动的效果,一旦应用程序运行起来以后,C2模式将取代C1模式,以进行更深度的优化。在Java SE 8中,这个特性是默认的。

JITWatch

前面也提到了,JITWatch可以通过可视化界面来帮助我们分析JVM输出的JIT编译输出日志,还可以帮助我们静态分析jar中的代码是否符合JIT编译优化的条件,还可以以曲线图形的方式展示JIT编译的整个过程中的一些指标,还给我们的代码提意见和建议,非常好用的工具。

下载

JITWatch需要在github上把代码clone下来,然后用maven来运行,地址为:https://github.com/AdoptOpenJDK/jitwatch/

安装hsdis

如果在jvm的启动参数中添加了下面的启动参数:

但是你发现启动你的java程序后,有如下的报错信息:

或者启动啦JITWATCH后,打开了某个编译信息log文件,但是看不到每个方法编译后的汇编信息,且那么你就需要安装hsdis。hsdis可以帮助我们查看编译后的本地代码,具体可以参考JITWatch提供的文档,根据自己的系统类型来选择安装:https://github.com/AdoptOpenJDK/jitwatch/wiki/Building-hsdis,如果你是mac,可以参考这篇文章:http://nitschinger.at/Printing-JVM-generated-Assembler-on-Mac-OS-X/

如果安装了hsdis库后,仍然在JITWatch中看不到汇编信息,那你检查下环境变量配置是否正确,实在不行可以尝试下重启电脑。

运行JITWwatch

在代码根目录下执行launchUI.sh(Linux/Mac)或则launchUI.bat(windows)

如果你使用maven,也可以在代码根目录下这样运行(其他运行方式,请参考JITWatch的github首页)

如果你使用的是mac,而且idk版本是jdk7,且运行mvn clean compile exec:java时出现下面的错误和异常时: 

请在org.adoptopenjdk.jitwatch.launch.LaunchUI类的main函数开头处添加下面的代码(或者直接使用我fork修改好的JITWatch):

然后重新运行即可看到JITWatch的界面。

用JITWatch来帮助优化代码

首先点击“Open Log”按钮,选择前面提到过的hotspot_pid<PID>.log文件,然后点击“Start”分析该文件。随后就会在左边生成程序运行过程中加载的类及其目录结构。选择某个类后,右侧会展示该类对应的方法。这些方法中可能部分方法前面有个绿颜色的勾,这说明这个方法被编译成本地代码,选中这个方法后,可以在下方看到该方法具体信息,比如方法调用次数,方法大小等。如下图所示:

jitwatch加载JIT编译log文件
jitwatch加载JIT编译log文件

这个界面中,顶部的工具栏都可以自己尝试一下,个人觉得“TopList”和“Suggest”比较直接,我们根据这两个就可以快速的定位需有优化哪些代码了,大体是什么原因导致未编译或者未内联。

选中方法后,点击“TriView”即可查看该方法和字节码和编译后的汇编代码,如下图:

jitwatch方法字节码和编译后的本地代码查看
jitwatch方法字节码和编译后的本地代码查看

如果你左边的java代码看不到,那你就需要在上一个界面中点击“Config”来添加源码路径或者源码文件以告诉JITWatch从哪里找源码;如果你右边的汇编代码看不到,说明你上面的hsdis未安装好,请重新安装。

此时,点击上面的“Chain”按钮,即可看到该方法调用了哪些方法,以及这些方法是否被编译了,是否被内联了。如下图所示:

JITWatch查看方法编译和内联状态
JITWatch查看方法编译和内联状态

总结

JIT的功能能显著提升java程序的性能,尤其是编译为本地代码和内联功能。内联需要方法比较小,也就是说写代码时就尽量把方法写得更小,让方法的复用度更高,复用的越多,就越可能被编译为本地代码。高性能的框架和类库针对JVM的JIT功能进行优化是非常有必要的,JVM提供的调试输出参数和JITWatch这样友好的工具能大大帮助我们快速的发现和定位需要优化的代码,大大提升了效率。

尽管我们可以手动调整JIT相关的一些参数,来让我们的更多的方法被编译和被内联,但一般不建议这么做(大牛都这么说)。

JIT编译成本地代码的过程也是需要消耗时间的,而且编译后本地代码不一定会使用(made not entrant,如果JVM根据一段时间的执行后进行了某项优化,但是在后来的某次执行时验证之前的优化是不完备的,那么JVM会取消这个优化,继续用解释执行的方式来执行字节码),所以并不是把所有或者大部分代码都编译一定会性能最优,那有可能也是灾难。

我所了解的JVM JIT性能调优的大致原理和方法就是这些,如有错误请指出。

性能优化永远是最后一步,不要提前过早开始性能优化。

Reference

如果你有耐心,就看看下面的文章吧,因为它们比我写的更详细

http://www.oracle.com/technetwork/articles/java/architect-evans-pt1-2266278.html

https://www.chrisnewland.com/images/jitwatch/HotSpot_Profiling_Using_JITWatch.pdf

http://www.docklandsljc.uk/presentations/2015/ChrisNewland-JITWatch.pdf

http://blog.csdn.net/hengyunabc/article/details/26898657

https://advancedweb.hu/2016/05/27/jvm_jit_optimization_techniques/

JVM启动参数:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

JITWatch使用文档:https://github.com/AdoptOpenJDK/jitwatch/wiki (右侧的页面目录分类很清晰)

https://advancedweb.hu/2016/05/27/jvm_jit_optimization_techniques/

https://advancedweb.hu/2016/06/28/jvm_jit_optimization_techniques_part_2/

mac输入法切换快捷键用不了

新买的mac各种不好用不会用,拼音输入法居然不能和英文通过快捷键自由切换。网上各种搜也没有找到我想要的问题。因为大家都说按command+space就可以切换,但是我的却不行。

后来在System Preference > Keyboard中发现,拼音输入法的切换按钮上ctrl+space,但是实际上ctrl+space是打开的spotlight,mac系统初始化做得真够烂的,应该是快捷键冲突了。

于是我重置了快捷键,发现spotlight变成command+space了,输入法变成了ctrl+space,试了试,spotlight可以用,但是输入法不能切换。晕,mac默认的快捷键都不能正常工作。

于是我尝试把spotlight和输入法的快捷键换一下,勾选了输入法切换快捷键,修改快捷键为command+space,然后勾选spotlight快捷键修改成ctrl+space,经测试,都ok了。

总结下:

spotlight快捷键这样设置:ctrl+space

输入法快捷键这样设置:command+space

我的系统版本:10.11.5 (15F34)

 

macbook-pro安装adobe-flash-player后仍然不能看视频

想用刚买的mac pro看看优酷,结果提示需要安装flash player插件,然后我按照提示安装了adobe flash player,重启了safari,结果还是看不了视频,仍然提示未安装flash player。重启了机器后仍然是不行。

各种百度google搜索都没有找到办法。后来自己摸索,整好久才发现,原来默认插件安装是PPAPI Plug-in,它不是针对safari浏览器的,而是针对opera和chrome的。而NPAPI Plug-in这个才是真对safari、firefox、遨游的。要检查你安装的flash player插件是否支持safari可以这样看:系统偏好(System Preference)> Flash Player > Updates里面,效果如下:

Flash Play设置
Flash Play设置

如图所示,如果你的两个插件中NPAPI插件为安装,那么你就需要安装这个插件,安装方法如下:

首先几点上图中的install now,然后会自动打开一个adobe的网站,

安装flash player
安装flash player

 

不要点install now,因为那样会再次安装PPAPI插件,而这个插件对safari没用。应该点击左边的一个链接“Need Flash Player for a different computer”,点了以后看到的效果应该是这样的:

安装支持safari和遨游的flash player
安装支持safari和遨游的flash player

如上图的方式选择NPAPI,然后点击Downloading Now,后续按照提示一步步进行即可,然后重启浏览器就可以看视频了。

说实话,mac系统真心不如windows来得简单直接,不知道是转型mac不习惯,还是真的不好用~~

 

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/

 

 

 

 

精品工具收集(二)

Clover:类似chrome标签的文件夹管理

Hbuilder:国内团队开发的,非常好用的html5、html5+、js、css的IDE,还能和android、ios的webview打通,开发非常便捷高效,强烈推荐

everything:极其快速的搜索软件,可以在几十秒内对整个磁盘的所有文件建索引

MobaXterm命令行终端:比xshell、破解的SecureCRT、putty都要好用的终端,功能介绍参考这里:http://jingxuan.io/?p=182。有免费版

xmind:思维导图,可以帮助你总结、规划、发散思维。工作时常用的软件,有免费版

Glances:一款跨平台的系统监控工具,在linux上使用最爽,相当于时top、df、free等常用命令的集合版,可以让你一眼看到系统的所有指标,觉得让你眼前一亮。

findjar:jar包查找

ZOC:免费的ssh terminal终端,可以很方便的使用复制粘贴功能,字体也不错

sourceTree:非常简单好用的git客户端,界面清晰直观

VS Code:非常轻便的编辑器IDE,相比sublime、notepad++、ultraEdit、editplus,功能更强大一些,更像IDE一点,好友的支持了git客户端、调试和丰富的扩展插件;相比eclipse、visual studio、xcode又更轻量级一些,打开速度非常快。至少可以取代sublime和notepad++这类的编辑器,而且特别适合前端同学,因为它就是用node写的,编写插件很方便。

eclipse全面提速

你是否经常在等待eclipse的一些操作完成?

eclipse loading
eclipse loading

如果你看到这里,说明答案是yes。如果你苦于eclipse中响应很慢的功能,并且想给eclipse提速让开发更舒服些,就请看看下面的内容。

注意:可能一般人都建议加大内存。如果可以,你可以买个cpu好点的机器。弄个SSD让你的文件操作更快。我们假设你买不起这些,你所能做的就是启动eclipse实例,所有ubuntu的设置都是基于eclipse 4.3.0版本,build id:I20121031-2000,当然其他平台的版本的设置都差不多。

Eclipse优化

插件

当我第一次找到强大的插件时,我非常高兴。我安装的越来越多后,eclipse就用起来不舒服了。所以你可以从众多的插件中禁用一些不常用的插件,禁用不代表删除,你仍然可以启用他们。

 

禁用不常用的eclipse启动插件
禁用不常用的eclipse启动插件

一些插件可能在尝试体验时用一用,但是后来可能在也不用了,这种情况可以把它删掉。

卸载eclipse插件
卸载eclipse插件

eclipse.ini

下面的优化都需要修改eclipse所在目录下的eclipse.ini文件。

  • 给eclipse执行jvm。它可以让你使用自己的jdk,而不是系统环境变量所指定的jdk
  • 使用最新的jdk来运行eclipse。使用最新的jdk要好很多。
  • 使用sun的jdk来运行ecipse。原因同上。
  • 配置jvm虚拟机的启动参数。你可以自定义虚拟机参数,如果你觉得他们更合适(虚拟机参数介绍)。我使用下面的启动参数来增加堆的大小至768Mb,perm区设置为256Mb(内存总大小为3Gb)

你可以添加-Xverify:none参数来跳过jvm对class文件的校验,以此提升eclipse的启动速度,但这是很不安全的。

你还可以通过测试不同的垃圾回收器策略、server参数来测试eclipse的性能差异。以下为实验过程中使用的部分参数:

可以在这里查看所有的eclipse运行时参数,选择适合你的参数。

禁用动画

动画很酷,但如果可以的话,我总是在所有的工具中禁用动画。所以classic主题是我最常用的主题。

设置eclipse主题
设置eclipse主题

禁用label decoration

label decoration是项目、文件、类层级上的小图标,它可以有益于显性化文件的状态。比如:文件是否已经提交到git。很多插件都提供了这个功能,但很少有用。你可以仅留下你想要的,其他的禁用。

设置label decoration
设置label decoration

自动补全

有时在性能较差的机器上,或者当你有很多类的时,自动补全功能性能就会很差。一个很小的优化是减少自动补全的proposal。我仅保留了Java Proposals和Template Proposals:

eclipse Content Assist,eclipse自动补全设置
eclipse Content Assist,eclipse自动补全设置

取消验证器

如果你对自己的技术很自信,就可以暂停所有的校验器。就算出现问题,你也可以靠自己的能力定位问题,节省了你的开发时间。

取消eclipse校验器
取消eclipse校验器

关闭不相关的工程

如果你仅开发部分eclipse中的工程,那你最好把其他功能关闭掉。他们不会出现在eclipse索引中。你可以在workspace中手动关闭不相关的工程(Close unrelated projects)。但我推荐使用Working Set,你可以添加多个工程到一个Working Set中,这样就可以快速的在Working Set件切换。

关闭编辑器中不用的tab

编辑中太多的tab会导致eclipse性能下降,可以这样控制下tab的个数:

勾选 Close editors automatically 并设置 Number of opened tabs 为10。

控制eclipse编辑器中tab的个数
控制eclipse编辑器中tab的个数

禁用拼写检查

你还是个程序员吗?我觉得没有任何理由需要拼写检查功能。取消这个功能吧:

禁用auto build

如果你在意什么时候build你的工程,可以这样设置:

快捷键

仁者见仁,智者见智。就算你用超快的IDE功能,但如果你要花10个动作才能实现一个操作,那你的开发过程就不算快。把你最常用的动作配置成快捷键,并记住他们,几周的使用后,你的开发效率将由显著提升。

为了逼着自己使用所有的快捷键,我直接把工具栏给禁用了。

参考链接

 

本文译自:http://mishadoff.com/blog/eclipse-speedup/

 

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开头

Flex布局学习笔记

flex布局
flex布局

本文是根据阮老师的博客《Flex布局教程:语法篇》汇总的,便于以后自己查阅。另外阮老师还有一篇讲解很经典的flex布局实战博文《Flex布局教程:实例篇》。

flex容器有6个属性

  • flex-direction:决定主轴的方向(即项目的排列方向)。
    • row(默认值):主轴为水平方向,起点在左端。
    • row-reverse:主轴为水平方向,起点在右端。
    • column:主轴为垂直方向,起点在上沿。
    • column-reverse:主轴为垂直方向,起点在下沿。
  • flex-wrap:定义,如果一条轴线排不下,如何换行。
    • nowrap(默认):不换行。
    • wrap:换行,第一行在上方。
    • wrap-reverse:换行,第一行在下方。
  • flex-flow:是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
  • justify-content:定义了项目在主轴上的对齐方式。
    • flex-start(默认值):左对齐
    • flex-end:右对齐
    • center: 居中
    • space-between:两端对齐,项目之间的间隔都相等。
    • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
  • align-items:定义项目在交叉轴上如何对齐。
    • flex-start:交叉轴的起点对齐。
    • flex-end:交叉轴的终点对齐。
    • center:交叉轴的中点对齐。
    • baseline: 项目的第一行文字的基线对齐。
    • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
  • align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
    • flex-start:与交叉轴的起点对齐。
    • flex-end:与交叉轴的终点对齐。
    • center:与交叉轴的中点对齐。
    • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
    • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
    • stretch(默认值):轴线占满整个交叉轴。

flex中项目有6个属性(项目:item)

  • order:定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow:定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
  • flex-shrink:定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。
  • flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。如果值为100%,表示整个轴线长度空间
  • flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
  • align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

Reference