开发者

Can "final" classes in Java be allocated differently in some JVMs?

开发者 https://www.devze.com 2023-03-28 15:15 出处:网络
Does anyone have any information on what optimisations (I realise this is implementation specific) most JVMs will do when confronted with a final object?In particular, an array of final objects in Jav

Does anyone have any information on what optimisations (I realise this is implementation specific) most JVMs will do when confronted with a final object? In particular, an array of final objects in Java? For example:

final class A { }
A myArray[] = new A[10];

If the class "A" is final then it can have no subclasses, so it seems like it would be possible to allocate (not calling constructors) the开发者_如何转开发 entire array (i.e. malloc(sizeof(A)*10)) and save on garbage collection/book keeping.


I severely doubt any JVM would bother to do that. In part because it would probably create extra book keeping. Any instance held both in the array and else where would have to dual modified to keep in line with the rules of the JVM.

eg.

final class A {
    String value = "default";
}

A instance = new A();
A[] array = new A[] {instance};
instance.value = "another value";
assert instance == array[0];
assert instance.value == array[0].value;

The implementation you suggest would force a copy of instance to be created when the array is created, thus forcing the JVM to remember to change the contents of value whenever it was changed in the other instance of A.


In response to your comment:

That's an interesting idea and would work to a certain extent. However, your idea will wreck garbage collector performance. The reason for this is that the best JVMs don't use reference counting. The best JVMs use a moving garbage collector. This technique traces from all root nodes (eg. Threads) to see what objects are referenced. All objects in the reference chain are moved into a contiguous block. Any memory outside of this block is considered free. No calls to dealloc or finalise or anything. This technique is VERY fast (in part due to the high "infant mortality" of objects in GCed languages). What's more is that this technique doesn't have to bother checking for circular references.

Back to the point: when the array drops out of scope the JVM will have to check if there are any other references to elements of the array and malloc new space for these objects before it can free up the memory of the array. This prevents the use of a "moving garbage collector" and we have to go back to inefficient GC techniques like reference counting. So whilst you idea seems good at first glance (and only for a certain edge case), it prevents other GC strategies that can be more widely applied and provide much greater efficiency savings.


The optimizations for final keyword that JVM (or compiler to more more accurate) does are different for different case.

  1. Generally a good coding practice is to declare all the method variables as file (unless you are changing them inside method). Now what compiler (not jvm) does is is to replace all the occurrence of that variable with its value (as its final, it wont change).

  2. For myArray[] = new A[10];, the array reference is final and not the array values.

  3. For final class A { }, its just a design constraint wherein the class is prevented from subclassing.

    So looking at these, it seems the optimizations for final are done by mostly by compiler and hence should be same across implementations.


When you create an array in Java. no element instances are created! A java instance is never a C-like struct which is copied or allocated for an array (the array contains references only). So there is no difference whether the element type is final or not.

After

A[] myArray = new A[10];

myArray[n] is always null! Afterwards you can assign the elements:

myArray[0] = new A();


I was reading up on final methods today. And it seems that declaring a class final is pretty much just short hand for declaring all methods final.

The advantage of declaring a method final is that it allows the compiler to avoid using a virtual table lookup when dispatching the method -- that is, it knows exactly which class the method is to be found in.

In Java methods are virtual by default, whereas in C++ the reverse is true. The only methods that are not virtual in Java are static methods and final methods.

The performance benefit from explicitly declaring a method final is debatable. A good JIT compiler will be able to analyse code and perform any optimisations that it deems useful. If class A does not currently have any loaded subclasses then the JIT compiler may decide to optimise any code paths that call the methods of an instance of A, if they are called frequently. This includes getting rid of vtable lookups or even inlining the method call.

eg. the following code path is a good candidate for optimisation if method is quick to execute.

public void run(A instance) {
    while (true) {
        instance.method();
    }
}

If a subclass of A that overrides method is subsequently loaded then the JIT compiler can invalidate any optimisations made with regard to the calling of method.

In essence a good JIT can give all the advantages of declaring something final, without actually having to declare anything final. And since we don't have to declare anything final we are free to develop a in fully object-orientated approach, without having to worry if a certain class or method is final. Therefore, final should only really be used for documentation purposes. The one exception this are fields of a class as it marks any computations with these fields as candidates for memoisation -- that is caching the result and using the cached result rather than performing the computation again.

0

精彩评论

暂无评论...
验证码 换一张
取 消