I have had similar problems before and after alot of tweaking and testing I've come to the conclusion that just plain 2D perlin noise as is will never look like natural terrain, it's essentially white noise(ie no huge mountains or valleys, just hills close together)
What I recently found as the way to go is by making a fractal out of multiple Perlin Noises but with different "resolutions" -if you will, added together to get custom terrain resolution using different octaves(ie higher octave = higher noise in your terrain). I usually go about using gain = 0.5(this really do not have to be changed much, it looks pretty good as is), and octaves = ~16
Note; this is made in processing, Java might have some different syntax, but it should be quite the same
float fractal(int x,int y,float gridSubs,float gain,int octaves){
float total = 0;
float freq = 1.0/gridSubs;
float amp = gain;
for(int f=0;f<octaves;f++){
total += noise(x*freq,y*freq)*amp;
freq *= 2.0;
amp *= gain;
}
return total;
}
Website where I found it: here
If you replace your noise() function with this you might get better looking results in the end.
As for your problem with your seams you probably have the coordinate offset in the noise function call for each chunk set wrongly, it should look somewhat like this:
noise( localX * tileSize + (chunkX * chunkSize) , localZ * tileSize + (chunkZ * chunkSize) );
You might have to add some resolution koefficent to make the noise somewhat smooth.
Now You said you are storing the heightvalues in a 2D heightMap, now that is fine and makes the heightvalues easy to access if you need to update them often or need to access them easily.
The problem with this is the size of the array easily get very large, and by that I mean, Very large. With my past experiences I could get a small map(can't remember size, but smaller than yours) to eat up 4Gb of my RAM just by loading it. I did use a float array though so using an integer array could have reduced the memory usage slightly.
How I do it now is I just recalculate each point in the terrain whenever I need to update the geometry, how I currently have set it up it does introduce a slight lagspike each time the terrain is changed when moving around. The benefit is that I can have a 4times larger map with greater detail and still use about 1-2Gb of RAM.
Since what I understood, you simply want to move around the terrain and look at it. This would benefit from not storing the heightmap in an array, since you do not really need the values after you have generated the terrain(this may differ if you are manually smoothing out the terrain).
One last thing to note; I am not an professional programmer, this is just what I have learned from my past experiences using randomly generated noise.