Most of these suggestions apply only after a bottleneck has been identified:
Establish whether you have a memory problem.
Reduce the number of temporary objects being used, especially in loops.
Avoid creating temporary objects within frequently called methods.
Presize collection objects.
Reuse objects where possible.
Empty collection objects before reusing them. (Do not shrink them unless they are very large.)
Use custom conversion methods for converting between data types (especially strings and streams) to reduce the number of temporary objects.
Define methods that accept reusable objects to be filled in with data, rather than methods that return objects holding that data. (Or you can return immutable objects.)
Canonicalize objects wherever possible. Compare canonicalized objects by identity.
Create only the number of objects a class logically needs (if that is a small number of objects).
Replace strings and other objects with integer constants. Compare these integers by identity.
Use primitive data types instead of objects as instance variables.
Avoid creating an object that is only for accessing a method.
Flatten objects to reduce the number of nested objects.
Preallocate storage for large collections of objects by mapping the instance variables into multiple arrays.
Use StringBuffer rather than the string concatenation operator (+).
Use methods that alter objects directly without making copies.
Create or use specific classes that handle primitive data types rather than wrapping the primitive data types.
Consider using a ThreadLocal to provide threaded access to singletons with state.
Use the final modifier on instance-variable definitions to create immutable internally accessible objects.
Use WeakReferences to hold elements in large canonical lookup tables. (Use SoftReferences for cache elements.)
Reduce object-creation bottlenecks by targeting the object-creation process.
Keep constructors simple and inheritance hierarchies shallow.
Avoid initializing instance variables more than once.
Use the clone( ) method to avoid calling any constructors.
Clone arrays if that makes their creation faster.
Create copies of simple arrays faster by initializing them; create copies of complex arrays faster by cloning them.
Eliminate object-creation bottlenecks by moving object creation to an alternative time.
Create objects early, when there is spare time in the application, and hold those objects until required.
Use lazy initialization when there are objects or variables that may never be used, or when you need to distribute the load of creating objects.
Use lazy initialization only when there is a defined merit in the design, or when identifying a bottleneck that is alleviated using lazy initialization.