18

Is there any way to create class that extends ByteBuffer class?

Some abstract methods from ByteBuffer are package private, and if I create package java.nio, security exception is thrown.

I would want to do that for performance reasons - getInt for example has about 10 method invocations, as well as quite a few if's. Even if all checks are left, and only method calls are inlined and big/small endian checks are removed, tests that I've created show that it can be about 4 times faster.

Jonas
  • 121,568
  • 97
  • 310
  • 388
Sarmun
  • 2,378
  • 2
  • 22
  • 24
  • 1
    My top tips for NIO buffer performance: Use -server, make your methods small (so that they can be inlined *into*) and sometimes it's better to switch to byte[]. – Tom Hawtin - tackline Mar 09 '09 at 11:35
  • [Similar question](http://www.thatsjava.com/java-tech/26034/) on thatsjava.com – finnw May 27 '11 at 09:51
  • +50 bounty for a way to circumvent the access restriction (tt cannot be done using reflection alone. Maybe there is a way using `sun.misc.Unsafe` etc.?) – finnw May 27 '11 at 09:53
  • Related: http://stackoverflow.com/questions/462094/in-java-how-do-i-make-a-class-with-a-private-constructor-whose-superclass-also-h and http://stackoverflow.com/questions/4682659/accessing-constructor-from-abstract-base-class-with-reflection – finnw May 27 '11 at 10:10
  • @finnw, which access restricton are you talking about? If you are talking about a plain system without a security manager all you need to do is get the private field `theUnsafe` – Peter Lawrey May 30 '11 at 14:54
  • @Peter Lawrey, the package-private access of the `ByteBuffer` constructor. I know how to get `theUnsafe`, but not how to use it (or something else) to create a subclass of `ByteBuffer`. – finnw May 30 '11 at 17:09
  • @finnw, I wouldn't create a sub-class, instead create a wrapper which can expose the underlying ByteBuffer as required. i.e. only for NIO operations. This means you are only incurring the ByteBuffer overhead once per read/write which is a small price to pay. For every other access, use the wrapper. – Peter Lawrey May 30 '11 at 17:25
  • @Peter Lawrey, I cannot do that because I do not have direct access to the array, nor can I replace it. It is wrapped by another API which it is undesirable to modify. – finnw May 31 '11 at 08:32
  • @finnw, are you saying the byte[] is provided for you? and it has to be a ByteBuffer. The problem you will have is much of the optimisation you can make will be changing the way you call this buffer. e.g. periodical bounds checks instead of a check on every access and complex get operations like parsing a double from text. I don't think you will see much improvement if you have to support the ByteBuffer as it behaves currently. – Peter Lawrey May 31 '11 at 09:05
  • @Peter Lawrey, my motivation is different from the OP's. He was doing it for performance, I am doing it for interoperability. – finnw May 31 '11 at 09:50
  • @finnw, If you want interoperability rather than performance, why don't use just use the built in ByteBuffer. Can you to tell me again, what you need which the built in ByteBuffer doesn't do already? – Peter Lawrey May 31 '11 at 09:54
  • @finnw, what the motivation for the subclass, you need to add extra fields? You can use `HashMap` w/ WeakReference for key. WeakReference must be subclassed, though. You want to handle `put/get`, you can always have a duplicate of the buffer w/ exactly the same backing data (either native memory or plain byte[]). In any case, there is something you miss. W/ _very_ extensive use of NIO for years I have never needed to extend ByteBuffer. – bestsss Jun 01 '11 at 13:51
  • @finnw, note2, if you still need to modify the byte[] reference, you can use `Field.setAccessible(true)`. The field you need is: `ByteBuffer.class.getDeclaredField("hb")`, you may need to change `offset` as well. Effectively you get: ByteBuffer.setArray(byte[]). I dont recommend the approach but it's quite straightforward. – bestsss Jun 01 '11 at 13:58
  • @Peter Lawrey and @bestsss, I am not trying to modify the behavior of a `ByteBuffer`, I am trying to take a similar but incompatible class and present it to another API as a `ByteBuffer`. – finnw Jun 01 '11 at 20:05
  • @finnw, but without knowing what similar but not the same, means its hard to suggest alternatives. The only general approach is to create a class which is in the same package as ByteBuffer. – Peter Lawrey Jun 01 '11 at 20:10
  • @finnw, so if I understand correctly, some 3rd party lib needs ByteBuffer but you have your own class. The lib will just use vanilla ByteBuffer and be happy with? If so keep the relation in a similar Map to what I suggested (just do not use ByteBuffer as key, as it's mutable). Then in your own class you'd get a field `ByteBuffer buffer` and you have bi-directional relationship. ByteBuffer->your class through the Map, your class->`buffer field`. You can always obtain both from any object. There other ways to manage relation, incl using the last 4bytes for index in an array (+slice the buf) – bestsss Jun 01 '11 at 21:29
  • @finnw, also added info in my answer how to declare a class bypassing the verifier, you can have fun w/ but it's truly an awkward solution, if you go for. – bestsss Jun 01 '11 at 21:41

7 Answers7

15

You cant extend ByteBuffer and thanks God for.

You cant extend b/c there are no protected c-tors. Why thank god part? Well, having only 2 real subclasses ensures that the JVM can Heavily optimizes any code involving ByteBuffer.

Last, if you need to extend the class for real, edit the byte code, and just add protected attribute the c-tor and public attribute to DirectByteBuffer (and DirectByteBufferR). Extending the HeapBuffer serves no purposes whatsoever since you can access the underlying array anyways

use -Xbootclasspath/p and add your own classes there, extend in the package you need (outside java.nio). That's how it's done.

Another way is using sun.misc.Unsafe and do whatever you need w/ direct access to the memory after address().

I would want to do that for performance reasons - getInt for example has about 10 method invocations, as well as quite a few if's. Even if all checks are left, and only method calls are inlined and big/small endian checks are removed, tests that I've created show that it can be about 4 times faster.

Now the good part, use gdb and check the truly generated machine code, you'd be surprised how many checks would be removed.

I can't imagine why a person would want to extend the classes. They exist to allow good performance not just OO polymorph execution.


edit:

How to declare any class and bypass Java verifier

On Unsafe: Unsafe has 2 methods that bypass the verifier and if you have a class that extends ByteBuffer you can just call any of them. You need some hacked version (but that's super easy) of ByteBuffer w/ public access and protected c-tor just for the compiler. The methods are below. You can use 'em on your own risk. After you declare the class like that you can even use it w/ new keyword (provided there is a suitable c-tor)

public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);    
public native Class defineClass(String name, byte[] b, int off, int len);
bestsss
  • 11,796
  • 3
  • 53
  • 63
  • You said "I can't imagine why a person would want to extend the classes": it's because sometimes you need to pass a `ByteBuffer` to a fixed API (an API that can only take a `ByteBuffer`), but you need to be able to make the `ByteBuffer` behave in custom ways (e.g. by implementing `Closeable` and then freeing up some underlying resource when `close()` is called, or maybe you need to back a `ByteBuffer` with a custom datastore.). I have come across the need to do this multiple times before. – Luke Hutchison Nov 20 '21 at 22:02
10

You can disregard protection levels by using reflection, but that kinda defeats the performance goal in a big way.

You can NOT create a class in the java.nio package - doing so (and distributing the result in any way) violates Sun's Java license and could theoretically get you into legal troubles.

I don't think there's a way to do what you want to do without going native - but I also suspect that you're succumbing to the temptation of premature optimization. Assuming that your tests are correct (which microbenchmarks are often not): are you really sure that access to ByteBuffer is going to be the performance bottleneck in your actual application? It's kinda irrelevant whether ByteBuffer.get() could be 4 times faster when your app only spends 5% of its time there and 95% processing the data it's fetched.

Wanting to bypass all checks for the sake of (possibly purely theoretical) performance does not sound a good idea. The cardinal rule of performance tuning is "First make it work correctly, THEN make it work faster".

Edit: If, as stated in the comments, the app actually does spend 20-40% of its time in the ByteBuffer methods and the tests are correct, that means a speedup potential of 15-30% - significant, but IMO not worth starting to use JNI or messing with the API source. I'd try to exhaust all other options first:

  • Are you using the -server VM?
  • Could the app be modified to make fewer calls to ByteBuffer rather than trying to speed up those it does make?
  • Use a profiler to see where the calls are coming from - perhaps some are outright unnecessary
  • Maybe the algorithm can be modified, or you can use some sort of caching
Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
  • I don't think reflection would help here (I was going to say the same thing)... I think what he wants to do is to actually change the method to avoid the check. But perhaps I read it wrong... but regardless reflection will negate any speed improvements. – TofuBeer Mar 08 '09 at 23:13
  • I would like either to extends HeapByteBuffer, and change few methods, or extend ByteBuffer and write all the methods. And my program is very heavily using it, about 20-40% is going to ByteBuffer methods, so if it can be done, it will improve performance significantly. – Sarmun Mar 08 '09 at 23:28
  • I happen to be playing around with ByteBuffers myself right now... I hit a performance thing (went from 4 seconds to 22 seconds, and now back to 4 seconds). Have you used a profiler to be sure of where the slowdown is (in my case I was making unneeded copies of the buffer). – TofuBeer Mar 08 '09 at 23:54
  • @TofuBeer I believe reflection actually could be used as intended, to bypass rather than change the methods in question - but yeah, totally pointless when you motivation is performance. – Michael Borgwardt Mar 09 '09 at 00:15
  • I am using -server VM. Is there a way to inline method calls more aggressively? (because if all methods 3-4 level deep are inlined there, it would make significant improvement - I don't think vm is doing that currently, but don't know why) I also think that it's not worth messing with JNI. – Sarmun Mar 09 '09 at 01:15
  • Best solution that currently comes to my mind, is to create MyByteBuffer (with same interface), and all classes in project to use that one, and to use ByteBuffer's array() to construct it and vice-versa. – Sarmun Mar 09 '09 at 01:22
  • You can redistribute modified boot classes under the appropriate GPL or JRL (Java Research Licence). I am not a lawyer. I do not speak for Sun. Etc. – Tom Hawtin - tackline Mar 09 '09 at 11:33
  • You most definitely cannot. Look at the LICENSE file that comes with your JDK, at clause D of the supplemental license terms (as of JDK 6). – Michael Borgwardt Mar 09 '09 at 12:34
  • did you allocate the bytebuffers as direct via the allocateDirect method? – TofuBeer Mar 09 '09 at 16:55
  • the openjdk you can modify it... but cannot call it Java (it is GPL, you can fork). The research license, IIRC, lets you make mods avaialble to other research licensees - neither is a real solution, but both are legally allowed (I am not a lawyer!) – TofuBeer Mar 09 '09 at 16:57
  • @Michael, this is an old post and I excuse to bother, yet the time measured by a profiler in ByteBuffer could be just incorrect. Profiling in java for tight loops can be quite misleading and depends where there the hotspot has put the safe point, when used sampling. When used hooks to modify the code then it's even worse since it messes up the inlining quite badly. – bestsss May 29 '11 at 16:59
  • @bestsss, I thought that the profiler was inflating the cost of ByteBuffer, but once I started comparing using Unsafe directly, it made a measurable difference. – Peter Lawrey May 30 '11 at 10:26
  • @Peter, that's the right way to do it, full test of ByteBuffer vs Unsafe. Still a bit surprised the difference can be above 10% – bestsss May 30 '11 at 11:57
2

ByteBuffer is abstract so, yes, you can extend it... but I think what you want to do is extend the class that is actually instantiated which you likely cannot. It could also be that the particular one that gets instantiated overrides that method to be more efficient than the one in ByteBuffer.

I would also say that you are likely wrong in general about all of that being needed - perhaps it isn't for what you are testing, but likely the code is there for a reason (perhaps on other platforms).

If you do believe that you are correct on it open a bug and see what they have to say.

If you want to add to the nio package you might try setting the boot classpath when you call Java. It should let you put your classes in before the rt.jar ones. Type java -X to see how to do that, you want the -Xbootclasspath/p switch.

TofuBeer
  • 60,850
  • 18
  • 118
  • 163
  • 3
    ByteBuffer has package private abstract _set and _get methods, so you couldn't override it. And also all the constructors are package private, so you cannot call them. – Sarmun Mar 08 '09 at 23:21
  • You can call them via reflection (get the method and call setAccessible(true) on it), but that will be slow. You should be able to add a class via the bootclasspath, but as was pointed out you cannot ship it. – TofuBeer Mar 08 '09 at 23:52
  • 3
    "you can extend it" doesn't to me say "you can extend it if you put your subclass on the bootclasspath". If you mess with the boots then you can do what you want. Very close to a -1 from me... – Tom Hawtin - tackline Mar 09 '09 at 11:30
  • If he wants to do proper testing that is what he has to do. Note I said file a bug against Sun about it. In context it is a valid thing to do. – TofuBeer Mar 09 '09 at 14:50
1

+50 bounty for a way to circumvent the access restriction (tt cannot be done using reflection alone. Maybe there is a way using sun.misc.Unsafe etc.?)

Answer is: there is no way to circumvent all access restrictions in Java.

  • sun.misc.Unsafe works under the authority of security managers, so it won't help
  • Like Sarnum said:

ByteBuffer has package private abstract _set and _get methods, so you couldn't override it. And also all the constructors are package private, so you cannot call them.

  • Reflection allows you to bypass a lot of stuff, but only if the security manager allows it. There are many situations where you have no control on the security manager, it is imposed on you. If your code were to rely on fiddling with security managers, it would not be 'portable' or executable in all circumstances, so to speak.

The bottom line of the question is that trying to override byte buffer is not going to solve the issue.

There is no other option than implementing a class yourself, with the methods you need. Making methods final were you can will help the compiler in its effort to perform optimizations (reduce the need to generate code for runtime polymorphism & inlining).

Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453
  • @finnw There is no way sun.misc.Unsafe or anything else can help circumventing the access restriction. – Jérôme Verstrynge May 27 '11 at 20:14
  • @JVersty, Unsafe is rightly named, but there is always a way to circumvent access control unless you have a SecurityManager/Access control to explicitly prevent it. Even then, if you have access to starting the JVM you can do anything you wish. – Peter Lawrey May 29 '11 at 13:33
  • @Peter Lawrey We are saying the same thing. Unsafe is not key in circumventing access or not. The security manager is. – Jérôme Verstrynge May 29 '11 at 15:41
  • what Peter says is exactly true, if you start the VM you can do whatever you like. If the code is run in a tight security box, well there must be a reason for and most likely the overhead of the security checks would outweight any improvements introduced by Unsafe. – bestsss May 29 '11 at 16:56
1

The simplest way to get the Unsafe instances is via reflection. However if reflection is not available to you, you can create another instance. You can do this via JNI.

I tried in byte code, to create an instance WITHOUT calling a constructor, allowing you create an instance of an object with no accessible constructors. However, this id not work as I got a VerifyError for the byte code. The object has to have had a constructor called on it.


What I do is have a ParseBuffer which wraps a direct ByteBuffer. I use reflection to obtain the Unsafe reference and the address. To avoid running off the end of the buffer and killing the JVM, I allocate more pages than I need and as long as they are not touched no physical memory will be allocated to the application. This means I have far less bounds checks and only check at key points.

Using the debug version of the OpenJDK, you can see the Unsafe get/put methods turn into a single machine code instruction. However, this is not available in all JVM and may not get the same improvement on all platforms.

Using this approach I would say you can get about a 40% reduction in timings but comes at a risk which normal Java code does not have i.e. you can kill the JVM. The usecase I have is an object creation free XML parser and processor of the data contained using Unsafe compared with using a plain direct ByteBuffer. One of the tricks I use in the XML parser is to getShort() and getInt() to examine multiple bytes at once rather than examining each byte one at a time.

Using reflection to the the Unsafe class is an overhead you incurr once. Once you have the Unsafe instance, there is no overhead.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
1

A Java Agent could modify ByteBuffer's bytecode and change the constructor's access modifier. Of course you'd need to install the agent at the JVM, and you still have to compile get your subclass to compile. If you're considering such optimizations then you must be up for it!

I've never attempted such low level manipulation. Hopefully ByteBuffer is not needed by the JVM before your agent can hook into it.

Peter Davis
  • 761
  • 7
  • 13
  • I'll add that [JMockit](http://jmockit.googlecode.com/svn/trunk/www/tutorial/AnExample.html#expectations) uses the Java Agent approach and can instrument private constructors. Not sure what black magic it uses. – Peter Davis Jun 01 '11 at 06:14
  • why would do it one the fly? you still need some bytecode enabled version to allow normal compilation [cheating the compiler is also an option, of course, but a difficult one; or you can manually create the class file]. Yet, if you have the modified class version and you can install any instrumentation, you can just alter the bootstrap path as easily. – bestsss Jun 01 '11 at 13:45
-1

I am answering the question you WANT the answer to, not the one you asked. Your real question is "how can I make this go faster?" and the answer is "handle the integers an array at a time, and not singly."

If the bottleneck is truly the ByteBuffer.getInt() or ByteBuffer.getInt(location), then you do not need to extend the class, you can use the pre-existing IntBuffer class to grab data in bulk for more efficient processing.

int totalLength = numberOfIntsInBuffer;
ByteBuffer myBuffer = whateverMyBufferIsCalled;
int[] block = new int[1024];
IntBuffer intBuff = myBuffer.asIntBuffer();
int partialLength = totalLength/1024;

//Handle big blocks of 1024 ints at a time
try{
  for (int i = 0; i < partialLength; i++) {
     intBuff.get(block);
     // Do processing on ints, w00t!
  }

  partialLength = totalLength % 1024; //modulo to get remainder
  if (partialLength > 0) {
    intBuff.get(block,0,partialLength);
    //Do final processing on ints
  }
} catch BufferUnderFlowException bufo {
   //well, dang!
}

This is MUCH, MUCH faster than getting an int at a time. Iterating over the int[] array, which has set and known-good bounds, will also let your code JIT much tighter by eliminating bounds checks and the exceptions ByteBuffer can throw.

If you need further performance, you can tweak the code, or roll your own size-optimized byte[] to int[] conversion code. I was able to get some performance improvement using that in place of the IntBuffer methods with partial loop unrolling... but it's not suggested by any means.

BobMcGee
  • 19,824
  • 10
  • 45
  • 57
  • That would work if the buffer contains only int's. But in my case buffer can contain different types, without large consecutive chunks containing single type, this will not help – Sarmun Jun 25 '11 at 05:17
  • Then work from a giant byte[] and roll your own equivalent to the int/byte conversion methods. Less safety checks, less conditions, and less exception handling and it may beat the ByteBuffer methods. It's almost always faster to operate on primitives directly when possible, because loops can be optimized more tightloy. – BobMcGee Jun 25 '11 at 05:32