Backend Handbook
Leapwise
  • 👋Introduction
  • Software Design Principles & Clean Code
    • Atomicity
    • Modularity
    • Hierarchy
    • Loose coupling
    • Asynchronous programming
  • Development Practices
    • JavaDocs
    • Technical Debt
    • Testing Guidelines
      • The Importance of Test Automation
      • The Testing Pyramid
        • Unit Tests
        • Integration Tests
        • End-to-End Tests
      • Mutation Testing
      • Contract Tests
        • REST Controller Contract testing
        • OpenAPI Contract testing
      • Testing Frameworks
        • JUnit 5
        • Testcontainers
        • Mockito
      • Writing Clean Tests - Best Practices
    • Common library
    • Generic CRUD
    • Update Facade
  • Development Tools & Environment
    • Monitoring
    • Performance tuning
    • Multi-tenancy & Configuration Management
    • Git practices
    • CI/CD
    • Maven
  • Project Management
    • Jira
    • Confluence documentation
    • SCRUM
    • Our ways of working
  • LIFE AT LEAPWISE
    • Introduction
    • Who are we?
    • What do we do?
    • Our values
    • Hiring process
      • Hiring: A Mid Frontend Developer's Point of View
    • Benefits we offer
    • Onboarding process
      • Onboarding: A Senior Digital Marketing Specialist's perspective
    • Mentorship program
    • Career development
      • Trainings & certificates we offer
      • Career development: A Senior Software Developer's Insight
    • Community building
    • Juniorship
    • First-hand info from our first team member
    • Join our team
Powered by GitBook
LogoLogo

Company

  • About
  • Culture
  • Services

Insights

  • Leapwise Newsletter
  • Blog

© Leapwise

On this page
  • Don’t write procedural code
  • Use primitives
  • Utilise caching
  • Use StringBuilder
  • Optimize conditional statements and loops
  • Choose the right data structure
  • Tune up Garbage Collector
  • Take care of the initial heap memory size

Was this helpful?

  1. Development Tools & Environment

Performance tuning

PreviousMonitoringNextMulti-tenancy & Configuration Management

Last updated 12 months ago

Was this helpful?

The code we are writing should be clean and we try to write it as production ready in the first attempt. It is not only that it will help us to have a better understanding of it, but it will also make the application more effective. The following paragraphs contain a description of guidelines that will help us to reach both.

Don’t write procedural code

Procedural code is hard to read for humans and PCs. Local variables that are primary building blocks of procedural code are stored on a Stack and kept there until method processing is finished. This already suggests that procedural code will consume more memory. Furthermore, the Just-in-Time compiler will have a hard time compiling such code and improving its performance - code might be even untouched by JIT.

Use primitives

When possible, use int instead of Integer, double instead of Double, … Primitives are stored on Stack and objects are stored on Heap - we want to use Stack more often because it is faster.

Utilise caching

When regular expressions need to be used in computation-intensive code, it is advised to cache the pattern reference rather than compile it every time. Additionally, if any data is frequently accessed from a disk or database, it should be cached.

Use StringBuilder

String concatenation is expensive, although it is often needed. If concatenation is needed, a combination of strings and a '+' sign should be avoided. Instead, StringBuilder should be used to prevent the creation of multiple string objects when concatenation is performed.

Optimize conditional statements and loops

Choose the right data structure

Java provides a wide range of data structures in its Collections Framework, such as ArrayList, LinkedList, HashMap, TreeSet, …

Each of those has a usage but it is up to the developer to understand their performance and choose the one that best suits his needs.

Tune up Garbage Collector

Java’s garbage collector helps manage memory by automatically reclaiming the memory by deleting the objects that are no longer in use. However, garbage collection can also be a source of performance overhead and it can be further optimized. Consider the following settings when doing the optimization:

  • -XX:+UseG1GC: : Use the Garbage First (G1) Collector.

  • -XX:ParallelGCThreads: Sets the number of threads used during parallel phases of the garbage collectors. The default value varies with the platform on which the JVM is running.

  • -XX:ConcGCThreads: Number of threads concurrent garbage collectors will use. The default value varies with the platform on which the JVM is running.

  • -XX:MaxGCPauseMillis: Sets a target for the maximum GC pause time. This is a soft goal, and the JVM will make its best effort to achieve it.

  • -XX:+ParallelRefProcEnabled: Recommend setting this value to enable parallel reference processing. By default, this option is disabled.

  • -XX:+ResizePLAB: Enable or disable resizing of Promotion Local Allocation Buffers (PLABs) by using a + or - symbol before the parameter name.

  • -XX:InitiatingHeapOccupancyPercent: Sets the Java heap occupancy threshold that triggers a marking cycle. The default occupancy is 45 percent of the entire Java heap.

  • -XX:G1ReservePercent: Sets the percentage of reserve memory to keep free to reduce the risk of to-space overflows. The default is 10 percent. When you increase or decrease the percentage, make sure to adjust the total Java heap by the same amount.

  • -XX:MinHeapFreeRatio: Sets the minimum percentage of heap free after GC to avoid expansion.

Take care of the initial heap memory size

Understanding our application's memory requirements is crucial for optimal performance. It's essential to have a clear grasp of the data volume the application handles at peak usage and identify potential bottlenecks. Additionally, awareness of the broader environment, such as the number of CPUs and available RAM, is vital for successful deployment. Ideally, developers should proactively communicate the application's memory needs along with the environment requirements. To achieve this, the memory requirements for the application must be explicitly determined. By default, if not specified otherwise, the Java Virtual Machine (JVM) allocates 25% of the environment's memory as the maximum limit for the application. That is why we need to specify the minimum and maximum heap size memory used by the application. We can say that defining explicit heap memory is mandatory for each application and it can be done with the usage of -Xms (sets the initial and minimum heap size) and -Xmx (sets the maximum heap size) parameters:

java -Xms256m -Xmx2048m

Oracle recommends that -Xms and -Xmx be set to the same value. This gives us a controlled environment where we get an appropriate heap size right from the start. Allocating too little memory can have significant performance impacts. It can lead to garbage collector issues, system freeze, and severe system performance issues. Allocating too much memory can lead to lengthy garbage collection pauses and lengthy memory defragmentation (also known as compaction). That in turn may lead to system failures. Make sure that the maximum heap size does not exceed 8192 megabytes.

The provided guidelines are intended to highlight the significance of minor code enhancements in influencing code performance. As applications become more complex, the likelihood of encountering performance issues in various areas increases. In such instances, monitoring the application may aid in addressing the identified issues.

Page cover image
Cover

if-else

if-else statements can affect Java performance because they require JVM to evaluate each condition sequentially. To optimize it, if-else conditions should be simple, and short-circuit evaluation should be used when possible.

Cover

switch

switch statements are not encouraged by clean code, but long chains of if-else statements should be replaced with a switch. It allows JVM to jump directly to the relevant block of code.

Cover

Loops

Loops are often the primary source of performance bottlenecks. for-each loop should be used instead of for-loop when possible. Method calls and calculations within the loop body should be minimal and (ideally) no object should be created.