Static and instance methods in Java

on waitingforcode.com

Static and instance methods in Java

You're still doing Java/C#/JavaScript/Python/PHP... and need a wind of change? I was like that 4 years ago. I changed then to the data engineering field and it solved my existential problems :) If you want to follow my path, I prepared a course that will help you with that! Join the class!
Static methods in programming have some important roles. Mostly used as utility methods, they help to differentiate normal, business objects, from universal purely non contextual ones. They also gained a lot of recognition thanks to popular libraries, such as Google Guava. But there are another role useful in the case of static methods in Java ?

In this article we'll focus on answering to this question by analyzing memory usage. At the beginning we'll shortly explain the difference between static and instance methods. After that we'll focus on where static methods are stored in JVM and how they are represented. The last part will be about a simple benchmark comparing memory state with use of static methods to do some factory operations and with use of simple objects.

Static methods and instance methods

Static methods belong to classes. It means that they are independent on instance of created objects. It also means that they are created when the class is loaded. In the other side, instance methods can be only created after concrete object initialization. Thanks to this information we can also deduce that they can depend on some of object's internal properties. It's not the case of static methods and it's the reason why some patterns as static factory method or fluent interfaces were commonly adopted.

To see this difference clearly, we can try to invoke instance and static methods through reflection API:

public class StaticInstanceMethodsTest {

  @Test
  public void invoke_static_with_reflection() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    Class<Animal> animalClass = Animal.class;
    Method isBreathingMethod = animalClass.getMethod("isBreathing");

    assertThat((boolean) isBreathingMethod.invoke(null)).isTrue();
  }

  @Test
  public void invoke_instance_method_with_reflection() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    Class animalClass = Animal.class;
    Method isAliveMethod = animalClass.getMethod("isAlive");

    assertThat((boolean) isAliveMethod.invoke(new Animal())).isTrue();
  }

  private static class Animal {
    public static boolean isBreathing() {
      return true;
    }

    public boolean isAlive() {
      return true;
    }
  }

}

As you can see, static method belongs to class and doesn't expect specific object to be passed in invoke method.

Static methods in JVM

Static methods influence on JVM's performance is very often confused with the influence of constants. As you know, in Java constants are defined as static final objects or enum instances as well (singleton pattern). Thanks to it we are sure that there are only one object in the memory. However, this optimization doesn't really apply to static methods. To understand that, we need first to investigate how methods are stored in JVM.

For each method, static and instance, there are only one declaration in the memory (for Java 8 in metaspace more precisely). So making methods static doesn't produce a big memory place optimization. Why both method types behave in the same manner under-the-hood ? It's thanks to difference on invocation level. Instance methods are called with 2 groups of parameters: reference to calling object (this) and methods parameters declared in signature. As you can deduce, static methods expects only the 2nd group because they don't depend on another objects.

So we can tell now that the main difference between the use of static and instance methods should be related to programming design. In fact, static methods suggest intuitively that they don't depend on particular object state. Thanks to it, they can be refactored and used in multi-threading environment more easily.

Static vs Instance methods size benchmark

Our test case is simple. We'll take one class and write it in two forms: using static method and using instance method. After we'll launch it and observe the changes made in memory after 500 calls. We'll also dump executed code and compare dumps manually with jvisualvm:

public class StaticInstanceMethodsBenchmarkTest {

  @Test
  public void compare_static_and_instance_methods() throws InterruptedException, IOException {
    Runtime runtime = Runtime.getRuntime();
    MemoryStats beforeStaticStats = MemoryStats.valueOf(runtime);
    int limit = 500_000;
    for (int i = 0; i < limit; i++) {
      AnimalStatic.isBreathing();
    }
    MemoryStats afterStaticStats = MemoryStats.valueOf(runtime);
    System.out.println("S>before: "+beforeStaticStats);
    System.out.println("S>after: "+afterStaticStats);

    dumpHeap("static_call.dump");

    MemoryStats beforeInstanceStats = MemoryStats.valueOf(runtime);
    for (int i = 0; i < limit; i++) {
      new AnimalInstance().isBreathing();
    }
    MemoryStats afterInstanceStats = MemoryStats.valueOf(runtime);
    System.out.println("I>before: "+beforeInstanceStats);
    System.out.println("I>after: "+afterInstanceStats);

    dumpHeap("instance_call.dump");

    /**
      * It proves that static method invocation, when there are no state depending on object,
      * helps to save memory occupancy. But it's only because of the necessity of new
      * objects creation. Normally there are still one method in the memory.
      */
    long usedMemoryStatic = beforeStaticStats.freeMemory - beforeStaticStats.freeMemory;
    long usedMemoryInstance = beforeInstanceStats.freeMemory = afterInstanceStats.freeMemory;
    assertThat(usedMemoryInstance).isGreaterThan(usedMemoryStatic);
  }

  private void dumpHeap(String fileName) throws IOException, InterruptedException {
    String name = ManagementFactory.getRuntimeMXBean().getName();
    String pid = name.substring(0, name.indexOf("@"));

    String[] cmd = { "jmap", "-dump:file=/home/konieczny/doc/txt/files/about_static_instance_methods/"+fileName, pid };
    Process process = Runtime.getRuntime().exec(cmd);
    process.waitFor(5, TimeUnit.SECONDS);
    assertThat(process.exitValue()).isEqualTo(0);
  }

  private static class AnimalStatic {
    public static boolean isBreathing() {
      return true;
    }
  }

  private static class AnimalInstance {
    public boolean isBreathing() {
      return true;
    }
  }

  private static class MemoryStats {

    private long totalMemory;
    private long freeMemory;
    private long maxMemory;

    public static MemoryStats valueOf(Runtime runtime) {
      MemoryStats memoryStats = new MemoryStats();
      memoryStats.freeMemory = runtime.freeMemory();
      memoryStats.totalMemory = runtime.totalMemory();
      memoryStats.maxMemory = runtime.maxMemory();
      return memoryStats;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("totalMemory", totalMemory).add("freeMemory", freeMemory)
                .add("maxMemory", maxMemory).toString();
    }
  }

}

The test should pass and some output should be generated:

S>before: MemoryStats{totalMemory=126877696, freeMemory=120166400, maxMemory=1854930944}
S>after: MemoryStats{totalMemory=126877696, freeMemory=120166400, maxMemory=1854930944}
I>before: MemoryStats{totalMemory=126877696, freeMemory=115448488, maxMemory=1854930944}
I>after: MemoryStats{totalMemory=126877696, freeMemory=115448488, maxMemory=1854930944}

The difference of free memory after static and instance methods executions, is mostly provoked by the fact of creating 500 instances of AnimalInstance object. We can see that on generated dumps opened with jvisualvm:

To exclude definitively the hypothesis about instance methods influencing metaspace size, we'll divide our test case on 2 and launch one after another with following argument responsible for printing GC details -XX:+PrintGCDetails. The division is made on loops and the results are:

  • for static method check
    int limit = 500_000;
    for (int i = 0; i < limit; i++) {
      AnimalStatic.isBreathing();
    }
    

    Generated output was:

    Heap
     PSYoungGen      total 37888K, used 9328K [0x00000000d6900000, 0x00000000d9300000, 0x0000000100000000)
      eden space 32768K, 28% used [0x00000000d6900000,0x00000000d721c170,0x00000000d8900000)
      from space 5120K, 0% used [0x00000000d8e00000,0x00000000d8e00000,0x00000000d9300000)
      to   space 5120K, 0% used [0x00000000d8900000,0x00000000d8900000,0x00000000d8e00000)
     ParOldGen       total 86016K, used 0K [0x0000000083a00000, 0x0000000088e00000, 0x00000000d6900000)
      object space 86016K, 0% used [0x0000000083a00000,0x0000000083a00000,0x0000000088e00000)
     Metaspace       used 4544K, capacity 5164K, committed 5248K, reserved 1056768K
      class space    used 535K, capacity 596K, committed 640K, reserved 1048576K
    
  • for instance method check
    int limit = 500_000;
    for (int i = 0; i < limit; i++) {
      new AnimalInstance().isBreathing();
    }
    

    Generated output was:

    Heap
     PSYoungGen      total 37888K, used 9974K [0x00000000d6900000, 0x00000000d9300000, 0x0000000100000000)
      eden space 32768K, 30% used [0x00000000d6900000,0x00000000d72bdbf0,0x00000000d8900000)
      from space 5120K, 0% used [0x00000000d8e00000,0x00000000d8e00000,0x00000000d9300000)
      to   space 5120K, 0% used [0x00000000d8900000,0x00000000d8900000,0x00000000d8e00000)
     ParOldGen       total 86016K, used 0K [0x0000000083a00000, 0x0000000088e00000, 0x00000000d6900000)
      object space 86016K, 0% used [0x0000000083a00000,0x0000000083a00000,0x0000000088e00000)
     Metaspace       used 4540K, capacity 5164K, committed 5248K, reserved 1056768K
      class space    used 535K, capacity 596K, committed 640K, reserved 1048576K
    
    

As expected, metaspace size is the same for both, instance and static methods. The only difference is in young generation where instance method takes a little bit more place than static.

To resume this article in few words, we can tell that static methods has more design meaning than optimization one. Unlike another static data such as classes fields, they don't help to keep more metaspace free. However, they can have a little influence on memory used by application. We observed that in the example of free memory available after running the same method code as static and instance method. And the difference was provoked by the necessity of create new objects. And the use of instance method in this case was not appropriated because method's result didn't depend on object's state.

Share on: