In this chapter, we learned about the critical components of the Java ecosystem: JDK, JRE, and JVM. We have also learned how Java is Platform-independent. We have stressed these topics because a good developer is not only someone who knows how to develop a Java program but someone who also knows how things work in the background: from writing the code to running it.
In our forthcoming videos, we will focus on developing skills in Java programming, including mastering its syntax. We will explore the mechanics of Java compilation, questioning why it operates as it does and considering alternative approaches. Understanding the internal workings of a programming language enhances your ability to write efficient code and lays a solid foundation in that language.
There are 3 ways a programming language compiles a written code
Complete Compilation: Here, the entire block of code is compiled in one go, before it is executed. The strength of this method lies in its speed and optimization. However, it falls short in flexibility. Code compiled for a particular operating system, say Windows, will falter if executed on another, like Linux. Sequential Compilation: Unlike the earlier method, this technique processes the code sequentially, interpreting and executing each line as it goes. Imagine reading a book out loud, grasping and articulating each word before proceeding to the next. Although this approach allows for flexibility, it is generally slower and less efficient. JIT Compilation: Just-In-Time (JIT) compilation represents a balanced combination of the previously mentioned methods. It works as the JVM interprets bytecode, with the JIT compiler pinpointing sections of code that are executed often. These parts are compiled, saved, and subsequently reused, achieving both platform independence and enhanced performance Let's take an analogy to understand how these various compilation techniques work.
Chef Analogy
Imagine a scenario where a Chef, facing a wave of customers at his restaurant, needs to guarantee their satisfaction by ensuring that their meals are prepared on time. Let's explore how he can accomplish this.
The initial strategy adopted by the chef involved rigorously following the recipe for each dish from beginning to end, without taking shortcuts or making any advanced preparations. Whenever two tables placed orders for the same dish at a 10-minute interval, the chef prepared each from the ground up.
While this approach was simple, it proved to be time-consuming, particularly during busy periods. The chef's method of following the recipe step by step is like how some programming languages like Python compile their code, one line at a time. This process is known as Sequential Compilation.
Then the chef thought of a better idea, what he did was before the restaurant opened, the chef anticipated the dishes that might be ordered and prepared all of them in advance.
While this ensured that the meals were served quickly, it also meant a lot of food might get wasted if it was not ordered. Additionally, the chef wasn't able to adapt easily to special requests or changes in the menu. The chef's way of guessing the most popular dishes and making them in large amounts before the restaurant starts is like how programming languages like "C" compile their code all at once before they run it. This whole process is called Complete Compilation.
The chef felt sad and demotivated because he couldn't find a good way to make food quickly without wasting any. The chef was sad for a while but then he got a great idea. He started seeing a pattern in what people were ordering. For dishes that were frequently ordered, the chef pre-prepared certain components like sauces or marinated ingredients to speed up the cooking process. For less common dishes, the chef prepared them on the spot.
Over time, the chef got better at predicting which dishes were likely to be ordered and adjusted the pre-preparations accordingly, ensuring efficiency without excessive waste. This is how the JIT or Just-In-Time compiler works. It compiles code as it runs, optimizing parts of the code that are used frequently and leaving less common parts to be compiled as needed. Over time, as it recognizes patterns in the code's execution, it becomes more efficient in its optimizations.
History of Java
To understand why Java uses a JIT compiler, let’s deep dive into the history of Java and understand why it came into existence in the first place.
Java, a modern was created at , where James Gosling led a team of researchers in an effort to create a new language that would allow consumer electronic devices to communicate with each other. Work on the language began in 1991, and before long the team’s focus changed to a new niche, the . Java was first released in 1995, and Java’s ability to provide interactivity and multimedia showed that it was particularly well suited for the Web. The difference between the way Java and other programming languages worked was revolutionary. Code in other languages is first translated by a into instructions for a specific type of computer. The Java compiler instead turns code into something called Bytecode, which is then interpreted by called the (JRE), or the Java virtual machine. The JRE acts as a virtual computer that interprets Bytecode and translates it for the host computer. Because of this, Java code can be written the same way for many platforms (“write once, run anywhere”), which helped lead to its popularity for use on the , where many different types of computers may retrieve the same Web page. Java took the path where it had to give up on some speed to gain the feature of “Platform independence”. The reasons why it came into existence were:- Portability: Before Java, most programming languages like C and C++ were closely tied to the hardware and operating systems they ran on. This made it difficult to write programs that could run on multiple types of systems without modification. Java introduced the concept of "Write Once, Run Anywhere" (WORA). This was achieved through the Java Virtual Machine (JVM), an abstraction layer that compiles Java bytecode to native machine code suited for the specific operating system. Network-Centric Computing: As the Internet was gaining traction, there was a growing need for languages that could easily handle networked computing. Java was designed with built-in support for network computing, including features for building networked applications and working with URLs, sockets, and other networking protocols. Security: Given its network-centric nature, Java was designed with security in mind. Unlike languages like C and C++, Java lacks pointers and provides built-in garbage collection, which minimizes issues like memory leaks and buffer overflows. Its security model also allows the downloading of untrusted code over a network and running it in a secure environment. Object-oriented programming: While not the first object-oriented programming language, Java simplified OOP and made it more accessible to a broader audience of developers. This made it easier to build large, maintainable, and modular applications. Simplified Syntax: Java aimed to reduce the steep learning curve associated with languages like C++ by offering a simpler, cleaner syntax while still maintaining the power and flexibility that professional developers require. Robustness and Reliability: Java incorporated various features like strong memory management, exception handling, and type checking, making it a robust and reliable choice for large-scale, mission-critical applications. Java was created to address the needs of modern, networked computing and to provide a secure, portable, and user-friendly programming environment. It filled gaps left by existing languages at the time, making it a popular choice for a wide range of applications, from enterprise software to mobile applications.
Android vs IOS
A real-life example of where Java’s “platform independent” feature comes in handy is Android Devices. Android is an operating system used by over 3 billion people on their smartphones. There are hundreds of manufacturers that produce several models of Android devices. Creating an operating system for such a wide range of devices was a very difficult task because every Android device model had its own specifications. Some had a high RAM, some had a very good processor. Android needed a programming language that could make this possible. If they wanted to create an operating system for 3 billion users, they needed a flexible and portable language to write the logic. This is where Java came into the picture. Kotlin, the famous framework recognized by Google as the primary language for developing Android apps is based on Java. In Android, apps are commonly written in Java or Kotlin. The source code is compiled to bytecode by the respective language compiler, which is then translated into Dalvik Executable (DEX) files by the Android build system. Earlier versions of Android used a runtime called Dalvik to execute this DEX code. However, modern Android uses the Android Runtime (ART), which compiles the DEX files into native machine code at the time of app installation, a process known as Ahead-of-Time (AOT) compilation. This Android run-time is very similar to Java run-time.In contrast, iOS apps are typically written in Objective-C or Swift. Unlike Android's two-step compilation process involving bytecode and then native code, iOS skips the bytecode step entirely, optimizing for the specific architecture of the device right from the start. This is also a form of AOT compilation, but it occurs during the build process rather than at installation. Thus, the key difference lies in when and how the AOT compilation occurs, with Android doing it at installation time and iOS during the build process.