3

Java and C# are Obsolete in the Age of Docker

 3 years ago
source link: https://medium.com/star-gazers/java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java and C# are Obsolete in the Age of Docker

Languages running on virtual machines were developed to make deployment on any platform easy. But this ability no longer matters when your software runs in a container.

1*EuO0YZxTR_Yos-vkMwF1cA.png?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6

Yes, I know this sounds like a click bait title. How dare I come with such a controversial and radical claim that Java and C# are obsolete?

Before jumping to the comment field and calling me a clueless idiot, let me clarify what I mean by “obsolete” in this case. Something being obsolete doesn’t necessarily mean it isn’t used or important anymore. I think it is fair to call COBOL obsolete as a programming language. It is no longer needed to solve specific problems. However, that does not mean we can shut down every system running COBOL or stop writing COBOL code. COBOL still matters, because large important legacy systems operating key financial systems still run on it.

You don’t rewrite millions of lines of working code just because the technology used to create it is largely obsolete.

Rather, what we mean is that if we imagined that all these systems where gone tomorrow due to some cosmic accident and you had to recreate these systems, would you use COBOL? Would COBOL offer important advantages giving significant benefit in creating these systems?

I will answer that with a resounding “NO.” We have better technologies to build these systems today if we had to do it all from scratch.

The Java Virtual Machine has been replaced by the “Linux Virtual Machine”

Java and C# are in a similar position. Ask yourself: would you build a systems language targeting a virtual machine to create enterprise systems in the cloud today? No, I don’t think so. Before the rise of a containerized cloud, it made sense to deploy software on any server in the form of a .jar file. In some ways, you could think of the Java Virtual Machine as creating its own little container.

With Docker and other container technologies, we move one step further. Instead of the Java Virtual Machine, we instead have what might be called the Linux Virtual Machine. Linux, rather than the Java APIs, become the least common denominator. You simply write software that will run in some kind of Linux environment that you can create with a Docker container and you can run anywhere.

Read more: A Short Explanation of What Docker and Containers Are.

Instead of your operating system spinning up a Java Virtual Machine or a .NET environment, you spin up a Docker container which creates the equivalent of a Linux Virtual Machine on whatever operating system you are running.

What are the advantage of this? You have a whole operating system environment with filesystems, OS services, etc., that you can utilize. You can use various programs written in any language. The Java and C# approach meant creating common language runtime where you could make special language variants such as Jyton (JVM) or Iron Python (.NET) that would produce code for this specific virtual machine.

To avoid confusion I am referring to a virtual machine in the abstract here. Both Java and C# today use JIT compilers. However we can think of the machine that understand Java byte code as a virtual machine. The same applies to .NET intermediate language (IL). Hardware that directly understand Java bytecode or .NET IL does not exist in the real world. It is imaginary hardware, brought to life through software.

Anyway back to my key point: This idea of targeting a virtual machine for your compiler output is largely obsolete. Why create all these alternative versions of every language to run on the same virtual machine to allow interaction when you can simply run them all within the same Docker container?

Difference in Java and C# Approach

Let us do a digression about C# since the language got brought up so frequently in the comment section of this story.

Many objected to the notion that I characterized C# as a non native language using a JIT. You don’t have to take my word for it, but this is what Microsoft says:

Windows 8.1 applications and Windows Desktop applications that target the.NET Framework are written in a particular programming language and compiled into intermediate language (IL). At runtime, a just-in-time (JIT) compiler is responsible for compiling the IL into native code for the local machine just before a method is executed for the first time.

In other words Java and C# programs are distributed the same way. They are both shipped as byte code or intermediate language (IL), whatever jargon you prefer. Java can use third party code in jar files, C# can use third party code in assemblies.

So why do people make a fuss of this and insist C# is somehow profoundly different?

The Native Image Generator (NGEN)?

The key difference from Java is that C# typically uses what is called a Native Image Generator (NGEN). This still implies a just-in-time compilation strategy. However it takes advantage of the fact that a JIT will create a bunch of native code in memory. This memory representation of all the code that has been compiled is usually referred to as an image.

What many JIT based languages allow you to do is to dump this image to file, so you avoid needing to perform all the same JIT operations over again. This is not quite the same as compiling however.

Since I am not C# expert I will use Julia as an example.

Read more: Why I Wrote a Julia Programming Book.

Julia is designed for JIT compilation. To create an image of the standard library, we deliberately run a lot of code that will exercise code paths into all the important areas of the libary. This causes most functions to be compiled into native code.

But this essentially creates a cache. You have no guarantee that you managed to JIT everything, so you still need the runtime and the JIT compiler when you later run this code. You just save yourself from recompiling the same frequently used functions over and over again.

The same applies to the .NET world with the NGEN. It does not compile everything to native code. You still need the runtime and JIT compiler to run these image files. In particular you need them to interact with .NET assemblies (equivalent of DLLs).

Thus NGEN does not allow you to deploy native binaries with zero dependencies. Thus in 2017 Microsoft created what they call .NET native. Why? Do developers care? Indeed they do. That is what Microsofts own survey showed.

1*UUcgMi0afoUptoPvQclN9g.png?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6
.NET and C# Developers want Ahead-Of-Time (AOT) Compilation.

.NET Native Proves the Competition Right

Rust, Swift and Go are some of languages competing in the same space as .NET. All of them newer languages which chose to be compiled to native code rather than target an intermediate language like Java and C#. The Microsoft survey asked what those looking for an AOT option used instead. This word cloud shows the answer.

1*4nxXYrXRZQ99TR1JXnaoSg.png?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6

Hence to assume Microsoft is ahead of this game is naive. They have created AOT versions of C# to counter rapidly growing competition.

With .NET Native you can compile your C# programs into one self contained binaries. That allows you to do exactly like e.g. Go and deploy directly to a server or Docker container without what is normally required by a .NET application. I am paraphrasing Microsoft:

  • Third-party assemblies that are required by your app (basically the equivalent of DLLs in native development)
  • The .NET Framework Class Library.
  • The common language runtime.

But this is clearly taking C# in a direction which is different from what it was designed for. For instance you cannot:

  1. Make calls from a .NET Native binary to other .NET assemblies.
  2. Use reflection to call arbitrary code in third party assemblies.

Thus you are forced to choose. You either run your C# apps through a JIT and get access to any number of third party libraries (in the form of assemblies) or you pre-compile your app, and it can only use code stored in the binary itself.

Why this limitation?

There is no native code binary interface defined for .NET languages. Hence there is no way to call an assembly compiled into native code. We don’t know what the calling convention would be. Apple’s Swift 5 in contrast which was designed for native compilation from the ground up has a well defined stable Application Binary Interface (ABI).

Thus what the native compiler will is to analyze what code in third party assemblies (libraries) your C# app calls and then it will embed that code in the binary. But to do that this has to be explicit. If you call code in third party libraries through reflection then all bets are off. The native compiler will not be able to determine through static analysis what code you are calling as that will be a runtime decision.

This is why creating native code of languages made for JIT-ing is difficult. We see the same problem in the Julia community. The images can end up getting quite large.

Microsoft provides a hack called a runtime directives file which tells you what you are looking up at runtime. But you need to keep that in sync yourself.

In short both Java and C# are currently going through hoops to basically bolt on functionality they where never designed to support. If native deployment was your goal, you wouldn’t have designed a language that looks like C# and Java. No, your language would have looked more like Go, Swift or Rust.

1*JuJmwihXUAU8kS0NHkFekQ.png?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6
Apps are uploaded to Apple’s App store with Bitcode (similar to Java byte-code and CLR IL), but users only ever download native machine code. Bitcode is never distributed to users.

The Cloud Solves Deployment

The easy deployment that byte code was supposed to provide can be solved in other ways. E.g. with Swift byte code is sent to the App store but not to your computer. The cloud will compile code for each desired platform. When you download from the App store you get native code for your specific platform. That means you don’t need any byte code interpreter or compiler installed on your computer and kept up to date. This of course is not the same as the container solution but it is a complementary solution that helps do away with the need to distribute byte code directly to users.

Is there No Point in Learning Java or C# Anymore?

No, of course these language still matter because enormous ecosystems and millions upon millions of lines of code have been written in these languages. Existing systems will have to be maintained. And new systems will be continued to be built using Java and C# because they enjoy an advantage in availability of large number of libraries as tools. Not to mention companies who want to hire lots of developers to build large systems will be able to tap into a large talent pool of Java and C# developers.

What I am saying instead is that just like COBOL, there would have been no point in creating Java and C# today if all this software and tools did not exist. Nobody would look at today’s software landscape and decide we need systems languages running on virtual machines. No, that problem has now more elegantly been solved by containers.

1*pFaeNO48gYlRMQii977cQg.jpeg?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6
The Gophers are the mascots for the Go programming language.

Go is Java for the Docker Age

The language Go from Google is a harbinger of this new age. It does perhaps not come as a surprise that Docker and Kubernetes are themselves written in Go.

Read more: Is Go a Systems Programming Language?

Please note that the language used to implement Docker is irrelevant here. It is not what makes Go suitable to run in a container environment. It would have been suitable even if Docker had been written in Pascal or Ada.

When everything runs in a Docker container you are free to compile to native code instead, which is what Go does. This gives several advantages:

  • Docker containers can be smaller because you don’t need to install a large virtual machine infrastructure, JIT compiler, runtime or other frameworks. I mean VM here as in the JVM and not something like VMWare.
  • Deployment is easy. For non-container use, it is much easier to distribute a native binary produced by Go. You can just download a Go binary onto your machine and run it without having to install a JVM or Common Language Runtime (.NET).
  • Cross compilation makes it easy to compile binary versions for different platforms.

For most software today, binary code is a small part of the size of the software. Assets make up much more of the software. You can see, for example, how Apple distributes macOS software in fat binaries containing x86 and ARM instructions today. Previously they had fat binaries containing x86 and PowerPC instructions. In none of the cases did this add much to the size of macOS software. Data files, images and other assets usually contain the bulk of an App installation.

Java, C# and other managed environments that were so hot in the 90s now simply represent an unnecessary extra layer of abstraction. Containers create a layer between your environment and the actual hardware you are running on. Why would you want to create yet another layer? It is just something that sucks up more resources and slows down your system.

Are Just-in-Time Compilers Obsolete?

Both Java and C# use Just-in-Time (JIT) compilation, unlike modern systems programming languages such as Go and Rust. One may be tempted to read between the lines of what I am saying and think that I am implying that Just-in-Time compilation is a dead end. After all Go, Rust and Swift all abandoned that approach.

However, that would be a premature conclusion. In fact, Just-in-Time-Compilation (JIT) has had a massive growth in an entirely different corner of our computing infrastructure. Lua, JavaScript, Julia, Pharo (derived from Smalltalk), R, Matlab, LISP and Python are languages that either started out using JIT or have either already transitioned to it or have begun to do so.

What is different about these languages? They are not systems programming languages and they are not statically typed, rather they are dynamic languages.

For dynamic languages, I think JIT is a natural fit. Due to the dynamic nature, ahead-of-time compilation (AOT) is not a natural fit. Unlike statically typed languages, you often use these languages interactively on the command line. Ahead-of-time compilation would be impossible in this case, or at least very cumbersome to use.

Thus I don’t think technologies like JIT are dead. To the contrary, I think usage will get expanded. However, for systems programming I think AOT will take over.

From various benchmarks, you can see that despite the many years of lead that Java has on Go, it will typically get outcompeted on almost every metric. Go will get more calculations done, process more requests and consume less memory and give lower latency than Java. Yes, I am sure you have cases where Java wins. But the point is that there is no apparent clear advantage to using JIT compilation in this space. If it was, then we should expect to see a clear performance advantage of Java and C# over Go, Rust or Swift. We don’t.

However, for dynamic languages, JIT tends to give a massive advantage. Doing ahead-of-time compilation for a dynamic language is simply impractical and awkward. I will not rule it out. You can do that for Julia today, but it will typically create rather large binaries and not offer any clear performance advantages.

Does Any of This Matter in the Real World?

Okay, so Java and C# are obsolete in theory but countless jobs require these technologies, so does this observation have any practical implication for the real world?

In the short term, the answer is no. But this is like any obsolete technology. It doesn’t die from one day to the other. Rather one will see a gradual decline. People will gradually migrate to other languages like Go, Rust, Swift, D, and whatever else is around the corner for systems programming.

These alternatives will typically offer better performance, simpler deployment and lower memory usage. For anyone starting a new project, that will obviously matter. Sometimes Java and C# will be the only natural choice because the competition lacks libraries and tooling in the particular domain you want to build a solution. However, that is a transient state of things. As the competition expand their ecosystems and tooling, the attractiveness of Java, C# and other managed systems will decline.

1*FL4HepMdwZx49i0mr3xdrw.jpeg?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6
Microsoft Word, a popular desktop application. Not likely to run in a Docker container any time soon.

Desktop GUIs and Mobile Apps

There are areas where containers make less sense such as the writing of desktop applications and mobile applications. However, that is not something that speaks in favor of statically-typed managed languages such as Java and C#. Quite the contrary.

Read more: What Is Docker and Does It Have Benefits for Desktop Developers?

Desktop applications are usually tailored to the desktop environment that you are in. Deployment on any OS or hardware is less of an issue. For instance, what would you rather use if you were making desktop applications on macOS? Java or Swift? Java, being VM based, offers no advantages. In fact, it only offers disadvantages. You get a larger memory footprint and slower startup time. Remember that unlike server software, desktop software is not running for months at a time. Desktop software gets launched and closed within a couple of hours. In this scenario, Ahead-of-Time compilation is an advantage.

One can just compare iOS and Android. The feel of an iPhone has always been snappier than Android even on lesser hardware. A lot of that comes down to Swift not needing JIT compilation or garbage collection.

It is also a safety advantage. On iOS you can cryptographically sign a code page in memory in order to verify it has not been tampered with. You cannot do that with JITed code because you don’t know what it is going to look like ahead of time.

What about Windows then? C# is popular for desktop GUIs. Sure, and as someone who did early Windows GUI programming using the Win32 C API and Microsoft Foundation Classes (MFC) I can tell you that C# was a welcome relief. But that had far more to do with sane library design than language. The Qt cross platform GUI tookit provided much of the same advantages in terms of ease of GUI programming relative to Win32 and MFC. And Qt is written in C++, not C# or Java. In fact if you write complex GUI applications today, I would wager you are more likely to use Qt than any C# solution.

C# will continue to matter on Windows for desktop development for the simple reason that this is the language in which Microsoft releases their GUI toolkits. It is not because C# is managed. What I am saying is that had Microsoft standardized on a compiled language like Swift it would have made no difference to their success.

The Microsoft rational for .Net is that they had all these languages such as Visual Basic running on their stack. People made components for VB in C++ using Common Object Model (COM). This was messy. They thought their solution to this would be .Net. But since VB basically died, the whole .Net approach makes much less sense than it once did. And Apple has shown you can do all these things with a native language. Both through Objective-C and Swift, Apple offers the same kind of desktop experience.

.NET offers a way to describe types across language barriers. Apple, I would claim, solved this problem more elegantly by using a message passing system instead. I am not getting deeply into that here, but I could write about it in the future if there is interest.

1*Pz1q8ORJjokJNvU1T_PyWQ.png?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6

How Java and C# Will Attempt to Strike Back

Of course, few well established technologies give up without a fight. More likely we will see increased efforts into turning Java and C# into environments for compilation to native code, .NET Native and GraalVM native Image are some examples of this. But this is a bit like Python trying to counter Julia by building its own JIT solutions. It sort of, kind of works, but there is a massive disadvantage in a large legacy having been built on entirely different principles. Suddenly features such as reflection which you have relied on in the past may not work.

Thus the question is really whether the options for doing native compilation of C# and Java become as streamlined before competitors such as Go, Rust and Swift matures and gain similar rich eco-systems and toolchains as Java and C#.

We always do car analogies in the tech industry, and even in this case there is a useful analogy. Tesla represents the new direction, but they have massive quality issues, manufacturing problems, scaling issues etc. They suffer in areas where traditional automakers excel. The question is whether Tesla will mature in their manufacturing prowess more quickly or slowly than traditional automakers such as GM, Ford and Toyota can manage to perfect the creation of well-designed electric cars that people want to buy.

What is the biggest problem? Being immature or having a large legacy to deal with? Personally I like to put my bets on the newcomers but the established industry often end up surprising me. Oracle and Microsoft may very well end up surprising by reimagining their languages and toolchains. Time will tell.

1*QIBU6IHPYKhpBt-qXR_DqA.png?q=20
java-and-c-is-obsolete-in-the-age-of-docker-39fb0d28f8b6
Container technologies on Linux for distributing self contained applications.

Why Do You Only Talk About Docker? There are Other Containers!

Indeed. Docker is just an icon of the new times. The world is of course far more diverse than Docker. You could use LXC, Mesos, OpenVZ and many other types of containers. There are specialized containers such as AppImage, Snap and Flatpak.

Docker is really a container for cloud solutions. Docker is a product of the rise of the cloud with horizontal scaling and micro-services. C# was Java was really made for a world of big dedicated servers. When you scale across lots of machines, you want easy deployment and replication of configuration and setup. That is what containers give you. If you only setup one machine that mattered less.

Still even the desktop benefits from container technology. E.g. Linux distributions suffer the problem that one has to make packages of every application for each distribution since they all use different package systems, have different layouts and organization. With container technology like Flatpak and Snap you paper over these differences and get one click install and launch on any Linux distro.

And this isn’t unique to Linux. Apps from Apple’s App store are also container based. With the App store you see the full alternative to the Java and C# approach. A cloud service determines your hardware and then you download native binaries specifically tailored for your machine. If not using such a distribution mechanism one can use fat binaries.

Either solution remove the need to have a JIT compiler, runtimes and frameworks for Java or C# installed. Naive code shipped in containers make distribution and deployment easy.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK