本文为做者原创,转载请注明出处。
咱们都知道Java是跨平台的,一次编译,处处运行,本质上依赖于不一样操做系统下有不一样的JVM。处处运行是作到了,但运行结果呢?同样的程序,在不一样的JVM上跑的结果是否同样呢?很遗憾,程序的执行结果没有百分百的肯定性,本篇分享我遇到的一些case。java
在Class类中,有一个方法是getMethods(),返回的是一个Method数组,该数组包含了Class所包含的方法。可是须要注意的是,其数组元素的排序是不肯定的,在不一样的机器上会有不同的排序输出。算法
public Method[] getMethods() throws SecurityException { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); return copyMethods(privateGetPublicMethods()); }
阿里的fastjson就曾经在这里踩到坑了,fastjson是序列化框架,当要去获取对象的某个属性值时,每每须要经过反射调用getter方法。好比,有个属性field,那么经过遍历Method数组,判断是否有getField方法,若是有的话,则调用取得相应的值。json
但对于boolean类型的字段,其getter方法有多是isXXX,也有多是getXXX,而fastjson在遍历时,只要判断有isXXX或者getXXX,就认定其为getter方法,而后当即执行该getter方法。数组
// 伪代码 for (Method method : someObject.class.getMethods()) { // 判断是否为getter方法 if(method.getName().equals("getField") || method.getName().equals("isField")){ // 经过getter取得属性值 return method.invoke(xxx, xxxx); } }
可是若是一个对象同时存在isA和getA方法呢?并发
private A a; private boolan isA(){ return false; } private A getA(){ return a; }
这个时候fastjson到底执行的是isA()仍是getA()呢?答案是不肯定,由于isA和getA在返回的Method数组中顺序是不肯定的,因此有的机器上多是经过isA()来获取属性值,有的机器上多是经过getA()来获取属性值,而这两个方法返回的一个是boolean类型,一个是A类型,致使fastjson在不一样机器执行的结果是不同的。框架
为何这个方法返回值不按照字母排序呢?每一个类或者方法名字都会对应一个Symbol对象,在这个名字第一次使用的时候构建,Symbol对象是经过malloc来分配的,所以新分配的Symbol对象的地址就不必定比后分配的Symbol对象地址小,也不必定大,由于期间存在内存free的动做,那地址是不会一直线性变化的,之因此不按照字母排序,主要仍是为了速度考虑,根据Symbol对象的地址排序是最快的。ide
线程Thread中有priority属性,表示线程的优先级,默认值为5,取值区间为[1,10]。虽然在Thread的注释中有说明优先级高的线程将会被优先执行,可是测试结果,倒是随机的。源码分析
以下,性能
static class Runner implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } } public static void main(String[] args) { Thread t1 = new Thread(new Runner(), "thread-1"); Thread t2 = new Thread(new Runner(), "thread-2"); Thread t3 = new Thread(new Runner(), "thread-3"); t1.setPriority(10); // t1 线程优先级设置为10 t2.setPriority(5); // t2 线程优先级设置为5 t3.setPriority(1); // t3 线程优先级设置为1 t1.start(); t2.start(); t3.start(); }
若是是严格按照线程优先级来执行的,那么应该是t1执行for循环,而后t2执行完for循环,最后t3执行for循环。但实际上测试结果显示,每次执行的输出顺序都没有遵循这个规则,而且每次执行的结果都是不同的。测试
---- console output ---- thread-2---0 thread-2---1 thread-3---0 thread-1---0 thread-1---1 thread-1---2 thread-3---1 ...... ......
线程调度具备不少不肯定性,线程的优先级只是对线程的一个标志,但不表明着这是绝对的优先,具体的执行顺序都是由操做系统自己的资源调度来决定的。不一样操做系统自己的线程调度方式可能存在差别性,因此不能依靠线程优先级来处理并发逻辑。
Java API中,通常使用native方法System.currentTimeMillis() 来获取系统的时间。从方法名上,能够看出,该方法用于获取系统当前的时间,即从1970年1月1日8时到当前的毫秒值。
下面罗列出了官方对该方法的注释:
public final class System { /** * Note that while the unit of time of the return value is a millisecond, * the granularity of the value depends on the underlying * operating system and may be larger. For example, many * operating systems measure time in units of tens of * milliseconds. */ public static native long currentTimeMillis(); }
方法注释明确指出了这个毫秒值的精度在不一样的操做系统中是存在差别的,有的系统1毫秒实际上等同于物理时间的几十毫秒。也就是说,在一个性能测试中,由于精度不一致的问题,有的系统得出的结果是1毫秒,另外系统得出的性能结果倒是10毫秒。
那如何实现高精度的时间计算呢?先来看看System.nanoTime()方法,下面列出了官方的核心注释:
public final class System { /** * This method can only be used to measure elapsed time and is * not related to any other notion of system or wall-clock time. */ public static native long nanoTime(); }
这个方法只能用于检测系统通过的时间,也就是说其返回的时间不是从1970年1月1日8时开始的纳秒时间,是从系统启动开始时开始计算的时间。
因此通常高精度的时间是采用System.nanoTime()方法来实现的,其单位为纳秒(十亿分之一秒),虽然不保证彻底准确的纳秒级精度。但用该方法来实现毫秒级精度的计算,是绰绰有余的,以下。
long start = System.nanoTime(); // do something long end = System.nanoTime(); // 程序执行的时间,精确到毫秒 long costTime = (end - start) / 1000000L
Runtime是JVM中运行时环境的抽象,包含了运行时环境的一些信息,每一个Java应用程序都有一个Runtime实例,用于应用程序和其所在的运行时环境进行交互。应用程序自己没法建立Runtime实例,只能经过Runtime.getRuntime()方法来获取。
显然,运行时环境是因操做系统而异的。其交互方式也存在差别,
例如,
// Windows下调用程序 Process proc =Runtime.getRuntime().exec("exefile"); // Linux下调用程序 Process proc =Runtime.getRuntime().exec("./exefile");
因此,若是应用程序中包含这类和运行时环境进行交互的方法,应确保应用的部署环境不变,若是不能保证的话,那么至少须要提供两套运行时交互逻辑。
以上是我遇到的不能跨平台的一些case,其实本质上都和native实现有关。你有没有遇到一些这样的坑呢?欢迎留言~
参考连接:
JVM源码分析之不保证顺序的Class.getMethods
公众号简介:做者是蚂蚁金服的一线开发,分享本身的成长和思考之路。内容涉及数据、工程、算法。