Type information is only needed at compile time to generate the correct bytecode. Bytecode instructions (like assembly instructions) can typically act on only one datatype. Thus, the instruction used reflects the type of the operands. This is true for most C-family languages.
To see in action how the bytecode would differ when using a primitive and a dynamic allocation, let's take a simple example.
public static void main (String [] args) {
    int i = 0;
    int j = i + 1;
}
And the bytecode generated:
public static void main(java.lang.String[]);
  Code:
     0: iconst_0
     1: istore_1
     2: iload_1
     3: iconst_1
     4: iadd
     5: istore_2
     6: return
So we store and load the integers using istore and iload, and then we add them using iadd (i for integer).
Now take this example, using a dynamic memory allocation instead of a primitive:
public static void main (String [] args) {
    Integer i = new Integer(0);
    int j = i + 1;
}
And the bytecode:
public static void main(java.lang.String[]);
  Code:
     0: new           #2                  // class java/lang/Integer
     3: dup
     4: iconst_0
     5: invokespecial #3                  // Method java/lang/Integer."<init>":(I)V
     8: astore_1
     9: aload_1
    10: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
    13: iconst_1
    14: iadd
    15: istore_2
    16: return
In this version, we first have to invoke the intValue() method of the Integer object to retrieve the value, and then we can act on it via iadd.
And for evidence that datatypes need not be stored after compilation (since they are encoded in the instructions themselves, like istore for "integer store"), see the reference in jrahhali's answer.