Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do unused local variables in a method acquire memory in JVM?

Tags:

java

I came across this post in SO Do uninitialized primitive instance variables use memory?

It states "In Java, does it cost memory to declare a class level instance variable without initializing it? For example: Does int i; use any memory if I don't initialize it with i = 5;?"

My question is what in case of local variables, say i have a method foo()

public int foo(){

  int x;

//Write code which does not use/initialize x
}

Will the local variable x occupy memory?

Edit

Jon's Answer is

UPDATE: Doing a bit more research on this, I find this page which suggests to me that, although the compiled bytecode implies that space is allocated for x, it may indeed be optimized away by the jvm. Unfortunately, I find no complete description of the optimizations performed. Particularly, the JVM documentation chapter on compiling does not mention removing unused variables from the stack. So, barring further discoveries, my answer would be that it's implementation-dependent, but it seems like the sort of optimization that any self-respecting compiler would perform. Notice too that it doesn't matter that much that this is a local variable rather than a field - in fact, local variables are the ones most likely to be optimized away, since they are the easiest to analyze and eliminate. (precisely because they are local)

Let us see if can find more evidences which supports this.

like image 890
Abhishek Singh Avatar asked Oct 29 '14 14:10

Abhishek Singh


4 Answers

Class level / Instance level variables will be initialized to their default values automatically. So, yes, they will occupy some space when a class is initialized / instance created respectively.

As far as method local variables are concerned, No, if they are just declared but not initialized, then they will not use any space, they are as good as ignored by the compiler..

If your code was this :

public static void main(String[] args) {
    int i;  // ignored
    int j = 5;
    String s = "abc";
    String sNull; // ignored
}

Byte code :

  LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  args   [Ljava/lang/String;
            2       4     2     j   I
            5       1     3     s   Ljava/lang/String;
   }
like image 183
TheLostMind Avatar answered Nov 20 '22 03:11

TheLostMind


This is the kind of question that's worth examining with javap.

public class Foo
{
public int bar(){

  System.out.println("foo");
    return 8;
  }
public int foo(){

  int x;
  System.out.println("foo");
    return 8;
  }
}

Notice that the difference between foo() and bar() is that one declares a local variable x and the other does not.

Now look at the jvm code (use javap -v Foo to see this on your machine)

  public int bar();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String foo
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: bipush        8
        10: ireturn
      LineNumberTable:
        line 6: 0
        line 7: 8

  public int foo();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String foo
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: bipush        8
        10: ireturn
      LineNumberTable:
        line 12: 0
        line 13: 8
}

The interesting thing is that the line-by-line output is identical, but the locals for bar is 1, and for foo it's 2. So it looks like space is indeed allocated for x, even though the compiler output doesn't ever use it.

UPDATE: Doing a bit more research on this, I find this page which suggests to me that, although the compiled bytecode implies that space is allocated for x, it may indeed be optimized away by the jvm. Unfortunately, I find no complete description of the optimizations performed. Particularly, the JVM documentation chapter on compiling does not mention removing unused variables from the stack. So, barring further discoveries, my answer would be that it's implementation-dependent, but it seems like the sort of optimization that any self-respecting compiler would perform. Notice too that it doesn't matter that much that this is a local variable rather than a field - in fact, local variables are the ones most likely to be optimized away, since they are the easiest to analyze and eliminate. (precisely because they are local)

like image 22
Jon Kiparsky Avatar answered Nov 20 '22 03:11

Jon Kiparsky


Expanding slightly on the testcase from @JonKiparsky:

public class StackSlotTest {
    public int bar(){
        int a;
        a = 5;
        System.out.println("foo");
        return a;
    }

    public int foo(){
        int x;
        int a;
        a = 5;
        System.out.println("foo");
        return a;
      }
}

I added the variable a to both methods, and added a set and use of it in both.

  public int bar();
    Code:
       0: iconst_5
       1: istore_1
       2: getstatic     #2                  // Field java/lang/System.out:Ljava/
io/PrintStream;
       5: ldc           #3                  // String foo
       7: invokevirtual #4                  // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
      10: iload_1
      11: ireturn

Above you see that the iload_1 bytecode loads the value of a to be returned. The second stack slot is referenced. (The first is the this pointer.)

  public int foo();
    Code:
       0: iconst_5
       1: istore_2
       2: getstatic     #2                  // Field java/lang/System.out:Ljava/
io/PrintStream;
       5: ldc           #3                  // String foo
       7: invokevirtual #4                  // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
      10: iload_2
      11: ireturn

In this case the value of a is loaded with iload_2, to access the third slot, because the second slot is occupied (sort of) by the (totally unused) variable x.

like image 5
Hot Licks Avatar answered Nov 20 '22 01:11

Hot Licks


The way a primitive variable is stored in a local variable (in bytecode) is with:

istore(for int values), dstore(for double values), fstore(for float values) and so on..

Each of them pop the top of the operator stack and write it into local variable numbered Var. So, an operator needs to push the value that need to be stored, on the top of the operator stack, just before the store operation. They works in pair.

So, if you do something like:

int i;
//...
i = 12;
//...

Your compiler will push the integer 12 in the top of the operator stack and then pop this value and write it in a local variable only at the variable initialization time.

//...
bipush 12
istore_1 1

If you never initialize your variable, the compiler cannot push a value at the top of the operator stack so the store operation is impossible. it's not a compiler optimization, it's just the way bytecode works. So, your compiler just removes your line.

Take this simple example:

public static void main(String[] args) {
    int i;
    int j = 10;
    System.out.println(j);
    i = 12;
    System.out.println(i);
}

And look when the local variable i is initialized:

public static void main(String[] p0) {
    bipush 10
    istore_2 2
    getstatic PrintStream System.out
    iload_2 2
    invokevirtual void PrintStream.println(int)
    bipush 12
    istore_1 1
    getstatic PrintStream System.out
    iload_1 1
    invokevirtual void PrintStream.println(int)
    return
}

You may notice that the local variable # 2 is used before the local variable # 1.

Take this last example:

public static void main(String[] args) {
    int i;
    int j = 10;
    System.out.println(j);
}

The bytecode would be:

public static void main(String[] p0) {
    bipush 10
    istore_2 2
    getstatic PrintStream System.out
    iload_2 2
    invokevirtual void PrintStream.println(int)
    return
}

The local variable 1 is never used and something is stored in the local variable 2. Because of that, 2 x 32 bits would be allocated here even if the local variable 1 is not used.

edit: (from Hot Licks comment)

The JVM interpreter will allocate as many local slots as are specified in the method header. They are allocated whether they are used or not. Long and double values get two slots (even though the slots are 64 bits and only one is used in most modern implementations).

A non-initialized int local variable would required 32 bits here, for example.

like image 3
Pier-Alexandre Bouchard Avatar answered Nov 20 '22 01:11

Pier-Alexandre Bouchard