I need to store a ton of RGB color objects. These are taking up between 8% & 12% of the total memory of my app for some common usages. I presently have it defined as follows:
class MyColor {
byte red;
byte green;
byte blue;
}
I assume that (most) JVMs actually use an int for each of those entries. The easiest alternative is:
class MyColor {
byte [] color = new byte[3];
private static final int red = 0;
private static final int green = 1;
private static final int blue = 2;
}
Will that put the entire array in a single int? Or is it an int[3] under the covers? If the first, this is great. If the second, then the best is:
class MyColor {
int color;
private static final int red_shift = 0;
private static final int green_shift = 8;
private static final int blue_shift = 16;
}
Or is there a better approach?
Update: I will also have a getRed(), setRed(int), ... as the accessors. I just listed the data components of the class to keep it smaller. And size is the critical issue here. The code doesn't spend a lot of time accessing these values so performance is not a big issue.
Update 2: I went and ran this using SizeofUtil (referenced below - thank you). I did this using code as follows:
protected int create() {
MyColor[] aa = new MyColor[100000];
for (int ind=0; ind<100000; ind++)
aa[ind] = new MyColor2();
return 2;
}
}.averageBytes());
And here's where it gets weird. First, if I don't do the for loop, so it only is creating the array (with all values null), then it reports 400016 bytes or 4 bytes/array element. I'm on a 64-bit system so I'm surprised this isn't 800000 (does Java have a 32-bit address space on a 64-bit O/S?).
But then came the weird part. The total numbers with the for loop are:
- 2800016.0
- 2600008.0
- 2800016.0
First surprise, the 2nd approach with byte[3] uses less memory! Is it possible that the JVM, seeing the byte[3] in the declaration, just allocates it inline?
Second, the memory per object is (2,800,000 - 400,000) / 100,000 = 24. I'll buy that for the first approach where each byte is made a native 64-bit int. 3 * 8 bytes = 24 bytes. But for the third case where it's a single int? That makes no sense.
Code here in case I missed something:
package net.windward;
import java.util.Arrays;
public class TestSize {
public static void main(String[] args) {
new TestSize().runIt();
}
public void runIt() {
System.out.println("The average memory used by MyColor1 is " + new SizeofUtil() {
protected int create() {
MyColor1[] aa = new MyColor1[100000];
for (int ind = 0; ind < 100000; ind++)
aa[ind] = new MyColor1();
return 1;
}
}.averageBytes());
System.out.println("The average memory used by MyColor2 is " + new SizeofUtil() {
protected int create() {
MyColor2[] aa = new MyColor2[100000];
for (int ind = 0; ind < 100000; ind++)
aa[ind] = new MyColor2();
return 2;
}
}.averageBytes());
System.out.println("The average memory used by MyColor3 is " + new SizeofUtil() {
protected int create() {
MyColor3[] aa = new MyColor3[100000];
for (int ind = 0; ind < 100000; ind++)
aa[ind] = new MyColor3();
return 1;
}
}.averageBytes());
System.out.println("The average memory used by Integer[] is " + new SizeofUtil() {
protected int create() {
Integer[] aa = new Integer [100000];
for (int ind = 0; ind < 100000; ind++)
aa[ind] = new Integer(ind);
return 1;
}
}.averageBytes());
}
public abstract class SizeofUtil {
public double averageBytes() {
int runs = runs();
double[] sizes = new double[runs];
int retries = runs / 2;
final Runtime runtime = Runtime.getRuntime();
for (int i = 0; i < runs; i++) {
Thread.yield();
long used1 = memoryUsed(runtime);
int number = create();
long used2 = memoryUsed(runtime);
double avgSize = (double) (used2 - used1) / number;
// System.out.println(avgSize);
if (avgSize < 0) {
// GC was performed.
i--;
if (retries-- < 0)
throw new RuntimeException("The eden space is not large enough to hold all the objects.");
} else if (avgSize == 0) {
throw new RuntimeException("Object is not large enough to register, try turning off the TLAB with -XX:-UseTLAB");
} else {
sizes[i] = avgSize;
}
}
Arrays.sort(sizes);
return sizes[runs / 2];
}
protected long memoryUsed(Runtime runtime) {
return runtime.totalMemory() - runtime.freeMemory();
}
protected int runs() {
return 11;
}
protected abstract int create();
}
class MyColor1 {
byte red;
byte green;
byte blue;
MyColor1() {
red = green = blue = (byte) 255;
}
}
class MyColor2 {
byte[] color = new byte[3];
private static final int red = 0;
private static final int green = 1;
private static final int blue = 2;
MyColor2() {
color[0] = color[1] = color[2] = (byte) 255;
}
}
class MyColor3 {
int color;
private static final int red_shift = 0;
private static final int green_shift = 8;
private static final int blue_shift = 16;
MyColor3() {
color = 0xffffff;
}
}
}