“Java is Dead”, “Java is Dying”, “Java is Obsolete” and other variations on the meme of Java mortality appear frequently on line in the IT “press”. These are click-bait, intended to draw your attention to a headline, or get some cheap SEO. For the more complex and nuanced truth about trends in Java usage – look beyond opinions for evidence.
In Java performance testing, one of the most common and sometimes most frustrating issues is the memory leak. Even the most experienced engineer can slip a memory leak into their code. It is important to know how to spot one and debug the issue. Even if you can’t find the source of the issue without a developer’s help, they will always need data to work with so being able to provide useful information is crucial. Therefore, for a Java memory leak, the first thing you will always be asked for is a heap dump.
By Paul Bostrom, Senior Software Engineer
Choosing a programming language for a project is difficult. As technology progresses, new languages emerge exponentially, each one with a particular set of features; others come along that seek to improve existing features, as well as solve new problems. Clojure is a relatively new language, released in 2007 by Rich Hickey. Clojure is a Lisp-like, functional language that runs on the Java Virtual Machine. Let’s look at some of the features that make Clojure a compelling choice.
The Java Virtual Machine
The Java programming language was introduced in 1995 and provided a powerful general purpose programming language. One particular feature it provided was automatic memory management and garbage collection, so that programmers did not have to write code to manage memory like in a C or C++ program. Java also provided an execution environment called the Java Virtual Machine (JVM) to allow programs to be portable across a number of different platforms. For these and other reasons, Java has grown to become one of the most popular programming languages.
The popularity of Java has made the JVM an ideal target for the development of newer programming languages like Clojure. Just as the Java language provided features not available in C or C++, Clojure attempts to provide additional features that Java may not include, while building upon the many features that are included in the Java programming environment. Clojure also provides a number of Java interoperability features to work with the many stable Java libraries in existence. What this means in practical terms is that Clojure code can exist in a variety of forms. Perhaps you just want to write a Clojure program that is simply “glue code” to integrate several reliable Java libraries into a single application. Another option is to create a specialized library in Clojure that Java programs can utilize. Of course, Clojure provides a rich core library that allows the creation of general purpose programs that do not require any direct interaction with existing Java code.
Clojure code is a lot shorter than equivalent code in Java. There are a number of reasons for this, but it’s important to note that the length of the code in a software program has a direct correlation with the cost of developing and maintaining the software. Suppose a Java program is 50 lines, while the equivalent Clojure program is 20 lines, which in my experience is a typical code reduction factor between Java and Clojure. The programmer will spend less time thinking about, writing, and testing the 20-line Clojure program than the 50-line Java program, even though both programs accomplish the same task. One of the reasons Clojure programs are so much shorter is because it has dynamic types, meaning the code doesn’t require a lot of extra statements to declare the various constructs that make up the program. Other factors contributing to the conciseness are its functional programming constructs, as well as its macro system. These will be discussed in more detail below.
Functional programming is a very broad topic, but one of the main concepts is the idea of first-class functions. What this means is that the programming language can define functions that are used as values throughout the program. These functions can interact with other functions by being passed along as arguments or returned as values. This differs from programming in an object-oriented program like Java, where every function (or method) must be contained within an object, which is then passed along as an argument and returned from methods contained in other objects. By allowing functional programming, Clojure lets you define exactly what your program should do without having to wrap everything in unnecessary class declarations.
Immutable, Persistent Data Structures
Clojure forces programs to use immutable data structures by default. While at first this may seem like a limitation of the language, it actually turns out to be an advantage, because it avoids the incidental complexity that is involved with mutable data structures. A data structure created in one part of a program is usually passed along to another part of the program to be used in some other computation. If that data structure can be modified during its lifecycle, it makes the program harder to reason about, and the programmer has to use more mental energy to account for the various transformations that the data might undergo. With immutable data structures, the programmer can take it for granted that the data structure will never change. Immutable data structures are even more important in multi-threaded environments, discussed in the next section.
Advances in technology have brought multi-core processing into all facets of computing, down to the hardware that runs mobile phones. Multiple cores allow programs to create different threads of execution to be performed simultaneously. In many cases, these threads will all want to share the same data structure. In Java there are several tools to synchronize a program across multiple threads, but these tools can be unwieldy and in some cases introduce performance penalties and are hard to diagnose bugs. Clojure provides a number of features to safely and efficiently design multi-threaded programs. One feature was mentioned above: immutable data structures. If data is immutable, there is no need to provide any coordination across threads since the underlying data cannot change. In most cases, however, it is desirable for a program to share some amount of state across its threads of execution. For these instances, Clojure provides a tool called software transactional memory (STM). This is a mechanism similar to how transactions are processed in a database and allows for safe coordination of updates to application data to prevent it from reaching an inconsistent state.
Macros are a powerful feature of Clojure and other Lisps that allow the creation of code that manipulates other code. Stated slightly differently, a macro is code that takes code as input and performs some transformation which returns an expanded version of the code as output. With macros, a program can create new syntax rules and language constructs to make a specific task easier to implement. Macros can even be used to create wholly new languages, known as Domain Specific Languages, which might cater to a particular class of programming tasks.
Hopefully the information in this article has revealed a number of advantages to using Clojure in your next software project. In some cases, just the ability to concisely construct a program is enough of an advantage to warrant choosing Clojure. With additional requirements for concurrent execution, Clojure becomes an even more compelling choice.