# Optimizing garbage collection for local objects

I am trying to make a 3D application with OpenGL/LWJGL at the moment, but I am now already hitting some limits on the JVM, most likely due to a configuration that needs to be done.

I have the following code in the render loop:

@Override
protected void render(final double msDelta) {
glClearColor(0.0f, 0.25f, 0.0f, 1.0f);
glClearDepthf(1f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
testProgram.use();

for (int i = 0; i < 2400; i++) {
float fVar = i + currentTime / 1000f * 0.3f;
modelviewMatrix = Matrix4f.multiply(
Matrix4f.translate(0.0f, 0.0f, -8.0f),   //translate
Matrix4f.rotate(currentTime / 1000f * 45.0f, 0.0f, 1.0f, 0.0f),  //rotate around Y
Matrix4f.rotate(currentTime / 1000f * 21.0f, 1.0f, 0.0f, 0.0f),   //rotate around X
Matrix4f.translate(
(float)Math.sin(2.1f * fVar) * 2.0f,
(float)Math.cos(1.7f * fVar) * 2.0f,
(float)Math.sin(1.3f * fVar) * (float)Math.cos(1.5f * fVar) * 2.0f
)  //translate
);
glUniformMatrix4(MODELVIEW_LOCATION, false, modelviewMatrix.asFloatBuffer());
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}


The most important part is Matrix4f (My own implementation, not LWJGL's one... I don't know how I could make it more clear without renaming it to something terrible):

public class Matrix4f {
private final static float EPSILON = 0.01f;

private final float[] elements = new float[16];

public Matrix4f() {

}

public Matrix4f(final float[] elements) {
System.arraycopy(elements, 0, this.elements, 0, 16);
}

public Matrix4f multiply(final Matrix4f other) {
float[] a = getElements();
float[] b = other.getElements();
return new Matrix4f(new float[] {
a[0] * b[0] +   a[4] * b[1] +   a[8] * b[2] +   a[12] * b[3],
a[1] * b[0] +   a[5] * b[1] +   a[9] * b[2] +   a[13] * b[3],
a[2] * b[0] +   a[6] * b[1] +   a[10] * b[2] +  a[14] * b[3],
a[3] * b[0] +   a[7] * b[1] +   a[11] * b[2] +  a[15] * b[3],   //X column

a[0] * b[4] +   a[4] * b[5] +   a[8] * b[6] +   a[12] * b[7],
a[1] * b[4] +   a[5] * b[5] +   a[9] * b[6] +   a[13] * b[7],
a[2] * b[4] +   a[6] * b[5] +   a[10] * b[6] +  a[14] * b[7],
a[3] * b[4] +   a[7] * b[5] +   a[11] * b[6] +  a[15] * b[7],   //Y column

a[0] * b[8] +   a[4] * b[9] +   a[8] * b[10] +  a[12] * b[11],
a[1] * b[8] +   a[5] * b[9] +   a[9] * b[10] +  a[13] * b[11],
a[2] * b[8] +   a[6] * b[9] +   a[10] * b[10] + a[14] * b[11],
a[3] * b[8] +   a[7] * b[9] +   a[11] * b[10] + a[15] * b[11],  //Z column

a[0] * b[12] +  a[4] * b[13] +  a[8] * b[14] +  a[12] * b[15],
a[1] * b[12] +  a[5] * b[13] +  a[9] * b[14] +  a[13] * b[15],
a[2] * b[12] +  a[6] * b[13] +  a[10] * b[14] + a[14] * b[15],
a[3] * b[12] +  a[7] * b[13] +  a[11] * b[14] + a[15] * b[15]  //W column
});
}

public FloatBuffer asFloatBuffer() {
FloatBuffer floatBuffer = BufferUtils.createFloatBuffer(elements.length).put(elements);
floatBuffer.flip();
return floatBuffer;
}

float[] getElements() {
return elements;
}

@Override
public String toString() {
return Arrays.toString(elements);
}

public static Matrix4f identity() {
return new Matrix4f(new float[] {
1.0f, 0.0f, 0.0f, 0.0f, //X column
0.0f, 1.0f, 0.0f, 0.0f, //Y column
0.0f, 0.0f, 1.0f, 0.0f, //Z column
0.0f, 0.0f, 0.0f, 1.0f  //W column
});
}

public static Matrix4f scale(final float sx, final float sy, final float sz) {
return new Matrix4f(new float[] {
sx, 0.0f, 0.0f, 0.0f,   //X column
0.0f, sy, 0.0f, 0.0f,   //Y column
0.0f, 0.0f, sz, 0.0f,   //Z column
0.0f, 0.0f, 0.0f, 1.0f  //W column
});
}

public static Matrix4f translate(final float tx, final float ty, final float tz) {
return new Matrix4f(new float[] {
1.0f, 0.0f, 0.0f, 0.0f, //X column
0.0f, 1.0f, 0.0f, 0.0f, //Y column
0.0f, 0.0f, 1.0f, 0.0f, //Z column
tx,    ty,    tz, 1.0f  //W column
});
}

public static Matrix4f rotate(final float theta, final float x, final float y, final float z) {
if (Math.abs(x * x + y * y + z * z - 1.0f) >= EPSILON) {
throw new IllegalArgumentException("(x, y, z) is not a unit vector: x = " + x + ", y = " + y + ", z = " + z);
}
float cosThetaRes = 1.0f - cosTheta;
return new Matrix4f(new float[] {
cosTheta + x * x * cosThetaRes,     y * x * cosThetaRes + z * sinTheta, z * x * cosThetaRes - y * sinTheta, 0.0f,   //X column
x * y * cosThetaRes - z * sinTheta, cosTheta + y * y * cosThetaRes,     z * y * cosThetaRes + x * sinTheta, 0.0f,   //Y column
x * z * cosThetaRes + y * sinTheta, y * z * cosThetaRes - x * sinTheta, cosTheta + z * z * cosThetaRes,     0.0f,   //Z column
0.0f,                               0.0f,                               0.0f,                               1.0f    //W column
});
}

public static Matrix4f frustum(final float left, final float right, final float bottom, final float top, final float near, final float far) {
return new Matrix4f(new float[] {
2 * near / (right - left),          0.0f,                               0.0f,                           0.0f,   //X column
0.0f,                               2 * near / (top - bottom),          0.0f,                           0.0f,   //Y column
(right + left) / (right - left),    (top + bottom) / (top - bottom),    (near + far) / (near - far),    -1.0f,  //Z column
0.0f,                               0.0f,                               2 * near * far / (near - far),  0.0f    //Z column
});
}

public static Matrix4f perspective(final float fovy, final float aspect, final float near, final float far) {
float y2 = near * (float)Math.tan(Math.toRadians(fovy * 0.5f));
float y1 = -y2;
float x1 = y1 * aspect;
float x2 = y2 * aspect;
return frustum(x1, x2, y1, y2, near, far);
}

public static Matrix4f multiply(final Matrix4f... matrices) {
Matrix4f output = identity();
for (Matrix4f matrix : matrices) {
output = output.multiply(matrix);
}
return output;
}
}


As far as I see everything is method specific (a local variable) and thus eligible for garbage collection.

I have the following questions:

• Did I overlook something, resulting in it to not be garbage collected?
• Could some parts be done faster?
• Most importantly: With what garbage collector settings should I play to make it garbage collect all local variables as fast as possible, without doing a Stop-The-World garbage collect?
• Your render method should not compile given the class Matrix4f you have shown. Could you show a compilable example? – Bruno Costa Jan 17 '14 at 21:30
• You should make your conventions clear. The constructor should have JavaDoc that says that elements is in column-major order. Also name the argument multiply(Matrix4f right) to clarify which matrix is on the right side of the multiplication. – 200_success Jan 18 '14 at 3:09

modelviewMatrix is not available for garbage collection until the render() method is complete. It is not clear from the code where this variable is actually defined and why it is not declared in the render() method.

My suggestion is to move the code inside the for loop into its own method, lets call it renderIteration. Any objects created in the renderIteration() method and not returned to the caller will be available for GC when the renderIteration() method completes.

Cheers.

Not that may be your issue but I would do your multiply algorithm with a loop:

public Matrix4f multiply(final Matrix4f other) {
float[] a = getElements();
float[] b = other.getElements();
float[] result = new float[a.length];
//assume a.length == b.length and that lines * columns = a.length
int alen = Math.sqrt(a.length);
for(int l = 0; l < alen; ++l){
for(int c = 0; c < alen; ++c){
int res = 0;
for(int k = 0; k < alen; ++k){
res += a[c*alen +k] * b[l*alen +k]
}
result[l*alen + c] = res;
}

}
result new Matrix4f(result);
}