java字节码级别_在字节码级别了解Java 8 Streams

答案随着时间的推移而增长很多,所以我将从摘要开始:

意见

>对API实际执行的流的跟踪看起来很可怕.许多电话和对象创作.但请注意,对集合中所有元素重复的唯一部分是do-while循环的主体.因此,除了一些不断的开销之外,每个元素的开销是大约6个虚方法调用(调用接口指令 – 我们在接收器上的2个lambdas和4个accept()调用).

>赋予流API调用的lambda被转换为包含实现和invokedynamic指令的静态方法.它不是创建一个新对象,而是给出了如何在运行时创建lambda的处方.之后在创建的lambda对象上调用lambda方法没有什么特别之处(invokeinterface指令).

>您可以观察流如何被懒惰地评估. filter()和map()将它们的操作包装在StatelessOp的匿名子类中,后者又扩展ReferencePipeline,AbstractPipeline和最终BaseStream.实际评估在执行collect()时完成.

>您可以看到流如何真正使用Spliterator而不是Iterator.注意许多分支检查isParallel() – 并行分支将利用Spliterator的方法.

>创建了很多新对象,至少13个.如果在循环中调用此类代码,则可能会遇到垃圾回收问题.对于单次执行,它应该没问题.

>我希望看到两个版本的基准比较. Streams版本可能会慢一些,与“Java 7版本”的差异随着鱼数量的增加而减少.另见related SO question.

在示例中通过执行Streams使用跟踪

下面的伪代码通过使用流执行版本来捕获跟踪.有关如何读取跟踪的说明,请参阅本文的底部.

Stream stream1 = fishList.stream();

// Collection#stream():

Spliterator spliterator = fishList.spliterator();

return Spliterators.spliterator(fishList.a, 0);

return new ArraySpliterator(fishList, 0);

return StreamSupport.stream(spliterator, false)

return new ReferencePipeline.Head(spliterator, StreamOpFlag.fromCharacteristics(spliterator), false)

Predicate fishPredicate = /* new lambda f -> f.contains("fish") */

Stream stream2 = stream1.filter(fishPredicate);

return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { /* ... */ }

Function fishFunction = /* new lambda f.substring(0, 1).toUpperCase() + f.substring(1) */

Stream stream3 = stream2.map(fishFunction);

return new StatelessOp(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) { /* ... */ }

Collector collector = Collectors.toList();

Supplier supplier = /* new lambda */

BiConsumer accumulator = /* new lambda */

BinaryOperator combiner = /* new lambda */

return new CollectorImpl<>(supplier, accumulator, combiner, CH_ID);

List hasFishList = stream3.collect(collector)

// ReferencePipeline#StatelessOp#collect(Collector):

List container;

if (stream3.isParallel() && /* not executed */) { /* not executed */ }

else {

/*>*/TerminalOp terminalOp = ReduceOps.makeRef(collector)

Supplier supplier = Objects.requireNonNull(collector).supplier();

BiConsumer accumulator = collector.accumulator();

BinaryOperator combiner = collector.combiner();

return new ReduceOp(StreamShape.REFERENCE) { /* ... */ }

/*>*/container = stream3.evaluate(terminalOp);

// AbstractPipeline#evaluate(TerminalOp):

if (linkedOrConsumed) { /* not executed */ }

linkedOrConsumed = true;

if (isParallel()) { /* not executed */ }

else {

/*>*/Spliterator spliterator2 = sourceSpliterator(terminalOp.getOpFlags())

// AbstractPipeline#sourceSpliterator(int):

if (sourceStage.sourceSpliterator != null) { /* not executed */ }

/* ... */

if (isParallel()) { /* not executed */ }

return spliterator;

/*>*/terminalOp.evaluateSequential(stream3, spliterator2);

// ReduceOps#ReduceOp#evaluateSequential(PipelineHelper, Spliterator):

ReducingSink sink = terminalOp.makeSink()

return new ReducingSink()

Sink sink = terminalOp.wrapAndCopyInto(sink, spliterator)

Sink wrappedSink = wrapSink(sink)

// AbstractPipeline#wrapSink(Sink)

for (/* executed twice */) { p.opWrapSink(p.previousStage.combinedFlags, sink) }

return new Sink.ChainedReference(sink)

terminalOp.copyInto(wrappedSink, spliterator);

// AbstractPipeline#copyInto()

if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {

/*>*/wrappedSink.begin(spliterator.getExactSizeIfKnown());

/*>*/ /* not important */

/*>*/supplier.get() // initializes ArrayList

/*>*/spliterator.forEachRemaining(wrappedSink)

// Spliterators#ArraySpliterator#foreachRemaining(Consumer):

// ... unimportant code

!! do {

/*>*/action.accept((String)a[i])

} while (++i < hi) // for each fish :)

/*>*/wrappedSink.end() // no-op

} else { /* not executed */}

return sink;

return sink.get()

}

/*>*/if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return container; }

/*>*/else { /* not executed */ }

感叹号指向实际的主力:fishList的Spliterator中的do-while循环.这里是do-while循环的更详细的描述:

do {

/*>*/action.accept((String)a[i])

if (predicate.test(u)) { downstream.accept(u); } // predicate is our fishPredicate

downstream.accept(mapper.apply(u)); // mapper is our fishFunction

accumulator.accept(u)

// calls add(u) on resulting ArrayList

} while (++i < hi) // for each fish :)

在字节码级别上使用Lambdas的Streams API

让我们看一下执行代码的相关部分在字节码中的外观.有趣的是如何

fishList.stream().filter(f -> f.contains("fish")).map(f -> f.substring(0, 1).toUpperCase() + f.ubstring(1)).collect(Collectors.toList());

已翻译.你可以找到完整版本on pastebin.我将只关注过滤器(f – > f.contains(“fish”)):

invokedynamic #26, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate; [

java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

(Ljava/lang/Object;)Z,

FishTest.lambda$fish8$0(Ljava/lang/String;)Z,

(Ljava/lang/String;)Z

]

invokeinterface #27, 2 // InterfaceMethod java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;

那里没有特定于流API的东西,但新的invokedynamic指令用于创建lambdas. Java 7相当于lambdas将创建实现Predicate的匿名内部类.这将被转换为字节码为:

new FishTest$1 // create new instance of Predicate

dup

invokespecial FishTest$1.()V // call constructor

在Java 8中创建lambda将转换为单个invokedynamic指令,而无需创建新对象. invokedynamic指令的目的是将lambda的创建推迟到运行时(而不是编译时).这样可以启用caching lambda instances等功能:

The use of invokedynamic lets us defer the selection of a translation strategy until run time. The runtime implementation is free to select a strategy dynamically to evaluate the lambda expression. … The invokedynamic mechanics allow this to be done without the performance costs that this late binding approach might otherwise impose. … For example, … we generate the class the first time a given lambda factory site is called. Thereafter, future calls to that lambda factory site will re-use the class generated on the first call.

invokedynamic的参数给出了用于构造相应功能接口的实例的“配方”.它们表示运行时实例创建的元数据,对其实现的方法的引用(即Predicate.test())和方法的实现.

在我们的例子中,实现是调用静态方法boolean lambda$fish8$0(String),编译器潜入我们的类.它包含f.contains(“fish”)的实际字节码.如果您使用lambda捕获方法引用(例如list :: add),从外部范围捕获的变量等,事情会变得更复杂 – 在this document中查找“indy”的出现以获取更多信息.

The other parts of bytecode不那么有趣.除了明显的循环之外,do-while循环包含一个invokeinterface指令,在相应的Consumer上调用accept().

accept()调用沿着接收器传播,沿途调用我们的lambdas.这里没有什么特别的,lambda调用和通过接收器的传播都是简单的invokeinterface指令.

如何读取伪代码

缩进用于在缩进代码上方显示未展开的调用体.使用/ *> * /代码开始表示当前调用的延续(需要时为了更好的可读性).所以打电话

Objects.requireNonNull(new Object());

将以跟踪伪代码写为:

Object o = new Object(); // extracted variable to improve visibility of new instance creation

Objects.requireNonNull(o);

// this is the body of Objects.requireNonNull():

if (o == null) {

/*>*/throw new NullPointerException(); // this line is still part of requireNonNull() body

}

return o;

我还跳过了一些不重要的调用,如空检查,省略了通用参数,在适当的情况下将变量内联表达式提取到变量等,以提高可读性.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值