You need to translate them before scaling them. Otherwise, the entire coordinate space is scaled. For example:
// Scale the coordinate space by two.
Matrix4f.scale( 2, 2, 2 );
// Now because of the scale this is the same as translating by 2 in every axis.
Matrix4f.translate( 1, 1, 1 ); // Actually means Matrix4f.translate( 2, 2, 2 ) here.
When you transform you are transforming the coordinate space, not the object. If you translate, scale then rotate, the transformations should have no effect on each other.
That's one of your problems, potentially. Your cubes are scaling from the center because that is where the origin is in model space. Each vertex of your cube is placed relative to the center( 0, 0, 0 ) of the cube. To change this either make your model origin the bottom center( I don't recommend this ) or translate up by half the cubes height after scaling.
To go more in depth about why your cubes are scaled from ( 0, 0, 0 ), you should understand how vectors work. All vectors have an origin and a distance from that origin( unlike a point, which just has a location ), this origin is zero. To scale a matrix, we can simply multiply it by a scalar:
Vector3( 1, 1, 1 ) * 2 = Vector3( 2, 2, 2 )
This doubles each component of the vector. Similarly, if we scale the up vector by, say, 5, we get:
Vector3( 0, 1, 0 ) * 5 = Vector3( 0, 5, 0 )
This is just simple arithmetic. This vector is scaled up to 5 units. It is now 5 times higher than it was before, however the x and z components remain unchanged, because they are 0.
So, each of the vertices that make up your cubes have a vector to describe where they are. They are all relative to some origin( ( 0, 0, 0 ) in this case ). So, if we have a cube made up of the following vectors:
( -0.5, 0.5, -0.5 ), ( 0.5, 0.5, -0.5 ), ( -0.5, 0.5, 0.5 ), ( 0.5, 0.5, 0.5 )
( -0.5, -0.5, -0.5 ), ( 0.5, -0.5, -0.5 ), ( -0.5, -0.5, 0.5 ), ( 0.5, -0.5, 0.5 )
We can see how these are scaled. Let's take one of the top vectors and scale it by the up vector * 5:
New Vector = ( -0.5, 0.5, -0.5 ) * ( 0, 5, 0 )
New Vector = ( -0.5, 2.5, -0.5 )
So, this vector has been scaled upwards. Great, just like you were expecting( although only half as much ). Now let's try one of the bottom vectors:
New Vector = ( -0.5, -0.5, -0.5 ) * ( 0, 5, 0 )
New Vector = ( -0.5, -2.5, -0.5 )
This vector scaled downwards instead because it was negative to begin with. So, if we scale by a positive number, it's just going to get further away from the origin, the same with the top vectors. They just have different directions.
You might wonder why the cube is constructed like this. The cube's origin is 0, 0, 0( this is chosen to simplify things like placing the object, and scaling. Like I said, the origin doesn't have to be 0, 0, 0, but it's a good idea ) so, to get a cube of area 1, we want the total length between vectors in each component to be 1.
So, you can see how it works mathematically. Technically the origin is as arbitrary as the bottom center of the cube. Having weird origins can be a pain when scaling though.