The performance of EJB-based J2EE systems overwhelmingly depends on getting the design right. Use performance-optimizing design patterns: Value Object, Page-by-Page Iterator, ValueListHandler, Data Access Object, Fast Lane Reader, Service Locator, Verified Service Locator, EJBHomeFactory, Session Façade, CompositeEntity, Factory, Builder, Director, Recycler, Optimistic Locking, Reactor, Front Controller, Proxy, Decorator, and Message Façade.
Explicitly remove beans from the container when a session is expired. Leaving beans too long will get them serialized by the container, which can dramatically decrease performance.
Coarse-grained EJBs are faster. Remote EJB calls should be combined to reduce the required remote invocations.
Design the application to access entity beans from session beans.
Collocated EJBs should be defined as Local EJBs (from EJB 2.0), collocated within an application server that can optimize local EJB communications, or built as normal JavaBeans and then wrapped in an EJB to provide one coarse-grained EJB (CompositeEntity design pattern).
EJBs should not be simple wrappers on database data rows; they should have business logic. To simply access data, use JDBC directly.
Stateless session beans are faster than stateful session beans. If you have stateful beans in your design, convert them to stateless session beans by adding parameters that hold the extra state to the bean methods.
Optimize read-only EJBs to use their own design, their own application server, read-only transactions, and their own optimal configuration.
Cache JNDI lookups.
Use container-managed persistence (CMP) by default. Profile the application to determine which beans cause bottlenecks from their persistency, and implement bean-managed persistence (BMP) for those beans.
Use the Data Access Object design pattern to abstract your BMP implementations so you can take advantage of optimizations possible when dealing with multiple beans or database-specific features.
Minimize the time spent in any transaction, but don't shorten transactions so much that you are unnecessarily increasing the total number of transactions. Combine transactions that are close in time to minimize overall transaction time. This may require controlling the transaction manually (i.e., turning off auto-commit for JDBC transactions or using TX_REQUIRED for EJBs).
J2EE transactions are defined with several isolation modes. Choose the lowest-cost transaction isolation level that avoids corrupting the data. Transaction levels in order of increasing cost are: TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, and TRANSACTION_SERIALIZABLE.
Don't leave transactions open, relying on the user to close them. There will inevitably be times when the user does not close the transaction, and the consequent long transaction will decrease the performance of the system significantly.
Bulk or batch updates are usually more efficiently performed in larger transactions.
Lock only where the design absolutely requires it.
Beans.instantiate( ) incurs a filesystem check to create new bean instances in some application servers. Use the Factory pattern with new to avoid the filesystem check.
Tune the message-driven beans' pool size to optimize the concurrent processing of messages.
Use initialization and finalization methods to cache bean-specific resources. Good initialization locations are setSessionContext( ), ejbCreate( ), setEntityContext( ), and setMesssageDrivenContext( ); good finalization locations are ejbRemove( ) and unSetEntityContext( ).
Tune the application server's JVM heap, pool sizes, and cache sizes.