java8 API (一) (String、正则、IO、集合)

前言

目的

  • java8自14年发布已经过去2年多了,越来越多的应用,开始升级到java8,升级不是目的,目的是升级之后,我们就可以使用java8新增的特性,从而提高开发效率、性能、稳定性等。所以伴随着升级,在团队内分享了java8的新特性,不过光了解新特性,知道lambda是啥,真正实践的时候还是有些迷茫(我自己现在就是这样),所以最近开始慢慢的整理java8新增的api,一方面字节在学习,另一方面通过例子的形式,希望可以帮助大家了解新增的api,以及api如何与lambda&stream等新特性应用在开发中。文章中不包含的内容:
    • java8新特性和语法讲很少,如果对java8还不太了解可以先自己查阅学习。
    • 文中例子都比较简单,代码以lambda和stream的代码为主,java8之前的代码例子基本没有,脑补一下,大家都能想出来。
  • 文中例子,都是自己敲过一遍,并且运行出的结果,如果大家发现问题,或者有更好的例子等都欢迎大家随时反馈给我@有光。

java8新增的API

  • 非官方数据
    • 195个新的文件加入到 JDK8 API
    • 93 新类, 89 新接口 , 13 新enum
    • 2699 新方法, 56 新构造函数
    • 46 个接口标记为函数式接口
    • 213 接口默认方法
    • 68 接口静态方法

基础

String

join()

  • 字符串拼接。
        String.join(":", "alibaba", "icbu", "youguang");
        String join = String.join(":", Lists.newArrayList("alibaba", "icbu", "youguang"));
        System.out.println(join);// => alibaba:icbu:youguang

chars()

  • 创建一个字符流。
  • 例子:统计去重后的字符数.
        long collect = "alibaba:icbu:youguang".chars()//
                                              .distinct()//
                                              .count();
        System.out.println(collect);// => 11

StringJoiner

  • 字符串拼接。
  • PS:String.join方法内部就是用StringJoiner实现的。
         StringJoiner sj = new StringJoiner(":");
        sj.add("alibaba");
        sj.add("icbu");
        sj.add("youguang");
        String result = sj.toString(); //alibaba:icbu:youguang

        sj = new StringJoiner(":","prefix-", "-suffix");
        sj.add("alibaba");
        sj.add("icbu");
        sj.add("youguang");
        System.out.println(sj.toString()); //prefix-alibaba:icbu:youguang-suffix

正则

Pattern.splitAsStream()

  • 正则分割后的结果,通过流来操作。
  • 例子:冒号分割,过滤掉youguang
        String bar = Pattern.compile(":")
                            .splitAsStream("alibaba:icbu:youguang")
                            .filter(s -> !s.contains("youguang"))
                            .collect(Collectors.joining(":"));
        System.out.println(bar);// => alibaba:icbu

Pattern.asPredicate()

  • 通过正则生成Predicate。
  • 例子:结合stream来过滤字符串。
      Pattern pattern = Pattern.compile(".*@gmail\\.com");
        Stream.of("tony.liw@gmail.com", "tony.liw@alibaba-inc.com")
              .filter(pattern.asPredicate())
              .count();// => 1

数学操作

输入&输出

  • java8提供了更简单的方式进行文件操作。

Files.list

  • Files类从1.7引入,1.8增加list方法生成stream来操作文件/目录。
  • PS:此处使用了1.7中的try-with-resource语法(Stream实现了AutoCloseable接口)
        try (Stream<Path> stream = Files.list(Paths.get("/opt"))) {
            String joined = stream.map(String::valueOf)//
                    .filter(path -> !path.startsWith("."))//
                     .sorted().collect(Collectors.joining("; "));
            System.out.println("List: " + joined);// => List: /opt/cisco; /opt/local

Files.find

  • 查找文件
  • 例子: 找到目录下的所有JavaScript文件(目录查找深度不超过5)。
        Path start = Paths.get("/home/admin/");
        int maxDepth = 5;//目录树层数
        try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) ->
                String.valueOf(path).endsWith(".js"))) {

            String joined = stream
                    .sorted()
                    .map(String::valueOf)
                    .collect(Collectors.joining("; "));
            System.out.println("Found: " + joined);
        }

Files.walk

  • 遍历文件,下面例子实现与上面的find同样的功能,不同的是过滤JavaScript文件通过stream的filter实现。
        Path start = Paths.get("/home/admin/");
        int maxDepth = 5;//目录树层数
        try (Stream<Path> stream = Files.walk(start, maxDepth)) {
            String joined = stream
                    .map(String::valueOf)
                    .filter(path -> path.endsWith(".js"))
                    .sorted()
                    .collect(Collectors.joining("; "));
            System.out.println("walk(): " + joined);
        }

Files.readAllLines

  • 读取文件的所有内容
List<String> lines = Files.readAllLines(Paths.get("res/nashorn.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);

PS:使用该方法的时候要考虑到文件的大小,该方法会将所有内容一次读入内存。

Files.lines

  • 将文件内容转成stream,元素是每行的内容。
       try (Stream<String> stream = Files.lines(Paths.get("res/nashorn.js"))) {
            stream
                    .filter(line -> line.contains("print"))
                    .map(String::trim)
                    .forEach(System.out::println);
        }

Files.newBufferedReader&Files.newBufferedWriter

  • 带buffer的读写操作
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
    System.out.println(reader.readLine());
}
Path path = Paths.get("res/output.js");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
    writer.write("print('Hello World');");
}

NULL(空指针)

  • 如何避免NullPointerException,为了避免NPE,写代码过程!=null检查会出现在各种地方,java8引入了更好的解决方案(Optional+Lambda)。其他语言解决NPE的语法糖

Optional基本

    //不要这样,这与!=null没什么区别
    if(stringOptional.isPresent()){
         System.out.println(stringOptional.get().length());
    }
    //下面是推荐的常用操作

    optionalValue.ifPresent(s -> System.out.println(s + " contains red"));
    //增加到集合汇总
    optionalValue.ifPresent(results::add);
    //增加到集合中,并返回操作结果
    Optional<Boolean> added = optionalValue.map(results::add);

    //无值的optional
    Optional<String> optionalString = Optional.empty();
    //不存在值,返回“No word”
    String result=optionalValue.orElse("No word");
    //没值,计算一个默认值
    result = optionalString.orElseGet(() -> System.getProperty("user.dir"));
    //无值,抛一个异常
    try {
        result = optionalString.orElseThrow(NoSuchElementException::new);
    } catch (Throwable t) {
    }   

Optional.map

  • 例子:看代码应该秒懂了,就是取foo值,但是为了取这个值,正常的逻辑里面需要增加一串!=null检查。java8中可以通过map函数避免。
//老的写法
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}
//新的写法
Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

//第二种lambda的方式
resolve(() -> obj.getNested().getInner().getFoo())
    .ifPresent(System.out::println);

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

集合

Collection

removeIf

  • 通过传入Predicate删除集合里面符合条件的元素。
        Collection<String> c = new HashSet<>();
        c.add("Content 1");
        c.add("Content 2");
        c.add("Content 3");
        c.removeIf(s -> s.contains("2"));
        System.out.println(c);// => [Content 3, Content 1]
  • PS:如果这个接口可以满足过滤的要求,就没必要使用stream了,这个效率更好。
  • 扩展:充分利用Predicate,直接看例子
//基本操作
 list =new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
 list.removeIf(a->{ return a%3==0;});

 //OR 操作
 Predicate<Integer> predicate2 = a->{ return a % 3 == 0;};
 Predicate<Integer> predicate3 = a->{ return a % 5 == 0;};
 list =new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
 list.removeIf(predicate2.or(predicate3));

 //AND 操作
 Predicate<Integer> predicate2 = a->{ return a % 3 == 0;};
 Predicate<Integer> predicate3 = a->{ return a % 5 == 0;};
 list =new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
 list.removeIf(predicate2.and(predicate3));

stream、parallelStream

  • 大家都知道了,就不说了,记住所有集合类都是通过Collection接口继承来即可。

Iterable

forEach

  • 遍历每个元素,继承了Iterable接口的都可以使用。
 List<String> stringList = Arrays.asList("a", "b");
        stringList.forEach(System.out::println);

iterator

forEachRemaining

  • 遍历iterator,并根据指定的action进行处理。开发中可能会出现,需要对集合中的第一个或者前几个元素进行特别的操作,然后继续遍历剩余的元素执行另一个操作(action),这个时候使用forEachRemaining非常合适。例如下面字符串拼接的例子。
        if (!iterator.hasNext()) {
            return;
        }
        StringBuilder builder = new StringBuilder();
        builder.append(iterator.next());
        iterator.forEachRemaining( element -> {
            builder.append(", ").append(element);
        });
  • PS:这里只是说明一下用法,字符串拼接,当然用String.join就够啦。

List

replaceAll

  • 传入UnaryOperator,将元素替换为一元操作之后的值
   List<String> stringList = Arrays.asList("a", "b", "c");
        stringList.replaceAll(String::toUpperCase);
        System.out.println(stringList);//[A, B, C]

sort

  • 通过指定Comparator进行排序,参数为空则根据自然排序。
        List<String> stringList = Arrays.asList("a", "b", "c");
        stringList.sort(String::compareTo);
  • PS: 可能会有人问这个和Collections.sort()的区别。下面是Collections的sort方法代码。内部调用的就是list.sort
  public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

Set

  • 接口没什么变化。

Map

foreach

       Map<String, Integer> map = new HashMap<>();
        map.put("A", 10);
        map.put("B", 20);
        map.put("C", 30);
        map.forEach((k, v) -> System.out.println("Item : " + k + " Count : " + v));

getOrDefault

  • 根据key取value,没有返回一个默认值(这个一直是很多人想要的方法)。
System.out.println(map.getOrDefault("D",40));// => 40
  • PS:如果还在使用Apache Commons Collections包中的DefaultedMap类,更换了。

putIfAbsent

  • javadoc中提供了与putIfAbsent的等价方法,
  • 特别说明put方法返回值:对应的key曾经有值返回老的value,否则返回null
    //The default implementation is equivalent to, for this map:
       V v = map.get(key);
      if (v == null)
          v = map.put(key, value);
    
      return v;
    
  • 例子:
        System.out.println(map.putIfAbsent("B",40));// => 20
        System.out.println(map.putIfAbsent("D",40));// => null
    

remove(Object.Object)

  • key和valu都相等的时候才删除,下面是javadoc中原来等价方法实现。
      if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
          map.remove(key);
          return true;
      } else
          return false;
  • 例子:
 System.out.println(map.remove("D",40));// => true

replace(K,V)

  • map中包含这个key,替换对应的value。javadoc中原来等价的方法实现
      if (map.containsKey(key)) {
          return map.put(key, value);
      } else
          return null;
  • 例子:Map<String, Integer> map = new HashMap<>(); map.put("A", 10); System.out.println(map.replace("A",20));//=> 10
  • PS:注意与putIfAbsent区别

replace(K,V,V)

  • map中包含这个key,并且value相等时,替换对应的value。javadoc中原来等价的方法实现
      if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
          map.put(key, newValue);
          return true;
      } else
          return false;
  • 例子:
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 10);
        map.put("B", 20);
        System.out.println(map.replace("B",10,100));//=>false
        System.out.println(map.replace("B",20,100));//=> true

compute(K,remappingFunction)

  • 通过remappingFunction函数,根据指定的key和对应的value,通过计算生成新的value。javadoc中原来等价的方法实现
      V oldValue = map.get(key);
      V newValue = remappingFunction.apply(key, oldValue);
      if (oldValue != null ) {
         if (newValue != null)
            map.put(key, newValue);
         else
            map.remove(key);
      } else {
         if (newValue != null)
            map.put(key, newValue);
         else
            return null;
      }
  • 例子:统计单词出现次数/统计图书借阅次数/遇到同样的key,创建或者append一个msg信息等。
        Map<String, Integer> countMap = Maps.newHashMap();
        List<String> bookList= Arrays.asList("Book-A", "Book-B", "Book-A");
        for (String book : bookList) {
            countMap.compute(book, (k, v) -> v == null ? 1 : v + 1);
        }
        System.out.println(countMap);
  • PS:要注意如果remappingFunction计算结果为空,会从map中remove

computeIfAbsent(K,mappingFunction)vs computeIfPresent(K,remappingFunction)

  • 这两个方法比较相近容易混淆,对比一下,下面是javadoc中两个新方法等同老代码。左侧:computeIfAbsent,右侧:computeIfPresent screenshot
  • 区别:
    • computeIfAbsent value根据key计算。computeIfPresent value基于key和oldValue计算(参数名也可以看出点意思mappingFunction/remappingFunction)
    • newValue为空,computeIfPresent会删除map中的元素。
  • 例子:这两个方法很容易想到的一个场景是本地缓存(考虑到多线程场景可以使用ConcurrentHashMap),下面以computeIfAbsent方法为例,同时与java8之前的版本代码做一下对比。
    
    static Map<String, Integer> CACHE = new ConcurrentHashMap<>();
    
    public Integer  getJAVA8(String key){
        //java8会使用thread-safe的方式从cache中存取记录
       return CACHE.computeIfAbsent(key, s -> {
           int rt=0;
           //xxx 计算 rt=
           return rt;
       });
    }
    
    public Integer  getJAVA7(String key){
    
        Integer rt = CACHE.get(key);
    
        if (rt == null) {
            //java8 中ConcurrentHashMap复写了computeIfAbsent方法做了线程安全控制
            synchronized (CACHE) {
                rt = CACHE.get(key);
                if (rt == null) {
                    //xxxx计算 rt=
                    CACHE.put(key, rt);
                }
            }
        }
        return rt;
    }
    

merge(K,V,remappingFunction)

  • 看名字已经理解80%了,直接看javadoc中与老版本等价的代码好了。
      V oldValue = map.get(key);
      V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
      if (newValue == null)
          map.remove(key);
      else
          map.put(key, newValue);
  • 是不是一眼看过去和computeIfPresent很像。此处diff一下就清晰了,computeIfPresent是在key对应的value存在时才有效。 screenshot

replaceAll(function)

  • 根据function计算结果,替换map所有entry的value

Comparator

  • Comparator在平时开发中用的比较多,java8针对该接口,增加了16个默认方法和静态方法,下面挑一些跟大家分享。

comparing(keyExtractor)

  • 先看例子,生成根据firstName对Person进行排序的Comparator:
        //老写法
        Comparator<Person> comparator=new Comparator<Person>() {
    
            @Override
            public int compare(final Person o1, final Person o2) {
                return o1.name.compareTo(o2.name);
            }
        };
        //lambda 写法
        comparator= (o1, o2) -> o1.name.compareTo(o2.name);
        //Comparator.comparing方法,提供提取比较key的function即可
        comparator= Comparator.comparing(Person::getName);
    
  • PS:该方法,要求提取的key实现了 Comparable接口。key没有Comparable接口可以使用重载实现comparing(keyExtractor,keyComparator)
  • 重载方法:
    • comparing(keyExtractor,keyComparator)
    • comparingInt(keyExtractor) 提取的key是Integer类型
    • comparingLong(keyExtractor)
    • comparingDouble(keyExtractor)

reversed()

  • 反转比较规则
        List<Integer> integerList = Arrays.asList(1, 3, 2, 5, 4);
        //正序
        integerList.sort(Integer::compareTo);
        System.out.println(integerList); // => [1, 2, 3, 4, 5]
        //倒序
        integerList.sort(Comparator.comparing(Integer::intValue).reversed()); // => [5, 4, 3, 2, 1]

thenComparing(other)

  • 链式比较,以后不用在使用第三方的ComparisonChain类了。
  • 例子:先按字符串长度,再按字符默认顺序排序(忽略大小写)。
 Comparator<String> cmp = Comparator.comparingInt(String::length)
                                              .thenComparing(String.CASE_INSENSITIVE_ORDER);
  • 重载方法
    • thenComparing(keyExtractor,keyComparator)
    • thenComparing(keyExtractor)
    • thenComparingInt(keyExtractor)
    • thenComparingLong(keyExtractor)
    • thenComparingDouble(keyExtractor)

reverseOrder

  • 静态方法,返回与自然排序相反的Comparator(比较元素要实现Comparator接口)
        List<Integer> integerList2 = Arrays.asList(1, 3, 2, 5, 4);
        integerList.sort(Comparator.reverseOrder());
        System.out.println(integerList);// => [5, 4, 3, 2, 1]

naturalOrder

  • 与reverseOrder相反,默认的自然排序器。等价//c1.compareTo(c2);

nullsFirst(comparator) & nullsLast(comparator)

  • 针对null值指定策略,排在前面(nullsFirst),还是后面(nullsLast)
        List<Integer> integerList3 = Lists.newArrayList(1, 3, null, 5, 4);
        integerList3.sort(Comparator.nullsFirst(Integer::compareTo));
        System.out.println(integerList3);// => [null, 1, 3, 4, 5]
        integerList3.sort(Comparator.nullsLast(Integer::compareTo));
        System.out.println(integerList3);// => [1, 3, 4, 5, null]
  • 这两个比较器一个很大的作用是可以处理结合中有null值的情况,一般默认的比较器遇到null都会抛出NPE,如果不想NPE可以使用nullsFirst/nullsLast包装一下。上面例子:如果不通过Comparator.nullsFirst(Integer::compareTo)生成新的比较器,直接使用Integer::compareTo就会抛NPE异常。

Arrays

  • 工具类中增加了一些对数组相关的流和并行处理的操作。

Stream

  • 将数组转成流进行操作,这样Stream Api就都可以用了stream api
//数组内容求和
int[] array = new int[]{1,2,3,4,5};
//求和
Arrays.stream(array).sum()
//过滤
int[] ms = Arrays.stream(ns).map(n -> n * 2).filter(n -> n % 4 == 0).toArray();

setAll

  • setAll方法提供一个 int -> T的函数接口做参数,int是数组的索引,T是数组的新值。
  • 例子:通过该接口对数组的索引进行操作,然后将指定数组当前索引位置的值赋值为操作后的值。
        int[] array = new int[10];
        Arrays.setAll(array, i -> i * 10);
        System.out.println(Arrays.toString(array));// => 输出 [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]        

spliterator

Collections

  • emptySortedSet、emptySortedMap、emptyNavigableSet、emptyNavigableMap、unmodifiableNavigableSet、unmodifiableNavigableMap、synchronizedNavigableSet、synchronizedNavigableMap等。

集合&数组转化

数组转集合

  • 没发现java8有什么新增的方式,如果有同学知道的求推荐。老的方式(Arrays/Guava包):
        List<Integer> integerList = Arrays.asList(1, 2, 3, 4);
        // guava
        integerList = Lists.newArrayList(1, 2, 3, 4);

集合转数组

  • 看例子
        //原始方式
        Integer[] integerArray = integerList.toArray(new Integer[integerList.size()]);
        // guava
        int[] intArray = Ints.toArray(integerList);
        // java8-基本类型
        intArray = integerList.stream().mapToInt(Integer::intValue).toArray();
         // java8-对象类型
        stringArray = stringList.stream().toArray(String[]::new);
Character[] characterArray = stringList.stream().map(s -> s.charAt(0)).toArray(Character[]::new);

未完待续

  • java8 API (二) stream整理中

推荐一个终端获取jmx信息的小工具——jmxterm

作者@JianhaoMo

最近由于某些原因跟同学推荐了下jmxterm,发现还是不少同学不知道有这么个工具,所以在这里推荐下。

官网:http://wiki.cyclopsgroup.org/jmxterm/

jmxterm是本地命令行看jmx的工具,jdk7以上自带的jcmd命令可以动态打开关闭本地jmx

举例看codecache:

jcmd <pid> ManagementAgent.start_local

echo “get -d java.lang -b name=Code\ Cache,type=MemoryPool Usage” |  java -jar jmxterm-1.0-alpha-4-uber.jar -l <pid> -v silent -n

jcmd <pid> ManagementAgent.stop

fastjson 1.2.10版本发布,修复Bug,支持Class Level SerializeFilter

作者:温绍锦(@温高铁)阿里巴巴资深技术专家

Bug Fixed

  1. 修复ValueFilter导致序列化数据丢失的问题,这个在1.2.9优化序列化引起。
  2. 修复某些场景下解析json中的注释出错。issue 559
  3. 修复WriteNonStringValueAsString特性打开时,非public类序列化会导致int类型输出为0的问题。 issue 572
  4. 修复1.2.8/1.2.9版本不支持JDK 1.5的问题

功能增强

  1. 新增Class Level SerializeFilter支持,在此之前只能在toJSONString时SerializeFilter,对所有的类型都起作用,这样会对框架的实现由性能影响,新特性允许SerializeFilter注册在类型上,具体文档看这里https://github.com/alibaba/fastjson/wiki/Class_Level_SerializeFilter

相关链接

获取一直FullGC下的java进程HeapDump的小技巧

作者:@善良的右席

小技巧

我们应用的java进程出问题的时候,我们往往会用jmap或者gcore拿到一份HeapDump,拿到MAT上做一次Heap分析,但是 如果你排查的是一直在FullGC的gc问题,你Dump下来的堆往往是正处于FullGC中,可能会导致分析失败。@坤谷 发现了一个小技巧,可以dump下完整而且没有在被移动中的Heap。

操作流程

  • 找到java进程,gdb attach上去, 例如 gdb java -p 22443
  • 找到这个HeapDumpBeforeFullGC的地址(这个flag如果为true,会在FullGC之前做HeapDump,默认是false)
    (gdb) p &HeapDumpBeforeFullGC
    $2 = (<data variable, no debug info> *) 0x7f7d50fc660f <HeapDumpBeforeFullGC>
    
  • 然后把他设置为true,这样下次FGC之前就会生成一份core文件
    (gdb) set *0x7f7d50fc660f = 1
    (gdb) quit
    
  • 最后,等一会,等下次FullGC触发,你就有HeapDump了!

(PS. jstat -gcutil pid 可以查看gc的概况)

 

注意事项,设完,观察heapdump生成路径和磁盘,别生成太多,让磁盘满了,heapdump出来后,及时kill掉应用。

 

Q&A
Q1:把HeapDumpAfterFullGC也设上是不是会更快点儿?
A1:那就是被fullgc完之后的堆了,会不会堆里有些业务垃圾会有助于分析嘛。。

Q2: HeapDumpBeforeFullGC 可以用JCMD改的吧, 我看是manageable 属性
A2: 是的,如果jcmd,jinfo能改,就先用工具。这里是所有别的工具都失败的情况下的办法。一直fullgc的时候jinfo会不生效吧

Q3: jmx,mxbean能直接动态改的,比如去Jconsole里setVmOption,直接将HeapDumpBeforeFullGC和HeapDumpAfterFullGC设置成true,也比较方便的A3: 嗯嗯是的,但是这类方法缺点就是如果应用一直无限fullgc,会导致修改不成功

Q4: 一直fgc,用jmap -histo,我觉得就基本上能看出蛛丝马迹了
A4: 不错。其实很多时候还不需要heapdump,taobao-jdk6/ali-jdk8/ajdk8的大数组警告,直接能看出问题。

Q5: jmap -F 不行吗
A5: 那个时候dump下来的堆,可能正处于gc中,会解析失败

Q6: 文章中不是说等下次full gc吗?既然等到下一次full gc,我觉得jmap -F能dump出文件吧?或者直接设置jvm -XX:+UseHeapDumpBeforeFullGC应该也可以吧?不知道说的对不对
A6: jmap -F的原理和gcore差不多,强行dump出内存,这个时候如果正在gc中,那么内存中对象的引用关系可能是乱的。XX:+UseHeapDumpBeforeFullGC这个flag,线上应用肯定是不会开的。上面的方法就是强制打开这个flag

[翻译]JDK 8 兼容性指南

翻译官方文档,译者精力有限,忽略部分划删除线。

译者:坤谷(@JianhaoMo),井桐(@张同宝),激酶

 

兼容性是一个复杂的问题。 本文介绍了Java平台潜在的三种不兼容问题:

  • 源码: 源码兼容性问题关注Java源代码转换成class文件是否兼容,包括代码是否仍然可编译。
  • 二进制: 在Java语言规范中,二进制兼容性定义为:“类的改变是二进制兼容的(或者不破坏二进制兼容性),是指如果改变前的类的二进制在链接时没有错误,那么改变后的类在链接时仍然没有错误。”
  • 行为 : 行为兼容性包括在运行时执行的代码的语义。 欲了解更多信息,请参阅OpenJDK的开发人员指南 中,兼容性的种类一节。 下面的兼容性文档跟踪相邻的Java版本之间的不兼容。 例如,这个兼容性文档只报告的Java SE 8和Java SE 7的不兼容,而不是以前的版本。 要检查的Java SE 8与早期的Java版本的不兼容性,必须按顺序跟踪一下不兼容性温的。 Java SE 7和JDK 7的兼容性 JDK 6兼容性 J2SE 5.0不兼容问题(自1.4.2)

二进制兼容性

除了下面列出的不兼容问题,Java SE 8与Java SE 7是二进制兼容的。 除了提到的不兼容,Java SE 7编译的类文件将在Java SE8正常运行。而Java SE 8编译的类文件将无法在早期版本的Java SE运行。

源码兼容性

Java SE 8包括新的语言功能和平台的API。这些都在源文件中使用,这些源文件不能在早期版本的Java平台中编译。
一般情况下,源码兼容性策略是避免引入的源代码不兼容问题。 然而,实现Java SE 8的功能所需的更改可能导致代码无法在Java SE的7编译。见Java SE 8和Java SE 7之间的不兼容性和JDK 8和JDK 7之间的不兼容性。
Deprecated API仅用于与早期版本的兼容性支持。除非打开-nowarn命令行选项,只要使用了Deprecated API,javac编译器就会生成警告信息。 建议应用进行修改,以杜绝使用Deprecated API。
sun.*包的 一些API在发生了变化。 这些API不供开发人员使用。 开发人员导入sun.*包,需要自己承担风险。 欲了解更多详细信息,请参阅为什么开发人员不应该编写调用‘sun’包的程序
对于Deprecated API列表,请参阅Deprecated API。

行为兼容性

简而言之,行为兼容性意味着在输入相同时,程序在不同版本的库或平台执行相同(或等同)的操作。 有些平台行为的底层实现是故意设定为未定义的,这样平台发布可以更改。 出于这个原因,建议写代码不依赖于未定义的行为:如果依赖了未定义行为,问题不能算平台不兼容,而是算代码的bug。

Java类文件

Java类文件格式已经更新为Java SE 8版本。
按照JVM规范,Java SE 8的类文件版本是52.0。 由Java SE 8编译器产生的版本为52.0的类文件不能在早期版本Java SE中使用。
下面的文档有Java语言规范(JLS)和Java虚拟机规范(JVMS)的更改信息。

Java SE 8和Java SE 7之间的不兼容性

尽管Java SE 8是与Java平台的早期版本是非常兼容的, 几乎所有现有的程序可以不加修改地运行在Java SE 8,不过JRE和JDK也有一些小的潜在的不兼容性。为了完整性,这里记录极少数情况下“极端案例”。
本节介绍了Java语言的JVM或Java SE API的Java SE 8不兼容。
请注意,此版本中一些API已弃用,有些功能已被完全删除。 虽然这些都是不兼容,他们被列为在单独的列表。 欲了解更多信息,请参阅Deprecated APIs和JDK 8移除的功能。
2015年3月Java SE 8进行了版本维护,不兼容的问题有相应的修改。

  • JVM:接口默认方法不能触发接口立即初始化

    不兼容性: 行为
    Bug:8043188
    (译者注:8u40已修复)
  • JVM:JDWP支持接口默认方法和静态方法

    不兼容性: 行为
    RFE:8042123
    (译者注:8u40已修复)
  • JVM:对invokespecial指令调用实例初始化方法的校验更加严格
    (译者注:只有当前类型或直接超类的实例允许调用)
    不兼容性: 行为
    REF: 7160765 (译者注:可能是安全相关,无法看到更详细内容)
  • JVM:默认每个类文件中都设置了ACC_SUPER标志
    Java SE 8以及以上,Java虚拟机默认每个类文件中都设置了ACC_SUPER标志,无论标志在类文件的实际值和类文件的版本的。 该ACC_SUPER标志影响invokespecial的行为。
    不兼容性: 行为(译者注:可能是安全相关,无法看到更详细内容)
  • JAVA:classes_text支持小语种
    当使用DateFormat和SimpleDateFormat来格式化日期时间值时 ,上下文有关的月份名称支持既有格式化形式又有独立形式的小语种。 例如,在捷克语中,一月的格式化形式是ledna,而独立形式是leden。 DateFormatSymbols的getMonthNames方法和getShortMonthNames方法返回这些语言的月份格式化形式。请注意,直到的Java SE 7,DateFormatSymbols都是返回独立的形式。您可以用Calendar.getDisplayName方法和Calendar.getDisplayNames方法来制定返回哪种形式。 请参考API文档。
    不兼容性: 行为
    RFE:7079560
  • 核心库:javax.lang.model引入IntersectionType
    在Java SE 8,对于具有multiple bounds的TypeVariables ,javax.lang.model.type.TypeVariable.getUpperBound的返回值和早期版本的返回值不同。现在返回新引入IntersectionType的实例,而原来返回DeclaredType的实例。 这可能会导致javax.lang.model.util.TypeVisitor现有的实现行为改变:之前TypeVariable.getUpperBound返回multiple bounds变量时调用visitDeclared方法,现在调用visitIntersection方法。 对返回值调用getKind()也能观察到差别。
    不兼容性: 行为
    RFE: 6557966
  • 核心库:non-public java.lang.reflect.Proxy
    实现了non-public interface的java.lang.reflect.Proxy将是non-public, final, 且非abstract的。 在Java SE 8之前,代理类是public, final, 且非abstract。

    • 如果现有的代码不在同一个运行时package里面调用Proxy.getProxyClass和Constructor.newInstance方法来创建一个代理实例,它会失败,并抛IllegalAccessException。 对于这样的代码,它需要更改源码为:(1)调用Constructor.setAccessible设置访问标志设置为true,或(2)使用方便的Proxy.newProxyInstance方法。
    • 如果现有代码试图创建其他运行时package的non-public接口代理,需要授权新的permissionReflectPermission("newProxyInPackage.{package name}")不兼容性: 源码
  • 核心库:java.lang.reflect.Proxy不允许空InvocationHandler
    如果给定的InvocationHandler参数为空,java.lang.reflect.Proxy(InvocationHandler h)构造函数现在抛NullPointerException。
    现有代码使用空参数构造动态代理实例将抛NullPointerException。 这种用法估计很罕见,因为空代理实例无论用在何处都将抛NullPointerException。
    不兼容性: 行为
    RFE: 4487672
  • 核心库:java.math的BigDecimal.stripTrailingZeros的零值问题
    Java SE 8之前,如果调用BigDecimal.stripTrailingZeros的数值等于零,将返回该值。 现在则返回常量BigDecimal.ZERO
    不兼容性质: 行为
    RFE: 6480539
  • 核心库:java.net的HttpURLConnection响应头引号问题
    在以前的版本中HttpURLConnection摘要身份验证实现有误,在WWW-Authenticate响应头中对一些值加了引号。 在Java SE 8版本中,这些值不再加引号。 这是严格遵循RFC 2617 HTTP Authentication: Basic and Digest Access Authentication
    一些服务器实现的某些版本已知预期这些值被引用。 HTTP请求到这些服务器可能无法再成功进行身份验证。 而以前由于这些值被加引号而失败的身份验证,可能现在能成功验证。
    不兼容性: 行为
    RFE: 8010505
  • 核心库:java.net的socket非临时端口安全问题
    在此版本中,分配给包括不​​可信代码的所有代码默认socket权限已被更改。 以前,所有代码能够bind任何类型的socket,以及任何大于或等于1024的端口号。此版本仍可以bind socket到每个系统上的临时端口范围。 临时端口的确切范围因操作系统而不同,但通常在高范围(如从49152到65535)。新的限制是bind socket到临时端口范围之外需要显式系统安全策略授权。绝大多数使用客户端TCP socket(开启了SecurityManager)的应用将不会看到任何问题,因为这些通常bind到临时端口。 而使用datagram socket或服务端tcp socket(开启了SecurityManager)应用可能抛安全异常。之前版本不会。 如果发生这种情况,使用者应该检查被请求的端口号是否符合预期。如果符合预期,添加一个socket授权到本地安全策略来解决此问题。
    不兼容性: 行为
  • 核心库:java.net的DatagramPacket构造函数不再声明异常
    在Java SE 8之前,带java.net.SocketAddress参数的java.net.DatagramPacket构造函数,声明抛出java.net.SocketException。 然而,这个异常永远不会被抛出。 在Java SE 8版本中,该构造函数不再声明抛出java.net.SocketException。 如果的代码catch了SocketException或它的超类java.io.IOException ,在使用与Java SE 8编译之前删除这些catch块。
    不兼容性: 源码
    RFE: 8022126
  • 核心库:java.util.i18n的LocaleServiceProvider判断Locale问题
    选择LocaleServiceProvider的机制已经改变。LocaleServiceProvider的实现现在能够通过overideLocaleServiceProvider.isSupportedLocale方法来确定Locale是否支持。 然而,如果从JDK7迁移locale service providers, overide此方法对于严格的扩展检查可能涉及与现有应用程序的一些兼容性问题。参考LocaleServiceProvider类及其isSupportedLocale方法的描述了解更多细节。
    不兼容性: 行为
    RFE: 7168528
  • 客户端库:java.awt

    不兼容性: 行为
    RFE: 7146237
  • 安全库:javax.net.ssl拒绝客户端初始化的renegotiation
    Oracle JSSE provider的新sytem propertyjdk.tls.rejectClientInitializedRenego拒绝客户端初始化的renegotiation 。如果这个sytem property为true。服务端拒绝客户端renegotiation的请求,并且抛出handshake_failure警报。
    不兼容性: 行为
    RFE: 7188658
  • Hotspot JVM:gc删除Perm
    删除并忽略命令行选项 PermSize和MaxPermSize。 如果使用了这两个选项,会发出如下警告:

    Java HotSpot(TM) Server VM warning: ignoring option PermSize=32m; support was removed in 8.0
    Java HotSpot(TM) Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
    

    不兼容性: 源码
    RFE: 6965548

JDK 8与JDK 7之间的不兼容性

本段描述JDK 8在javac、HotSpot或Java SE API中与JDK 7不兼容之处。
需要注意的是,部分API在本次发布中已经标注为不建议使用,甚至部分特性已经被完全移除。这些不兼容的地方在这里单独列出来。如果需要更多的信息,参看Deprecated APIs和JDK 8移除的功能。

  • JVM:JDI中接口支持默认方法和静态方法
    Java SE 8语言规范为接口引入了静态方法和默认方法,因此JDI规范和具体实现中都已经允许使用这个技术。在JDI中,com.sun.jdi.InterfaceType类包含了方法Value invokeMethod(ThreadReference thread, Method method, List<? extends Value> arguments, int options)
    不兼容性: 行为
    RFE: 8042123
    (译者注:8u40已修复)
  • 核心库:java.lang的windows中检测登录用户home目录
    在windows中检测登录用户home目录的步骤,更新为遵照微软推荐的方法。这处改动对在如下两类情况下可能需要重点关注:1.比较旧的windows版本;2.用户home目录在注册表或环境变量中设置到其他的目录下。
    不兼容性: 行为
    RFE: 6519127
  • 核心库:java.lang.reflect的getMethods调用结果
    新特性默认方法会影响两个方法Class.getMethod、Class.getMethods的调用结果。
    Class.getMethod、Class.getMethods的javadoc继承了Java语言规范的定义。Java SE 8改变了这个规则,以便支持默认方法的同时削减继承于超接口的冗余方法(可参考JLS 8, 8.4.8)的数量。比如说,一个类有两个超接口:I和J,每一个都定义了int length();,一般来说,我们认为两个方法都是这个类的成员,但是如果J同样扩展于I,那么在Java SE 8中,这个类仅集成了一个方法:J.length()
    在Java SE 8发布的时候,Class.getMethod和Class.getMethods的实现并没有随定义的更新而更新(两个方法都会返回非继承的超接口的方法)。从兼容性看,这个区别没那么重要了,而与Java SE 7返回的一致性则是优先考虑的。因此无论如何,当重写方法(如上面提到的J.length)为默认方法,首先应该要过滤掉其他重写的方法(如上面提到的I.length)。
    从JDK 8u20开始,代码实现改为在重写默认方法时,增加上面提到的过滤的步骤。
    不兼容性: 行为
    RFE: 8029674
  • 核心库:java.text舍入行为问题
    在之前版本中,当使用NumberFormatDecimalFormat类时,在特殊情况下是会发生舍入行为的错误。这种错误行为一般发生在调用format()方法时,给的值非常接近于格式化字符串所定义的中间位置。在这种情况下,错误的双精度值舍入或不舍入行为就会发生。
    举例说,当使用默认推荐的NumberFormatFormatAPI:NumberFormat nf = java.text.NumberFormat.getInstance(),跟着格式化代码nf.format(0.8055d),0.8055d值在计算机无法精确地表示为一个二进制的值,而是0.80549999999999999378275106209912337362766265869140625。在这里默认的舍入是“half-even”法(译者注:此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况,如果前一位为奇数,则入位,否则舍去),JDK 7中调用format()方法会错误的输出0.806,然而正确的值应该是0.805,因为内存中记录的值是小于中间位置的。
    在所有可能由程序员自定义样式(而不是默认的样式)的位置里,这个舍入行为被修正。
    不兼容性: 行为
    RFE: 7131459
  • 核心库:java.util.collections的removeAll,retainAll不接受null
    在之前的版本中,一些Collection.removeAll(Collection)retainAll(Collection)的实现会静默的忽略传入null参数的情况。从这个版本开始,如果集合传入一个null参数,将会抛出一个NullPointerException
    不兼容性: 行为
    RFE: 8021591
  • 核心服务:java.management强制管理接口public
    在规范中提到的管理接口需要是puclic的,在这个版本中变成强制的了。非public的接口不存续暴露管理功能,所有的MBean和MXBean接口必须是public的。
    系统属性jdk.jmx.mbeans.allowNonPublic用于恢复成之前允许非public管理接口的行为。这个属性是过渡期使用的,在后续的发布版中将会被移除。
    不兼容性: 行为
  • 客户端库:java.awt
    java.awt.Component.setFocusTraversalKeys()方法中,如果参数keystrokes里的任何对象不是AWTKeyStroke的话,会抛出ClassCastException(之前是IllegalArgumentException)。
    不兼容性: 行为
  • 安全库:java.security限制com.sun.media.sound
    com.sun.media.sound添加到了JDK 8受限制包列表中。在SecurityManager下运行的应用无法访问这个包及其下各层级,除非得到明确的授权。com.sun.media.sound包一个内部的、不受支持的包,这相当于是说这不应该被外部的应用所使用。
    不兼容性: 源码
    RFE: 8019830
  • 其他库:corba限制se及其子包
    JDK内部包com.sun.corba.se及其子包已被添加到受限包列表中,运行SecurityManager情况下无法直接访问。
    不兼容性: 行为
    RFE: 8021257
  • 工具:javac改正二进制比较的类型规则
    Java语言规范(JLS)的15.21章节中关于二进制比较的类型规则没有正确的被javac实施。从JDK 5 版本开始,javac根据JLS规范15.21章节接受了一些类型不正确的对象-原生类型比较的程序。这些比较现在会被认为是类型错误。
    不兼容性: 行为
    RFE: 8013357
  • 工具:javac参数和方法的注解拷贝到合成桥接方法
    在这一次发布中,参数和方法的注解将会拷贝到合成桥接方法。这个修复意味着现在程序类似:

    @Target(value = {ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @interface ParamAnnotation {}
    
    @Target(value = {ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface MethodAnnotation {}
    
    abstract class T<A,B> {
        B m(A a){return null;}
    }
    
    class CovariantReturnType extends T<Integer, Integer> {
        @MethodAnnotation
        Integer m(@ParamAnnotation Integer i) {
            return i;
        }
    
        public class VisibilityChange extends CovariantReturnType {}
    }
    

    每一个生成的桥接方法拥有所有的注解,参数注解也将会复制。这种行为上的改变将会影响一些注解处理器或任何使用注解的普通的程序。
    不兼容性: 行为
    RFE: 6695379

  • 工具:javac参数注解会拷贝到自动生成的内部类构造器
    参数注解会拷贝到自动生成的内部类构造器中。这个修复意味着现在如下的程序:

    @Target(value = {ElementType.PARAMETER})
    @interface ParamAnnotation {}
    
    public class initParams {
      public initParams(@ParamAnnotation int i) {}
    
      public void m() {
         new initParams(2) {};
      }
    }
    

    方法m()里,内部类生成的构造器中,参数int i会有一个注解@ParamAnnotation。这个行为的变化可能影响一些注解处理器,或者使用了注解的应用程序。
    不兼容性:
    行为

  • 工具:javac不再识别编译目标值1.4.1、1.4.2和jsr14
    javac不再识别编译目标值1.4.11.4.2jsr14,这些之前就没有记录在文档中。很多最新的代码生成惯用语中1.4.11.4.21.4使用的更多,而组合选项-source 1.4 -target 1.5会在更新一些的惯用语中使用,同时也会输出更新一些的字节码文件格式。“jsr14”选项则是泛型被添加到平台后的一个过度选项,现在泛型编译目标应该是1.5或更高。
    不兼容性: 行为
    RFE: 8010179
  • 工具:javac类型转换编译问题
    下面的代码在JDK 7中编译会出现警告,而在JDK 8中将无法编译:

    import java.util.List;
    
    class SampleClass {
    
        static class Baz<T> {
            public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
                return null;
            }
        }
    
        private static void bar(Baz arg) {
            Baz element = Baz.sampleMethod(arg).get(0);
        }
    }
    

    JDK 8编译上面的代码中会报如下错误:

    SampleClass.java:12: error:incompatible types: Object cannot be converted to Baz
        Baz element = Baz.sampleMethod(arg).get(0);
    
        Note: SampleClass.java uses unchecked or unsafe operations.
    Note: Recompile with -Xlint:unchecked for details.
    1 error
    

    在这个例子中,原始类型被传递到sampleMethod(Baz<Object>)方法中,而这个方法应该传递的是其子类型(参考JLS,Java SE 7 Edition, 15.12.2.2章节)。如果要可用的话,就需要一个未经检查的转换,而他的返回类型是被擦除的(参考JLS,Java SE 7 Edition, 15.12.2.6章节)。在这个例子中,sampleMethod(Baz<Object>)的返回类型是java.util.List,而不是java.util.List<Baz<Object>>,因此get(int)的返回值是Object,这就和被赋值的Baz类型不一致了。
    更多的信息,请参见java.net上相关的电子邮件交流。
    不兼容性: 源码
    RFE: 7144506

  • 工具:javac的final字段明确赋值问题
    使用this关键字为final字段明确的赋值分析。
    传统的,Java语言禁止通过简单的名字(比如“x”)来访问没有明确赋值的final字段。在Java SE 7里,字段是可以通过“this.x”进行访问的(参见: https://bugs.openjdk.java.net/browse/JDK-7004835)。任何合法的程序在旧的规则下是非法的,而在新的规则下是不安全的,因为他必然会访问一个没有明确赋值的空白的final字段。
    从JDK 8u20开始,javac编译器已经更新实现了Java SE 7的规则。
    不兼容性: 源码
    Bug: 8039026
    (译者注8u20已修复)
  • 工具:javac接口的实现进行编译时接口需要存在
    当编译一个类,这个类中使用到的其中一个类实现了一个接口,而这个接口定义在别的文件中,这种类文件(定义接口的文件)在javac编译时必须可用。这是JDK 8的一个新的需求,不这样做会导致一个编译错误。例如:
    Client.java:

    import p1.A;
    
    class Client {
       void test() {
           new A.m();
       }
    }
    

    p1/A.java:

    package p1;
    
    public class A implements I {
       public void m() { }
    }
    

    p1/I.java:

    package p1;
    
    public interface I {
       void m();
    }
    

    如果p1/I.java或p1/I.class在编译Client.java时不可用,就会出现下面的错误信息:

    Client.java: error: cannot access I
           new A().m();
                ^
      class file for p1.I not found
    

    不兼容性: 行为

  • XML:JAXP的Xalan扩展功能安全问题
    JAXP中的Xalan扩展功能已经修改,以便于当SecurityManager存在时,默认的实现总是会被使用。这一改变会影响DOM Document创建的NodeSet。在之前,DOM实现由DOM工厂查找过程来定位。在这之后,当运行SecurityManager时,查找过程会跳过,并且用默认的DOM实现。
    这个改变仅影响使用了第三方DOM实现的应用。一般来说,NodeSet结构预计与JDK默认实现相兼容。
    不兼容性: 行为
  • XML:JAXP1.6规范更新
    JDK 8附带了JAXP 1.6,包括规范更新,要求使用java.util.ServiceLoader查找服务提供者。跨JAXP的服务提供者能够通过定义在java.util.ServiceLoader的流程被一致的放置。而JDK7中提供者配置文件可以是位于不同地方的,例如,通过不同的类加载器的getXXX方法比起ServiceLoader,这个改变导致与JDK 7的细微的差异。
    JDK 8 ships with JAXP 1.6 and so includes specification updates that mandate the use of java.util.ServiceLoader for finding service providers. Service providers across JAXP will now be located consistently following the process as defined in java.util.ServiceLoader. The changes may result in some subtle differences from implementations of JDK 7 where the provider-configuration file may have been located differently, for example, by using a different getXXX method of the ClassLoader than ServiceLoader.
    应用实现自己的类加载器,应该确认类加载器的getXXX方式的实现是一致的,以便保持兼容性。
    JSR 173中StAX API定义了接收factoryId参数的newInstancenewFactory方法。因为在StAX规范中对这个参数没有任何限制,意味着可以是任意字符串。在JDK 8规范中进行了一下变更,在JAXP上下文里,如果他想要表示服务配置文件的名称,factoryId的值必须是基本服务类的名字,即,他不是一个系统属性名称。
    不兼容性: 行为
    RFE: 8005954
  • XML:JAXP的javax.xml.stream工厂的类加载器不再忽略
    javax.xml.stream工厂中,类加载器参数不再能忽略。
    javax.xml.stream包包含工厂类(XMLEventFactory、XMLOutputFactory、XMLInputFactory),这些工厂类定义newFactory方法时需要两个参数:factoryId和ClassLoader。在JDK 7中,第二个参数在查找和实例化服务时可以被省略。在JDK 8中不再能省略。可以参考这些方法的Java API文档来获取更详细信息。
    不兼容性: 行为
    RFE:
    8005954

Java SE 8中移除的功能

  • API:java.lang的Thread.stop关闭
    Thread.stop方法从JDK 1.2版本就被弃用了。这个方法现在会抛出一个UnsupportedOperationException。
  • 核心库:java.net从需要的协议处理程序的列表中移除ftp协议
    ftp是一个历史遗留的协议,它在很长时间内被一些更加安全的文件传输协议(比如sftp)取代。ftp协议已经从协议处理程序列表中移除并且保证透明的。它实际上不是移除协议处理程序-应用使用这个协议时能够继续工作,但是它的存在已经不再需要。
    RFE: 8000941
  • 安全库:java.security不支持不安全的1024以下RSR密钥
    在衡量基于加密算法的公钥强度上,秘钥的长度是一个很重要的安全参数。小于1024 bits的RSA秘钥被认为是可以破解的。
    在这次更新中,如果他们的RSA秘钥长度小于1024 bits的话认证会被阻止。这个限制是通过Java Security property,jdk.certpath.disabledAlgorithms实施。这种做法会影响附着security property的provider(比如Sun provider和SunJSSE provider)。security property和jdk.certpath.disabledAlgorithms也同样覆盖TLS使用的静态秘钥(X.509证书秘钥)的使用。
    有了这些秘钥长度的限制,这些使用基于长度小于1024 bits的RSA秘钥的X.509证书在证书路径的建立和验证过程中会遇到兼容性的问题。这些秘钥的长度限制同样会影响需要验证X.509证书的JDK组件,比如JAR签名验证,SSL/TLS传输和HTTPS连接。
    为了避免这些兼容性问题,使用了基于长度小于1024 bits的RSA秘钥的X.509证书的用户会被推荐使用更强的秘钥并更新他们的证书。作为一种变通的方法,在自己能够承受的风险之上,为了允许较小长度的秘钥,用户可以调节秘钥的长度来限制安全性(jdk.certpath.disabledAlgorithms)。
    不兼容性: 行为

JDK 8中移除的功能

  • Install:安装时不再提供关闭自动更新选项
    为了关闭JRE的自动更新,关闭自动更新并且设置部署配置中的系统属性文件的deployment.expiration.check.enabled属性为false。为了关闭自动更新,移除Java控制面板中的更新按钮中的检测自动检测功能。查看Deployment Configuration File and Properties获取关于deployment.expiration.check.enabled property的更多信息。
  • Install:Solaris的Class Data Sharing文件不会被创建
    在这之前,Solaris SVID包在安装时会创建Class Data Sharing文件。在JDK 8中,32位的Solaris不再被支持,因此Class Data Sharing文件也默认不会被创建。如果想要手动创建Class Data Sharing文件。执行下列命令:
    $JAVA_HOME/bin/java -Xshare:dump
    当命令执行后,Class Data Sharing文件位于:
    $JAVA_HOME/jre/lib/server/{amd64,sparcv9}/classes.jsa
    RFE: 8023498
  • Deployment:Plugin移除经典的Java Plug-in
    旧的Java Plug-in(Java SE 6u10之前的版本)会在这个版本中被移除。
    RFE: 7076143
  • Deployment:Plugin移除Java 快速开始(Java Quick Starter)
    Java快速开始(JQS)服务在这个版本中会被移除
    RFE: 8004321
  • Deployment:Plugin移除Active-X Bridge
    Active-X Bridge在这个版本会被移除。
    RFE: 8004321
  • 核心库:sun.jdbc.odbc移除JDBC-ODBC Bridge
    从JDK 8版本开始,JDK将不再包含JDBC-ODBC Bridge。JDBC-ODBC Bridge已经被认为是一个过渡期的产品,并且是一个仅仅用于选择JDK软件包并不包含在JRE中的JRE不支持的产品。可以使用数据库供应商提供或者是一个商业版本的JDBC Driver替换JDBC-ODBC Bridge。
    RFE: 7176225
  • Tools:apt移除apt工具
    apt工具以及com.sun.mirror包中和它相关的API都将在这个版本中移除。使用命令行工具javac以及包javax.annotation.processing,包javax.lang.model处理注解。
    RFE:
    7041249
  • Tools:java移除32位的Solaris
    32位的Solaris操作系统的Java实现会在这个版本中移除。$JAVA_HOME/bin$JAVA_HOME/jre/bin文件夹现在只包含64位的版本。为了过渡的目的,ISA(Instruction Specific Architecture)目录中$JAVA_HOME/bin/{sparcv9,amd64}$JAVA_HOME/jre/{sparcv9,amd64}有指向32位版本的符号链接。这些ISA目录将在JDK 9版本中被移除。
    SUNWj8rt,SUNWj8dev和SUNWj8dmo,这些之前包含32位版本的安装包现在包含64位版本。SUNWj8rtx,SUNWj8dvx和SUNWj8dmx安装包将被移除。
    64位版本的java不会包含诸如Java Web Start和Java Plug-in的部署工具,因此桌面集成也不再需要了。
    注意到64位的Solaris的可执行文件不能加载位32位Solaris编译和链接的JNI库。因此任何在32位Solaris系统中创建的JNI库都需要重新编译64位Solaris版本。
    RFE: 8023288

Deprecated API

  • 核心库:java.lang:class_loading弃用endorsed
    endorsed-standards override mechanism允许在Java Community Process维护的标准之外,或者是持续独立发展的作为Java SE平台的独立的APIs上实现新版本并且在运行时刻安装。
    一个模块化的映像是由模块而不是jar包文件组成。往前看,我们希望通过可升级的模块(upgradeable module)只支持模块化形式的认可的标准和独立的APIs。
    这个特性会在JDK 8u40版本中被弃用。这个弃用是为将来的Java SE版本中出现的module特性而做准备的。如果想要了解更多信息,查看JEP 200 – The Modular JDKJEP 220 – Modular Run-Time Images
    这些改变不会改变运行时刻的行为。
    RFE: 8065675
  • 核心库:java.lang:class_loading弃用extension
    扩展机制(extension mechanism)允许包含扩展了Java SE平台APIs的jar文件安装到一个运行时映像,使它们的内容在可见的情况下编译或运行的映像上的每一个应用程序。
    这样的扩展机制在发布于1998年的JDK 1.2版本引入,但是在现在我们看不到任何凭证显示有人去使用这个特性。这并不奇怪,因为多数现代的Java应用程序会直接在class path上直接安装类库而不是在运行时刻去安装类库。
    这个特性会在JDK 8u40版本时弃用。这个弃用是为将来的Java SE版本中出现的module特性而做准备的。如果想要了解更多信息,查看JEP 200 – The Modular JDKJEP 220 – Modular Run-Time Images
    这个特性的弃用需要下列标准规范的改变:

    • java.lang.System.getProperties()方法中,java.ext.dirs系统属性的规范会被修订为包含一个会在将来版本中移除的弃用声明。
    • java.util.jar.Attributes.Name类中,域EXTENSION_INSTALLATIONIMPLEMENTATION_URLIMPLEMENTATION_VENDOR_ID的被弃用表明应该使用class path代替它们。
    • 在JAR文件的规范中,所有和打包JAR包的小程序依赖的已安装的可选包和触发对可选包下载的相关的manifest属性都被弃用。这些属性包括:xtension-Name, Extension-List, <extension>-Extension-Name, <extension>-Specification-Version, <extension>-Implementation-Vendor-ID, <extension>-Implementation-Version, <extension>-Implementation-URL, Implementation-Vendor-Id, Implementation-URL,Extension-Installation。 这些改变不会改变运行时刻的行为。 RFE: 8065702
  • 核心库:java.lang弃用SecurityManager.checkMemberAccess
    SecurityManager.checkMemberAccess被弃用。它在调用者的栈帧深度为4的时候容易出错。JDK的实现将不再调用SecurityManager.checkMemberAccess方法去执行成员变量访问控制的检测;替代它的是调用SecurityManager.checkPermission方法。
    通过重写checkMemberAccess方法定制的SecurityManager的实现可能会被影响导致重写的方法不会被调用。
  • 核心库:java.lang弃用SecurityManager类的checkTopLevelWindow, checkSystemClipboardAccess, checkAwtEventQueueAccess
    可替代的方法: checkPermission
    RFE: 8008981
  • 核心库:java.rmi弃用RMI/JRMP的HTTP代理
    RMI/JRMP的HTTP代理功能被弃用,而且HTTP的代理功能默认被关闭
    RFE: 8023862
  • 核心库:java.rmi弃用RMI(JRMP)静态生成stubs
    不再支持RMI(JRMP)静态生成stubs
    RFE: 8023863
  • 核心库:java.util.jar弃用Pack200.Unpacker接口的addPropertyChangeListener和removePropertyChangeListener
    这些方法希望在将来的Java SE版本中移除
    可替代的方法: 跟踪unpacker执行过程,轮询PROGRESS属性的值。
    RFE: 8000362
  • 核心库:java.util.logging弃用LogManager接口的addPropertyChangeListener和removePropertyChangeListener
    这些方法希望在将来的Java SE版本中移除
  • 核心库:com.sun.security.auth.callback弃用DialogCallbackHandler类
    使用时会告知将会被移除,在JDK 9的版本中会被彻底移除。
    RFE: 7190273
  • 核心服务:javax.management的RMI连接器IIOP将移除
    JSR-160规范已经更新了关于RMI连接器不再需要支持IIOP传输。Oracle JDK 8会继续支持IIOP的传输,但是支持IIOP的传输希望会在将来版本的JMX Remote API中移除。
    RFE: 8001048
  • 客户端库:javax.accessibility弃用javax.swing.JComponent.accessibleFocusHandler
    可替代的方法: java.awt.Component.AccessibleAWTComponent.accessibleAWTFocusHandler
    RFE: 7179482
  • JavaFX:Scene Graph弃用Builder
    可替代的方法: 使用合适的构造器和set方法去构造对象。
    RFE: RT-30520
  • 安全库:java.security弃用insertProvider
    java.security.SecurityPermission insertProvider.{provider name}的目标名称在将来使用时会被劝阻,因为他可能在重写java.security.Provider.getName方法时会遇到命名约束而被阻止。同样,在使用通过插入一个指定命名或者是选择获取的任意名称的provider授予代码权限时会有同样的风险。
    可替代的方法: 新的insertProvider目标名称。兼容现有已经被保留的policy文件,原因在于新旧的权限都会被Security.addProviderinsertProviderAt方法检测。
    RFE: 8001319
  • HotSpot JVM:gc弃用一些组合
    以下的GC组合被弃用:

    • DefNew + CMS
    • ParNew + SerialOld
    • Incremental CMS

    对应的命令行选项会产生警告信息并且建议你不要使用这样的组合。这些命令行选项会在将来的某个主要版本中移除。

    • 命令行选项-Xinggc被弃用
    • 命令行选项-XX:CMSIncrementalMode被弃用。注意,这个命令行选项会影响所有的CMSIncremental选项。
    • 命令行选项-XX:+UseParNewGC被弃用。除非你同时使用选项-XX:+UseConcMarkSweepGC
    • 命令行选项-XX:-UseParNewGC只有在和-XX:+UseConcMarkSweepGC选项一起使用时被弃用。 想要获得更多的信息,请查看 http://openjdk.java.net/jeps/173 RFE:8006479
  • Hotspot:gc弃用CMS GC的前端收集器
    CMS GC的前端收集器(foreground collector)已经被弃用,并且希望在将来的某个版本中移除。使用G1或者是常规的CMS替代。
    比如在使用-XX:+UseCMSCompactAtFullCollection -XX:+CMSFullGCsBeforeCompaction -XX:+UseCMSCollectionPassing等选项时,会打印一个已经弃用的警告,但是VM仍会继续工作。
    RFE: 8027132

[翻译]JDK8有什么新东西?

翻译官方文档,译者精力有限,忽略部分划删除线。
译者:坤谷(@JianhaoMo) ,校对:井桐(@张同宝

Java SE 8是一个Java主要特性的发布版本。本文总结了在Java SE 8、JDK 8以及Oracle实现的Java SE 8中的新特性和增强的功能。点击下面各个组件的名称可以获取该组件增强的更详细说明。

  • Java编程语言
    • Lambda表达式(Lambda Expressions)作为一种新的语言特性引入到这个版本中。它们允许我们将功能作为方法的参数传递,或者把代码看成是数据。Lambda表达式能够让你更加简洁的表达单个方法的接口(函数式接口)的实例。
    • 方法引用(Method references)为有名称的方法提供易于阅读的lambda表达式。
    • 默认方法(Default methods)能够使新功能被添加到库的接口中,并确保与旧版本中为这些接口写的代码二进制兼容。
    • 重复注解(Repeating Annotations)提供对于同样的声明和类型使用时可以多次使用相同的注解类型的能力。
    • 类型注解(Type Annotations)可以在一个类型的任意使用位置使用注解,而不局限于在类型声明处使用。配合可插拔的类型系统(pluggable type system)使用,类型注解可以提高代码的类型检查能力。
    • 改进类型推断
    • 方法参数反射
  • 集合类
    • 新的 java.util.stream包中的类为集合类的元素提供了流式API来支持函数式的操作。该流式API被集成到集合API中,它使得集合类可以进行(串行或并行map-reduce)批量操作。
    • 改进Has​​hMaps键冲突的性能
  • 紧凑的配置,提供了Java SE平台的预定义子集,使得小型设备的应用程序可以不需要再在整个平台上部署运行。
  • 安全
    • 客户端TLS 1.2默认启用
    • 新的AccessController.doPrivileged变量使代码对其权限的子集进行断言,而避免遍历整个方法栈来检查其他权限
    • 更强大的基于密码的加密算法
    • JSSE服务器支持SSL/TLS服务器名称指示(SNI)扩展
    • 支持AEAD算法:增强了SunJCE provider,支持AES/GCM/NoPadding cipher实现以及GCM算法参数。增强SunJSSE provider,支持基于cipher suites的AEAD模式。看考甲骨文提供的文档,JEP 115。
    • 增强KeyStore,包括新的Domain KeyStore类型java.security.DomainLoadStoreParameter,以及keytool工具新的命令选项-importpassword
    • SHA-224消息摘要
    • NSA Suite B Cryptography增强
    • 更好地支持高熵随机数生成
    • 新的java.security.cert.PKIXRevocationChecker类用于配置X.509证书吊销检查。
    • Windows 64位PKCS11。
    • 为Kerberos 5 Replay Caching 新增rcache类型。
    • 支持Kerberos 5协议转换和约束委派。
    • 默认情况下禁用Kerberos 5弱加密类型。
    • 对GSS-API / Kerberos 5提供未绑定的SASL。
    • SASL服务支持多主机名。
    • Mac OS X支持JNI桥接原生JGSS。
    • SunJSSE provider 支持更强的短暂DH密钥。
    • JSSE支持服务器端cipher suites的定制。
  • JavaFX
  • 工具
    • jjs命令用于启动Nashorn引擎。
    • java命令可以启动JavaFX应用程序。
    • 重新设计java man页。
    • jdeps命令行工具用于分析类文件。
    • Java管理扩展(JMX)给诊断命令提供远程访问。
    • jarsigner工具增加请求从Time Stamping Authority(TSA)已签名的时间戳的选项。
    • javac工具
    • javac命令的-parameters选项可以用来存储形参名称并可以用反射API来获取形参名称。
    • javac命令现在能正确地执行在Java语言规范(JLS)第15.21关于相等运算符的类型规则。
    • javac工具支持检查javadoc注释的内容。检查可能导致javadoc生成的文档的各种问题,如无效的HTML或可访问性问题。 该功能是通过启用新的-Xdoclint选项来执行。 欲了解更多信息,请参阅运行“ javac -X”的输出。这个功能也可以在javadoc工具中使用,默认启用。
    • javac工具还提供根据需要生成native头文件的能力。 这免去了构建流程中单独运行javah工具的必要。javac通过使用新的-h选项来开启,该选项用于指定头文件写入指定的目录。 native方法和标注了新注释java.lang.annotation.Native的常量字段,都将生成头文件。
    • Javadoc工具
    • javadoc工具支持新DocTreeAPI,使你能够以抽象语法树的方式遍历Javadoc注释。
    • javadoc工具支持新的Javadoc访问API,使你能够直接从Java应用程序调用Javadoc工具,而不执行新的进程。 请参阅的javadoc新特性了解更多信息。
    • javadoc工具现在已经支持检查javadoc注释的内容,检查可能生成导致各种问题的注释,如无效的HTML或可访问性问题。 该功能是默认启用,并且还可以通过新的-Xdoclint选项控制。 欲了解更多信息,请参阅“javadoc -X ”的输出。 这个功能也可以在javac工具提供,虽然它不是默认启用的。
  • 国际化
    • Unicode的改进,包括对Unicode 6.2.0支持。
    • 采用Unicode的CLDR数据和java.locale.providers系统属性。
    • 新的Calendar和LocaleAPIs。
    • 能够扩展安装自定义资源包。
  • 部署
    • 对于沙盒applets和Java Web Start应用程序,URLPermission现在用于允许连接回他们所启动的服务器,SocketPermission权限不再授予。
    • 所有安全级别的主JAR文件的manifest必须包含权限属性。
  • 日期-时间包
    • 一组新的软件包提供了一个全面的日期-时间模式。
  • 脚本
  • Pack200
    • Pack200支持由JSR 292引入的常量池项和新的字节码。
    • 支持由JSR-292,JSR-308和JSR-335引入的JDK8类文件的变化。
  • IO和NIO
    • Solaris新增基于Solaris事件端口机制的SelectorProvider,使用时,系统属性java.nio.channels.spi.Selector设置为值sun.nio.ch.EventPortSelectorProvider
    • 减少<JDK_HOME>/jre/lib/charsets.jar文件的大小。
    • 优化java.lang.String(byte[], *)构造函数和java.lang.String.getBytes()方法的性能。
  • java.lang和java.util包
    • 并行数组排序
    • 标准Base64编码器和解码器
    • 无符号运算支持
  • JDBC
    • 删除JDBC-ODBC桥。
    • JDBC 4.2引入了新功能。
  • Java DB
    • JDK 8包含Java DB 10.10。
  • 网络
    • 添加类java.net.URLPermission
    • 如果启用了security manager,类java.net.HttpURLConnection 请求打开一个连接需要获得许可。
  • 并发
    • java.util.concurrent包添加了相关类和接口。
    • java.util.concurrent.ConcurrentHashMap类添加了方法,用于支持基于新的流设施和lambda表达式聚合操作。
    • java.util.concurrent.atomic包添加了类,用于支持可扩展的、可更新的变量。
    • java.util.concurrent.ForkJoinPool类添加了方法,用于支持公共线程池。
    • 添加了java.util.concurrent.locks.StampedLock类,提供了有三种模式的capability-based lock,用于控制读/写访问。
  • Java的XMLJAXP
  • Hotsport JVM
    • 为使用Advanced Encryption Standard(AES)添加硬件相关的intrinsics。 可以通过UseAESUseAESIntrinsics两个flag使得Intel硬件上的基于硬件的AES intrinsics生效。 硬件必须是2010或更高版本的Westmere硬件。例如,要启用硬件AES,使用下面的选项: -XX:+ UseAES -XX:+ UseAESIntrinsics要禁用硬件AES使用下面的选项: -XX:-UseAES -XX:-UseAESIntrinsics
    • 去除PermGen。
    • 对方法调用的字节码指令,支持Java编程语言的默认方法。
  • Java Mission Control 5.3发行说明
    • JDK 8包括JavaMission Control 5.3。

性能调优利器——PerfJ

PerfJ是我业余时间开发的一个小工具,现在来说不建议用在生产环境,因为还没有得到更多案例的证明。

平时我们在做Java程序Profiling时,基本上使用YJP, hprof, jvisualvm这样的工具,还有for i in {1..100}; do jstack $pid > $i; done;这些工具基本上两个原理,一个是sampling(采样),一个是instrument(注入一些信息到方法里面,然后统计)。

这些工具有些问题不能解决:

如果是网络io,磁盘io等问题,基本只能看到epollWait, socket/file的read这一层,看不到更深层次的原因;
如果看到Java程序占用CPU很高,用上面工具只能估计。先用top看到线程级别,看哪个线程CPU搞,然后再jstack。

即系统工具都无法上达Java这一层。

讲师:周敏(@MinZhou

整理:以亭

1. Principle behind PerfJ

PerfJ是我业余时间开发的一个小工具,现在来说不建议用在生产环境,因为还没有得到更多案例的证明。

平时我们在做Java程序Profiling时,基本上使用YJP, hprof, jvisualvm这样的工具,还有for i in {1..100}; do jstack $pid > $i; done;这些工具基本上两个原理,一个是sampling(采样),一个是instrument(注入一些信息到方法里面,然后统计)。

这些工具有些问题不能解决:

  • 如果是网络io,磁盘io等问题,基本只能看到epollWait, socket/file的read这一层,看不到更深层次的原因;
  • 如果看到Java程序占用CPU很高,用上面工具只能估计。先用top看到线程级别,看哪个线程CPU搞,然后再jstack。

即系统工具都无法上达Java这一层。

PerfJ的原理

linux perf 是一个强大的profiling, tracing的工具集, 包含在kernel代码树里,但perf对于java的程序没有办法。

首先, java很多程序被Just in time compiling了, perf不知道java函数的原始名字。

其次,JVM由于历史原因把frame pointer的register给去了,所以无法爬栈。

PerfJ其实只是linux perf的一个包装。解决了第一个问题,JIT,原理是通过JVMTI把perfj attach到目标JVM,将它JIT后各函数的原始函数名和地址的对照拿出来,写到/tmp/perf-$pid.map文件中,之后perf会加载这个文件,从而统计出java程序的一些信息。第二个问题可以通过升级JDK到8u60 b19以上解决(见后面)。

2. Install perf, PerfJ, java 8u60 b19

安装前准备:

Linux 64bit 物理机;

 

(1)安装perf工具

centos系:yum install perf.x86_64;

ubuntu:apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`

(2)安装PerfJ

checkout the source from github

git clone https://github.com/coderplay/PerfJ.git

and run below in the future buildingcd PerfJ

./gradlew releaseTarGz

如果没有装gcc,可以到http://blog.minzhou.info/PerfJ/PerfJ-1.0.tgz这里下载,但不保证都能用 🙂

(3)下载火焰图生成工具FlameGraph

git clone https://github.com/brendangregg/FlameGraph.git

  • 安装最新JDK

https://jdk8.java.net/download.html

务必是jdk 8u60 b19以上!

如果打不开,可以到这里下载:http://share.weiyun.com/cd36ccd7f6e837bd4699f3a1448f31e8

下载后解压,修改PATH和JAVA_HOME环境变量。

export JAVA_HOME=新jdk路径

export PATH=$JAVA_HOME/bin:$PATH

3. Show some flame graph

http://blog.minzhou.info/perfj/perfj.svg

1

图中,绿色是Java代码,黄色是JVM代码,红色是Kernel代码,宽度表示占用CPU时间。

4. Building PerfJ and untar the ball

解压perfj-1.0.tgz

cd perfj-1.0/

运行:bin/perfj list, 查看所支持的事件。运行截图:

2

5. Context Switch

下载模拟context switch的java代码:http://blog/minzhou.info/perfj/ContextSwitchTest.java

编译:javac –cp . ContextSwitchTest.java

运行:java –cp . ContextSwitchTest

开启另一窗口,运行vmstat 1,发现cs很高。

使用java –cp . –XX:+PreserveFramePointer ContextSwitchTest,开启frame pointer

同时另一窗口运行:bin/perfj record -e cs  -g -p `pgrep -f ContextSwitchTest`

跑一段时间后,ctrl + C,然后bin/perfj report –stdio

得到结果如下:

3

如果出现这种无法打开map文件错误:

4

可能是perfj版本问题。下载这个:http://blog.minzhou.info/perfj/perfj-1.0.tgz(打不开?用http://share.weiyun.com/ae0cb80416b3ca3d97fc1877aa1bde4a)就好了。

从这个程序的stack可以看出Unsafe.park会导致当前线程让出cpu, 引发context switch。

刚才那段代码是用来看context switch在哪里发生,以前的工具是没有办法的。

6. CPU cache miss example

下载代码:blog.minzhou.info/PerfJ/L1CacheMiss.java

编译:javac -cp . L1CacheMiss.java

运行:java -cp . -Xmx2g -XX:+PreserveFramePointer L1CacheMiss

捕获:bin/PerfJ record -e  L1-dcache-load-misses  -g -p `pgrep -f CacheMiss`

报告:bin/perfj report –stdio

cache hit比cache miss快几十倍。

5

通过上面看到,94%的cache miss是cachemiss这个函数造成的。

7. Homework , Threadpool comparison

时间关系,未做介绍

8. More complex examples, io request profiling

下载可执行程序:http://blog.minzhou.info/PerfJ/leveldb-benchmark.jar

直接运行: java -cp leveldb-benchmark.jar  -XX:+PreserveFramePointer org.iq80.leveldb.benchmark.DbBenchmark  –benchmarks=fillrandom  –num=100000000

捕获:bin/perfj record -F 99 -g -p `pgrep -f DbBenchmark`

-F是指sampling的频率为99Hz。

报告:bin/perfj report –stdio

9. Off cpu analysis

在cpu上运行的有时候不占程序最主要时间,有时候往往是网络在等,有时候是写了一个sleep代码,这个往往很难找。我们继续用刚才的leveldb 例子:

运行: java -cp leveldb-benchmark.jar -XX:+PreserveFramePointer org.iq80.leveldb.benchmark.DbBenchmark –benchmarks=fillrandom –num=100000000

捕获:bin/PerfJ record -e sched:sched_stat_sleep -e sched:sched_switch  -e sched:sched_process_exit -g -o ~/perf.data.raw -p `pgrep -f Benchmark`

因为我们可以知道系统scheduler的trace point,所以我们record三个事件,然后通过这三件事件的时间差,知道哪个是off CPU之后不干活。

注入:bin/PerfJ inject -v -s -i ~/perf.data.raw -o ~/perf.data

报告: bin/PerfJ report –stdio –show-total-period -i ~/perf.data

6

可以看到截屏的这些stack虽然不占CPU,但在off cpu的时候一直歇着,很有可能拖慢程序的执行。

我们可以看到LockSupport.park的jni实现里有JVM sleep,所以在抢java concurrent锁的时候,如果冲突很大,还可能被锁sleep一下了。大家可以线下在网络代码里试试off cpu。

这里有很多示例:

http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html

http://www.brendangregg.com/perf.html

大部分是perf的用法,大家也可以用perfj试试,大部分都可以用。

例如上面leveldb的例子我们可以分析IO:

bin/perfj record -e block:block_rq_issue -F 99 -g -p `pgrep -f DbBenchmark`

用这个看物理设备的块请求是谁操作的,是哪些java代码调用的,然后我可以看到这个stack:

7

知道了java代码位置,可以用ftrace看这个io request的io size,用了多少时间,是random io还是seq io。

https://github.com/brendangregg/perf-tools/blob/master/examples/iosnoop_example.txt

# ./iosnoop Tracing block I/O… Ctrl-C to end.

COMM             PID    TYPE DEV      BLOCK        BYTES     LATmssupervise        1809   W    202,1    17039968     4096       1.32supervise        1809   W    202,1    17039976     4096       1.30tar              14794  RM   202,1    8457608      4096       7.53tar              14794  RM   202,1    8470336      4096      14.90tar              14794  RM   202,1    8470368      4096       0.27tar              14794  RM   202,1    8470784      4096       7.74tar              14794  RM   202,1    8470360      4096       0.25tar              14794  RM   202,1    8469968      4096       0.24tar              14794  RM   202,1    8470240      4096       0.24tar              14794  RM   202,1    8470392      4096       0.23tar              14794  RM   202,1    8470544      4096       5.96tar              14794  RM   202,1    8470552      4096       0.27tar              14794  RM   202,1    8470384      4096       0.24[…]

 

像这样,perfj找到系统的事件之后,然后用ftrace跟踪详细的信息。

10. CPU flame graph

捕获成功后,会在当前目录下生成perf.data。利用这个文件可以绘制火焰图。

sudo mv perf.data ../FlameGraph

cd ../FlameGraph/

sudo perf script | ./stackcollapse-perf.pl > out.perf-folded

./flamegraph.pl out.perf-folded –colors java > PerfJ.svg