invoke-dynamic
Understanding Java method invocation with invokedynamic
As of Java 8, invokedynamic is used as a primary implementation mechanism to provide advanced platform features. One of the clearest and simplest examples of this use of the opcode is in the implementation of lambda expressions. To follow along with the rest of this article, youโll need to have some familiarity with how the JVM invokes methods, or youโll need to read the first article in this series.
Java8๋ถํฐ, invokedynamic์ด ์ฃผ์ํ ๊ตฌํ์ฒด๋ก ์ถ๊ฐ๋๋ฏ.
invokeddyanmic์ ์ด์ฉํด์ lambda expression์ด ์ฐ๋ฆฌ๊ฐ ์์ํ๋๋ก ๋์ํ๋๋ก ํ๋ ๋ฏ.
Lambdas are object references
Java has only two types of values: primitive types (such as char and int) and object references. Lambdas are obviously not primitive types, so they must be object references. Consider the following lambda:
Java๋ 2๊ฐ์ง ํ์ ์ด ์์.
primitive type (char, int ๊ฐ์) ๊ทธ๋ฆฌ๊ณ object reference
๋๋ค๋ primitive type์ ๋ช ๋ฐฑํ๊ฒ ์๋๊ณ .. ๊ทธ๋ ๋ค๋ฉด object reference ์.
The lambda expression is assigned to a variable of type Runnable. This means that the lambda evaluates to a reference to an object that has a type that is compatible with Runnable.
() -> System.out.println(HELLO);
๊ฐ Runnable ๋ณ์์ ํ ๋น๋จ.์ด ์ด์ผ๊ธฐ๋ ๋๋ค๋ object reference๋ก ํ๋จ๋๋ค๋ ๊ฒ. (๋๊ตฌ์๊ฒ? ์ปดํ์ผ๋ฌ์๊ฒ)
Essentially, this objectโs type will be some subclass of Object that has defined one extra method (and has no fields). The extra method is understood to be the run() method expected by the Runnable interface.
์๋ง ๋๋ค๋ก ์์ฑ๋๋ ๊ฐ์ฒด๋ Object์ ์๋ธ ํด๋์ค ์ผ๊บผ๊ณ , Runnable ์ด๋ผ๋ ์ธํฐํ์ด์ค๋ก ์นํ๋๋ ๊ฒ์ผ๋ก ๋ณด์์ run() ์ด๋ผ๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ ๊ฒ์ด๋ผ ์๊ฐ๋จ.
Before Java 8, such an object was represented only by an instance of a concrete anonymous class that implemented Runnable. In fact, in the initial prototypes of Java 8 lambdas, inner classes were used as the implementation technology.
์๋ฐ 8 ์ด์ ์๋ ์ด๋ฌํ ๊ฐ์ฒด(๋๋ค๋ก ์ธํด ์์ฑ๋๋ ๊ฐ์ฒด๊ฐ Runnable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ์ต๋ช ํด๋์ค๋ก๋ง ํํ๋์์.
Java8 ์ ์ด๊ธฐ ํ๋กํ ํ์ ์, inner class๋ฅผ ์ด์ฉํด์ ๋๋ค๊ฐ ์์ฑํ๋ ๊ฐ์ฒด๋ฅผ ๊ตฌํํ๋ค๊ณ ํจ. (์ ์ดํด๋ ์๋จ. ์ด๋ป๊ฒ ๋ด๋ถ ํด๋์ค๋ฅผ ํตํด ํด๊ฒฐ ํ ์ ์์์๊น)
..Fixing the representation to use explicit inner classes would prevent a different representation from being used by a future version of the platform
inner class๋ฅผ ์ฌ์ฉํ๋๋ก ๋ช ์ํ๋ฉด, ๋ค๋ฅธ ๋ฒ์ ์ JDK์์๋ ์ฌ์ฉํ์ง ๋ชปํ ์๋ ์์. (์ ์ฐ์ฑ์ด ๋จ์ด์ง๋ ์ ์ด ์์๋๋ฏ)
inner class๋ฅผ ์ฌ์ฉํ bytecode ์ธ๋ฏ.
offset 0 ์ด invokedynamic(๋์ ์ผ๋ก...) ์ ํตํด ํธ์ถ.
์๋ง๋ ๋๋ค์ฝ๋๊ฐ Runnable์ ๊ตฌํํ ์ต๋ช ํด๋์ค๋ก stack ์ ์์๋ค๋ ์๋ฏธ ์๋๊น?
How invokedynamic operates
n the case of invokestatic and invokespecial sites, which I discussed in the previous article, the exact implementation method (referred to as the call target) is known at compile time. In the case of invokevirtual and invokeinterface, the call target is determined at runtime. However, the target selection is subject to the constraints of the Java language inheritance rules and type system. As a result, at least some call target information is known at compile time.
invokestatic
,invokespecial
์ ๊ฒฝ์ฐ ์ปดํ์ผ ํ์์ ์ด๋ค ๋์์ ํธ์ถํ ์ง ์ ์ ์์.invokevirtual
,invokeinterface
์ ๊ฒฝ์ฐ์๋ ๋ํ์์ ์ด๋ค ๋์์ด ํธ์ถ๋ ์ง ๊ฒฐ์ ๋จ.๊ทธ๋ ์ง๋ง Java ์ธ์ด ํน์ฑ์..? ์๋ง ์ ํ์ ์ด๋ผ ํด๋ ๋ช๋ช ํธ์ถ์ ์ปดํ์ผ ํ์์ ์ ์ ์๋๋ฏ.
In contrast, invokedynamic is far more flexible about which method will actually be called when the opcode is dispatched. To allow for this flexibility, invokedynamic opcodes refer to a special attribute in the constant pool of the class that contains the dynamic invocation. This attribute contains additional information to support the dynamic nature of the call, called bootstrap methods (BSMs).
invokedynamic
์ด๋ค ๋ฉ์๋๊ฐ ์ค์ ๋ก ํธ์ถ๋ ์ง ๊ฒฐ์ ํ๋ ๋ถ๋ถ์์ ์ ์ฐํจ.์ด๋ฌํ ์ ์ฐ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด์, invokedynamic opcodes ๋ class constant pool์ ํน๋ณํ ์์ฑ(dynamic invocation์ ํฌํจํ) ์ฐธ์กฐํ๊ณ ์์.
opcodes? is the portion of a machine language instruction that specifies the operation to be performed
์ด๋ฌํ ์์ฑ์ bootstrap method ๋ผ๊ณ ํ๋ ๋์ ํธ์ถ์ ์ง์ํ๊ธฐ ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์.
BSMs are a key part of invokedynamic, and every invokedynamic call site has a constant pool entry for a corresponding BSM. To allow the association of a BSM to a specific invokedynamic call site, a new entry type, also called InvokeDynamic, has been added to the class file format as of Java 7.
BSM์ invokedynamic ์ค์ํ ๋ถ๋ถ.
๋ชจ๋ ๋์ ํธ์ถ์ constant pool entry๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ, BSM์ ๋์๋๋ ๊ฐ์ธ๋ฏ.
The call site of the invokedynamic instruction is said to be unlaced at class loading time. The BSM is called to determine what method should actually be called, and the resulting CallSite object will then be laced into the call site.
In the simplest case, that of a ConstantCallSite, as soon as the lookup has been done once, it will not need to be repeated. Instead, the target of the call site will be directly called on all future invocations without any further work. This means that the call site is now stable and is, therefore, friendly to other JVM subsystems, such as the just-in-time (JIT) compiler.
invokedynamic instruction ์ class loading ์์ ์ unlaced ๋๊ณ (์ฌ๋ผ๊ฐ์ง ์๋๋ป์ธ๋ฏ)
์ค์ ๋ก๋ ์ด๋ค ๋ฉ์๋๊ฐ ํธ์ถ๋๋ ์ง ํ๋จํ๊ธฐ ์ํด์ BSM์ ํธ์ถ. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๊ฐ์ฒด๊ฐ racing ๋จ (์ฌ๋ผ๊ฐ)
์ด๋ฌํ ์์ ์ ํ๋ฒ ์ํ๋๋ฉด, ๋ค์ ๋ฐ๋ณตํ ํ์๊ฐ ์๋๋ฏ
For this mechanism to work efficiently, the JDK must contain suitable types to represent the call site, the BSMs, and other parts of the implementation. Javaโs original core reflection types are capable of representing methods and types. However, the API dates from the very early days of the Java platform and has several aspects that make it a less-than-ideal choice.
For example, reflection predates both collections and generics. As a result, method signatures are represented by Class[] in the Reflection API. This can be cumbersome and error-prone, and it is hampered by the verbose nature of Javaโs array syntax. It is further complicated by the need to manually box and unbox primitive types and to work around the possibility of void methods.
์์ ๊ฐ์ ์์ ์ JAva Reflection API๋ฅผ ํตํด์ ํ ์ ์๊ธด ํ๋๋ฐ.. ์ด์์ ์ธ ์ ํ์ ์๋์์.
Reflection์ Collection, Generic ๋ณด๋ค ์์ ๊ฐ๋ฐ๋์ด ์์ด์, ํธ์ถ ๋์์ ๋ํ ์ ์ ํ ๋ฉ์๋๋ฅผ ์ฐพ์ง ๋ชปํ์๋ ๋ฏ.
๋ํ ๋ฆฌํ๋ ์ ์ ์ฌ์ฉํ๋ฉด ์๋์ผ๋ก box or unbox ํด์ผํ๊ณ , void ๋ฉ์๋์ ๋ํ ๊ฐ๋ฅ์ฑ๋ ํด๊ฒฐํด์ผํ๋๋ฏ.
Method handles to the rescue
Instead of forcing the programmer to deal with these issues, Java 7 introduced a new API, called MethodHandles, to represent the necessary abstractions. The core of this API is the package java.lang.invoke and especially the class MethodHandle. Instances of this type provide the ability to call a method, and they are directly executable.
๋ฆฌํ๋ ์ ์ ์ฌ์ฉํ๋ฉด ํ๋ก๊ทธ๋๋จธ๋ก๋ถํฐ ์์์ ์ ๊ธฐ๋ ๋ฌธ์ ์ ๋ํด์ ์์์ ํธ๋ค๋งํ๋ผ๋ ์ด์ผ๊ธฐ๊ฐ ๋๋๊น..
Java7์ MethodHandless ๋ผ๋ API ๋์
java.lang.invoke ํจํค์ง์ MethodHanlde ํด๋์ค ๋์ ์ ํตํด, ๋ฉ์๋๋ฅผ ํธ์ถํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณต.
To get a handle for a method, the method must be looked up via a lookup context. The usual way to get a context is to call the static helper method MethodHandles.lookup(). This method returns a lookup context based on the currently executing method....
One important difference between method handles and reflection is that lookup contexts return only methods that were accessible from the scope where the lookup object was created. There is no way to subvert this; there is no equivalent of the setAccessible() back door that is present in reflection. This means that method handles are safe to use under all circumstances, including using them with a security manager.
method๋ฅผ ์ํ handle์ ์ป์ผ๋ ค๋ฉด
MethodHandles.lookup()
ํธ์ถ์ ํตํด ์ปจํ ์คํธ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ.๋ฉ์๋ ํธ๋ค๊ณผ ๋ฆฌํ๋ ์ ์ ์ฐจ์ด์ ์, lookup context๋ ๋ฉ์๋๋ง ๋ฐํํ๋ค๋ ์ .
์ด๋ฅผ ๋ฌด๋ ฅํํ ๋ฐฉ๋ฒ ์์.
๋ฆฌํ๋ ์ ์์ ์ ๊ณตํ๋
setAccessbile()
์ ๊ฐ์ ๋ฐฉ๋ฒ ์์.
์ด ์ด์ผ๊ธฐ๋ ๋ฉ์๋ ํธ๋ค์ ๋ชจ๋ ํ๊ฒฝ์์ ์์ ์ฑ์ ๋ณด์ฅํ๋ ๊ฒ.
However, care must be taken, as the access control check has been moved to method-lookup time. This means that a lookup context can hand out references to private methods that were visible to the lookup but are not necessarily visible at the time when the method handle is invoked.
To solve the problems of representing method signatures, the MethodHandles API also includes the MethodType class. This is a simple immutable type that has some very useful properties and does the following:
์กฐ์ฌํด์ผํ ์ ๋ ์๊ธดํจ.
lookup context์์๋ private ๋ฉ์๋์ ๋ํ ์ฐธ์กฐ๊ฐ ๊ฐ๋ฅํจ.
method handle์ด ํธ์ถํ ๋๋ ๋ณด์ด์ง ์๋๋ฐ..
์ด๋ฌํ ์ ๊ทผ๊ด๋ จ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์, Method Type ํด๋์ค๋ฅผ ํฌํจํ์. ์ด ํด๋์ค๋ ๋ณ๊ฒฝ๋ถ๊ฐํ ํด๋์ค๋ก ์ฌ๋ฌ๋ชจ๋ก ์ ์ฉํ properties๋ฅผ ๊ฐ์ง๊ณ ์์.
๋งค ์ ๊ทผ๊ฐ๋ฅํ ๋ฉ์๋ ๋ง๋ค ์๋ก์จ MethodType์ ์์ฑํ์ง ์๋๋ก ํจ.
MethodType์ ์ผ๋จ ๋ง๋ค๋ฉด, MethodHandle ๋ณด๊ณ ๋ฉ์๋ ์ด๋ฆ์ ํตํด ์ฐพ์ผ๋ผ๊ณ ํ ์ ์์.
reflective ํธ์ถ๊ณผ ๋น์ทํ๊ณ ๋์ํ๋๋ฐ, receiver object (mh) ๊ฐ ์ธ์คํด์ค ๋ฉ์๋๋ฅผ ๊ณต๊ธ๋ฐ์์ผ ํจ.
๊ทธ๋ฆฌ๊ณ ํธ์ถํ ๋ ์์ธ์ฒ๋ฆฌ๋ ํด์ผํ๊ณ .
The concept of a BSM should now be clear: When program control reaches an invokedynamic call site for the first time, the associated BSM is called. The BSM returns a call site object containing a method handle to the method that will actually be bound into the call site. For this mechanism to function correctly with static typing, the BSM must return a handle to a method of the correct method signature.
To get back to the lambda expression example I gave earlier, you can think of the invokedynamic opcode as representing a call to some sort of platform factory method for a lambda expression. The actual body of the lambda has been transformed into a private static method on the class where the lambda is defined.
์ฒ์ invokedynamic ํธ์ถ๋๋ฉด, ๊ด๋ จ๋ BSM์ ํธ์ถํจ.
๊ทธ๋ฌ๋ฉด BSM์ MethodHandle์ด ํฌํจ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํ.
์์ ๋๋ค ์ฝ๋์ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด๋ฉด, ๋๋ค์ ์ค์ ๋ณธ๋ฌธ์ ํด๋์ค์
private static method
๋ก ๋ณํ๋์์.
๋๋ค ํฉํ ๋ฆฌ๋ Runnable์ ๊ตฌํํ ์ด๋ค ํ์ ์ ์ธ์คํด์ค๋ฅผ ๋ฐํ
run() ์ด ํธ์ถ๋ ๋, ptivate method ๊ฐ ์คํ๋ ๊ฒ. (๋๋ค ๋ฐ๋)
constant pool
BSM
LambdaMetaFactory.metafactory() ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ๊ณ ์๋๋ฐ,
์ด ๋ถ๋ถ์ด ๋๋ค๊ฐ ์์ฑ๋ ๊ฒฝ์ฐ, ๋ฐํ์์ linkage bytecode๋ฅผ ์์ฑํ๋ ๋ถ๋ถ.
metafactory ๋ lookup object ์ MethodTypes ๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ ์ด๊ฒ type safety๋ฅผ ์ํ ๊ฒ.
MethodHandle(lookup) ๊ฐ์ฒด๋ priavte static method๋ฅผ ๊ฐ๋ฅดํค๊ณ ์๋๋ฐ.
์ด private static method๊ฐ lambda body์
์ฐธ๊ณ
Last updated
Was this helpful?