简介
上一篇文章我们讲解了Virtual Call的定义并举例分析了Virtual Call在父类和子类中的优化。
JIT对类可以进行优化,那么对于interface可不可以做同样的优化么?
一起来看看吧。
最常用的接口List
List应该是大家最最常用的接口了,我想这个大家应该不会反驳。
public interface List<E> extends Collection<E> {
今天我们就拿List来做例子,体验一下JIT优化接口的奥秘。
还是上代码,要分析的代码如下:
public class TestVirtualListCall {
public static void main(String
[] args
) throws InterruptedException
{
List
<String> list
=new ArrayList<>();
for (int i
= 0; i
< 10000; i
++)
{
doWithVMethod(list
);
}
Thread
.sleep(1000);
}
public static void doWithVMethod(List
<String> list
)
{
list
.add(“www.manongw.com”);
}
}
如果在命令行运行,大家记得在运行时添加参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:-Inline
直接看JIT Watcher的结果:

我们可以看到JIT中先对ArrayList的实现类做了一个比较。
然后调用的是invokeinterface,但是其本质还是invokevirtual,并且我们可以看到这个调用是被优化过了:optimized virtual call。
多个List的调用
同样的,我们可以测试一下多个list子类的情况下怎么调用:

public class TestVirtualListCall2 {
public static void main(String
[] args
) throws InterruptedException
{
List
<String>[] lists
=new List[]{new ArrayList<>(),new LinkedList<>()};
for (int i
= 0; i
< 10000; i
++)
{
doWithVMethod(lists
[i
%2]);
}
Thread
.sleep(1000);
}
public static void doWithVMethod(List
<String> list
)
{
list
.add(“www.jb51.net”);
}
}
同样,使用JIT Watcher来运行:
我们可以看到JIT做了两次对象类型的比较,然后对两个invokeinterface都做了优化。
结果和我们的父类子类结果是一样的。
不一样的List调用
上面我们在做多个list调用的时候,是轮循着来调用的,如果我们先调用ArrayList的方法,再调用LinkedList的方法,会有什么不同呢?
一起来看看。
public class TestVirtualListCall3 {
public static void main(String
[] args
) throws InterruptedException
{
List
<String> list1
= new ArrayList<>();
List
<String> list2
= new LinkedList<>();
for (int i
= 0; i
< 10000; i
++)
{
doWithVMethod(list1
);
}
Thread
.sleep(1000);
for (int i
= 0; i
< 10000; i
++)
{
doWithVMethod(list2
);
}
Thread
.sleep(1000);
}
public static void doWithVMethod(List
<String> list
)
{
list
.add(“www.jb51.net”);
}
}
上面我们先循环ArrayList,然后再循环LinkedList。
看下结果有什么不同:

可以看到,JIT先比较了ArrayList,然后只做了一次方法的优化。
也就是说LinkedList的调用是没有进行代码优化的。
上面的结果是在C2编译器下,也就是level4的编译水平下解析的。
我们看下如果在C1编译器下,也就是Level3编译水平下有什么不同。

可以看到C1编译下,所有的invokeinterface都没有进行编译优化,只有在C2编译下,才会进行优化。
不同的JVM版本可能优化方式不一样。大家可以自行实验。
总结
本文用实例展示了Virtual Call在interface上面的优化使用。
感兴趣的朋友,可以一起讨论。
补充知识:Java 8 Stream 流已被操作或关闭
在Java 8中,Stream不能重复使用,一旦被消耗或使用,流将被关闭,类似流水线,水龙头的水一样一去不复返
示例 – 流关闭
查看以下示例,它会抛出一个IllegalStateException,表示“流被关闭”。
TestJava8.java
package com
.mkyong
.java8
;
import java
.util
.Arrays
;
import java
.util
.stream
.Stream
;
public class TestJava8 {
public static void main(String
[] args
) {
String
[] array
= {“a”, “b”, “c”, “d”, “e”};
Stream
<String> stream
= Arrays
.stream(array
);
// loop a stream
stream
.forEach(x
–> System
.out
.println(x
));
// reuse it to filter again! throws IllegalStateException
long count
= stream
.filter(x
–> “b”.equals(x
)).count();
System
.out
.println(count
);
}
}
Output
java
.lang
.IllegalStateException
: stream has already been operated upon or closed
at java
.util
.stream
.AbstractPipeline
.(AbstractPipeline
.java
:203)
at java
.util
.stream
.ReferencePipeline
.(ReferencePipeline
.java
:94)
at java
.util
.stream
.ReferencePipeline$StatelessOp
.(ReferencePipeline
.java
:618)
at java
.util
.stream
.ReferencePipeline$
2.(ReferencePipeline
.java
:163)
at java
.util
.stream
.ReferencePipeline
.filter(ReferencePipeline
.java
:162)
at com
.hostingcompass
.whois
.range
.run
.TestJava8
.main(TestJava8
.java
:25)
at sun
.reflect
.NativeMethodAccessorImpl
.invoke0(Native Method
)
at sun
.reflect
.NativeMethodAccessorImpl
.invoke(NativeMethodAccessorImpl
.java
:62)
at sun
.reflect
.DelegatingMethodAccessorImpl
.invoke(DelegatingMethodAccessorImpl
.java
:43)
at java
.lang
.reflect
.Method
.invoke(Method
.java
:498)
at com
.intellij
.rt
.execution
.application
.AppMain
.main(AppMain
.java
:144)
示例 – 重用流
TestJava8.java
package com
.mkyong
.java8
;
import java
.util
.function
.Supplier
;
import java
.util
.stream
.Stream
;
public class TestJava8 {
public static void main(String
[] args
) {
String
[] array
= {“a”, “b”, “c”, “d”, “e”};
Supplier
<Stream
<String>> streamSupplier
= () –> Stream
.of(array
);
//get new stream
streamSupplier
.get().forEach(x
–> System
.out
.println(x
));
//get another new stream
long count
= streamSupplier
.get().filter(x
–> “b”.equals(x
)).count();
System
.out
.println(count
);
}
}
Output
a
b
c
d
e
1
每个get()都会返回一个新的流