Various ByteBuddy questions

735 views
Skip to first unread message

jhalt...@gmail.com

unread,
May 29, 2014, 11:48:20 PM5/29/14
to byte-...@googlegroups.com
I'm evaluating ByteBuddy to potentially replace Cglib and have some various questions:

- In what case is it recommended to reuse ByteBuddy instances?
- Are ByteBuddy instances threadsafe?
- Does ByteBuddy cache classes internally similar to the way that Cglib does, or can it be made to? I'd like to avoid generating the same proxy (defined as a superclass and set of superinterfaces) multiple times.
- Is there a way to make a single `Unloaded` instance and swap in different interceptors prior to instantiation similar to Cglib's `Enhancer.registerCallbacks` method?
- Is there a way to tell if a class was generated via ByteBuddy similar to Cglib's `Enhancer.isEnhanced` method? Would I need to throw in a marker interface to do this?
- Is there a subset of ByteBuddy's packages that I could repackage to support basic proxy creation if that's all I'm interested in? My ByteBuddy usage may look something like this:

new ByteBuddy().withNamingStrategy(NAMING_STRATEGY)
.subclass(Object.class)
.method(METHOD_MATCHER)
.intercept(INVOCATION_HANDLER_ADAPTER)
.make()
.load(ProxyFactory.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();

Thanks,
Jonathan

Rafael Winterhalter

unread,
May 30, 2014, 3:57:15 AM5/30/14
to byte-...@googlegroups.com, jhalt...@gmail.com
Hey Jonathan,
thank you for using Byte Buddy. As for your questions:

1. You should reuse Byte Buddy instances if you want to reuse a certain configuration without considering performance. Most Byte Buddy instances are dead-cheap to create and I did some benchmarking using JMH where I compared reuse vs. recreation and there is no significant overhead in non-reusing. All instances that ship with Byte Buddy are immutable what makes it rather easy for the JVM's just-in-time compiler to optimize your code in case that the creation is time-critical. The "raw" invocation without just-in-time compilation only cares for a few additional nanoseconds.

2. Yes, All Byte Buddy classes that are exposed to a user are fully immutable what makes them thread safe. The reuse of instances, even among different threads is therefore possible and even recommended.

3. No, Byte Buddy does not keep a cache internally. In order to maintain a cache, Byte Buddy would need to remember a quite complex vector of parameters of what classes it created, including the class loader of this class. This would be potentially expensive and dangerous, especially if Byte Buddy had a different class loader than its created classes as it could create severe memory leaks. Also, from my experience with cglib and javassist, their caches do more harm then good, even though the feature seems convenient at first inspection. If you only want to create proxies that are always loaded with the same class loader as the proxied class, you could for example write a simple class-to-class cache. If Byte Buddy maintained the cache, it could not make this assumption but had to store a vector of all (!) input variables and compare any vector to other inputs. This would not be very efficient. Therefore, you should write your own cache or use a library that specializes on caching such as Ehcache or Guava caches. See this question on Stack Overflow that relates to the same issue: http://stackoverflow.com/questions/23732236/caching-generated-classes-in-byte-buddy

4. If I understand this question correctly, then you want to register an instance-specific callback? You can use the MethodDelegation to only define a field without a value. This is also possible when using an invocation handler adapter as I assume it is what you intend to to. The invocation handler defines a method MethodDelegation#of(String) where the string describes a field name. All invocations will then be forwarded to the handler in this field (which must be set manually). You can additionally define an accessor method for this field, using any user-defined interface and the FieldAccessor instrumentation. An example of something similar can be found in the tutorial where the FieldAccessor is explained. The explanation comes with a runnable code example. By using your own interface for accessing a field, you can also avoid limitations such as javassists only working with a single interceptor and its inability to proxy any class that defines methods named "getProxy" or "setProxy".

5. This is against Byte Buddy's philosophy which wants to be as transparent as possible. As a matter of fact, all Byte Buddy generated classes are fully usable without Byte Buddy being found on the class path. This allows for easy serialization and using Byte Buddy in build-time instrumentation. You can however define your own marker interface and simply implement this interface for any instance.

6. No, there is no such submodule, but Byte Buddy is merely 500 kB big and I do not think it will grow much further in size. (As a comparison, cglib is about 350 kB big.) A problem Byte Buddy or any other ASM-using library faces in this context is its repackaging dependency on ASM. This makes modularization hard as you need to offer a dependency with and without dependencies for each module and it tends to confuse both library users and maintainers of code that use ASM. It could also lead to confusing class path issues if someone used a subset of a no-dependency version and someone else used another subset of a dependency-version.

Once again, thanks for using Byte Buddy and stay tuned for an update to version 0.2 which will introduce proper support for Java 8 features such as calling default methods, several runtime performance improvements and some miscellaneousfeatures such as a "forwarder instrumentation" what makes the creation of very fast proxy classes very easy. Stay tuned!

Best regards, Rafael

Jonathan Halterman

unread,
May 30, 2014, 2:32:48 PM5/30/14
to Rafael Winterhalter, byte-...@googlegroups.com
Hi Rafael - Thanks for all of the info. A few responses inline below:


On Fri, May 30, 2014 at 12:57 AM, Rafael Winterhalter <rafae...@gmail.com> wrote:
Hey Jonathan,
thank you for using Byte Buddy. As for your questions:

1. You should reuse Byte Buddy instances if you want to reuse a certain configuration without considering performance. Most Byte Buddy instances are dead-cheap to create and I did some benchmarking using JMH where I compared reuse vs. recreation and there is no significant overhead in non-reusing. All instances that ship with Byte Buddy are immutable what makes it rather easy for the JVM's just-in-time compiler to optimize your code in case that the creation is time-critical. The "raw" invocation without just-in-time compilation only cares for a few additional nanoseconds.

2. Yes, All Byte Buddy classes that are exposed to a user are fully immutable what makes them thread safe. The reuse of instances, even among different threads is therefore possible and even recommended.

3. No, Byte Buddy does not keep a cache internally. In order to maintain a cache, Byte Buddy would need to remember a quite complex vector of parameters of what classes it created, including the class loader of this class. This would be potentially expensive and dangerous, especially if Byte Buddy had a different class loader than its created classes as it could create severe memory leaks. Also, from my experience with cglib and javassist, their caches do more harm then good, even though the feature seems convenient at first inspection. If you only want to create proxies that are always loaded with the same class loader as the proxied class, you could for example write a simple class-to-class cache. If Byte Buddy maintained the cache, it could not make this assumption but had to store a vector of all (!) input variables and compare any vector to other inputs. This would not be very efficient. Therefore, you should write your own cache or use a library that specializes on caching such as Ehcache or Guava caches. See this question on Stack Overflow that relates to the same issue: http://stackoverflow.com/questions/23732236/caching-generated-classes-in-byte-buddy

This makes sense. I'll probably do something similar to the JDK Proxy's Loader->Class weak caching.
 

4. If I understand this question correctly, then you want to register an instance-specific callback?

Yes - Cglib allows you to swap in different callbacks for type prior to instantiating a new instance. I think it uses a static method with a ThreadLocal in the enhanced class to track callbacks.
 
You can use the MethodDelegation to only define a field without a value. This is also possible when using an invocation handler adapter as I assume it is what you intend to to. The invocation handler defines a method MethodDelegation#of(String) where the string describes a field name. All invocations will then be forwarded to the handler in this field (which must be set manually).

That makes sense - similar to how JDK proxies work. Perhaps a recipe for this on the website would be nice since I imagine it's a common use case.
 
You can additionally define an accessor method for this field, using any user-defined interface and the FieldAccessor instrumentation. An example of something similar can be found in the tutorial where the FieldAccessor is explained. The explanation comes with a runnable code example. By using your own interface for accessing a field, you can also avoid limitations such as javassists only working with a single interceptor and its inability to proxy any class that defines methods named "getProxy" or "setProxy".

5. This is against Byte Buddy's philosophy which wants to be as transparent as possible. As a matter of fact, all Byte Buddy generated classes are fully usable without Byte Buddy being found on the class path. This allows for easy serialization and using Byte Buddy in build-time instrumentation. You can however define your own marker interface and simply implement this interface for any instance.

6. No, there is no such submodule, but Byte Buddy is merely 500 kB big and I do not think it will grow much further in size. (As a comparison, cglib is about 350 kB big.) A problem Byte Buddy or any other ASM-using library faces in this context is its repackaging dependency on ASM. This makes modularization hard as you need to offer a dependency with and without dependencies for each module and it tends to confuse both library users and maintainers of code that use ASM. It could also lead to confusing class path issues if someone used a subset of a no-dependency version and someone else used another subset of a dependency-version.

I currently repackage Cglib and ASM, but I only include the packages needed to generate proxies via the Enhancer. This gets my library's total jar size down to 376k, including the repackaged Cglib and ASM stuff. I'd love to do something similar with ByteBuddy and only go after the packages I need. Is there any guidance you can give me along these lines?
 

Once again, thanks for using Byte Buddy and stay tuned for an update to version 0.2 which will introduce proper support for Java 8 features such as calling default methods, several runtime performance improvements and some miscellaneousfeatures such as a "forwarder instrumentation" what makes the creation of very fast proxy classes very easy. Stay tuned!

Sure! Another question I wanted to throw out there: how do ByteBuddy's ClassLoadingStrategies compare to what Cglib does?

Cheers,
Jonathan

Rafael Winterhalter

unread,
May 30, 2014, 5:06:53 PM5/30/14
to byte-...@googlegroups.com, jhalt...@gmail.com
Hi Jonathan,

as to the use of instance specific call backs: I know that cglib allows for some strange magic when instantiating new instances with predefined instance callbacks. Those callbacks are represented by instance fields which are pulled from thread-local, static fields on construction in order to emulate thread-safety. To be honest, it is one messy hack and I would not recommend its use it after I once looked through the source code. Cglib fully ignores Java's memory model in this context and you might end up with strange behavior. Rather, define instance fields and set these fields explicitly AFTER creating a new instance. One problem this approach cannot solve is the interception of calls from within constructors what is an issue cglib tries to resolve with this thread-local workaround. My thought was that if some one was creating "dead" proxies, constructor calls should not concern you as you should rather instantiate the proxies by using a library such as objenesis for silent construction. For anybody else, I will add logic for lazily creating an instance interceptor in a future release of Byte Buddy in order to address this issue with a cleaner solution than cglib.

If you want to repackage Byte Buddy, have a look at the "byte-buddy-dep" package. This package is a "raw delivery" of Byte Buddy without ASM repackaging. I am not sure if there are tools that filter all classes that are required for a given execution path but I assume that you could any instrumentation that you do not use and all of the "instrumentation.method.stack.bind" package, as well as most stack manipulations from the "instrumentation.method.stack" package. The heavies instrumentation is the method delegation which you apparently do not use. However, with Byte Buddy, you could keep your library's size down by simply not repackaging Byte Buddy at all but by referencing it and your user's can avoid application size if they have more than one dependency on the library. Yes, this will make your library draw in another one but in today's Spring/Hibernate world, 500 kilo byte for a utility library are not much. And as code generation does not work on Android, you will most likely not need to tackle low capacity. If you insist on repackaging, have a look at the packages but note that I unfortunately need to move a few classes with the 0.2 release. This will hopefully not need to be necessary for any future released but the Java 8 compatibility I am working on right now required some minor changes and size I still work with one digit download numbers I rather apply them by regrouping than dirty but compatible.

As for the class loading strategies: I hope my answer https://github.com/raphw/byte-buddy/issues/1 here solved your question.

Once again, thanks for being an early adopter! I looked at your use case and I like the idea a lot. I hand-coded this plenty times in production code.
Best regards, Rafael

Rafael Winterhalter

unread,
May 31, 2014, 10:58:34 AM5/31/14
to byte-...@googlegroups.com, rafae...@gmail.com, jhalt...@gmail.com
Another comment: You could compile Byte Buddy without including any debug information such as cglib released do it by default. This would further reduce the size of the dependency.

jhalt...@gmail.com

unread,
May 31, 2014, 1:42:35 PM5/31/14
to byte-...@googlegroups.com, rafae...@gmail.com, jhalt...@gmail.com
On Saturday, May 31, 2014 7:58:34 AM UTC-7, Rafael Winterhalter wrote:
> Another comment: You could compile Byte Buddy without including any debug information such as cglib released do it by default. This would further reduce the size of the dependency.

Awesome. Thanks for all of the responses!

Rafael Winterhalter

unread,
Jun 17, 2014, 7:23:39 AM6/17/14
to byte-...@googlegroups.com, rafae...@gmail.com, jhalt...@gmail.com
You are welcome. Byte Buddy version 0.2 is now released.
Reply all
Reply to author
Forward
0 new messages