Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Bite-sized bytecode and class loaders

2020 java

Today I gave a talk at the London Java Community on bytecode and classloaders. The presentation is available at SpeakerDeck; the presentation was recorded and is on the London Java Community channel.

For the presentation, I wrote a JVM emulator that allows stepping through bytecode and seeing the result of the local and stack as you go. It’s not a complete implementation (the deficiencies are listed on the README) but it’s something you could look through to get a feel of how the JVM works when interpreting code.

The JVMulator is available at https://github.com/alblue/jvmulator and you can build it with Maven or your favourite IDE. There’s a GUI which is set up to run as the main class, so once built, you can run it with java -jar or even mvn exec:java to launch it.

Bytecode

The JVM runs on bytecode; it’s a compact encoding of instructions where most instructions take up a single byte. There’s a good description of it on Wikipedia, and there’s also a useful table of bytecodes as well.

The majority of bytecodes take no operands, but deal with values being pushed to or pulled from the stack. There are also a number of local variable placeholders which are specific to the frame being executed; these typically hold things like the counter in the loop for iteration or other local variables. Methods can have zero or more locals and require zero or more stack depth; both figures are encoded in the method bytecode, so that when the JVM runs it can reserve the amount of required space on the stack for the method to execute.

Arguments passed in to the method take up one local slot, though these placeholders can be re-used throughout a method’s execution if the argument is no longer required after first use. For instance methods specifically, there’s a hidden first argument which contains the this pointer, so if you have an instance method with 2 arguments, it’s always going to reserve at least 3 slots for local variables.

Some bytecodes take operands in the instruction stream, so not all bytes in the stream represent valid instructions. For example, when pushing a constant to the stack the bipush will push the next byte on the stack, and sipush will push the next two bytes as a short onto the stack. Although many such opcodes take only one or two bytes, there is a special wide mode which means that the next instruction takes double the normal amount of variables. This is primarily used when dealing with local variables; the first 256 local variables can be accessed by having a single byte, but if you have more than 256 local variables (why‽) then you’d use the wide form of the iload bytecode for that.

Bytecode is stored in the Code attribute of a method, so all Java class files that have code associated with them (i.e. everything that’s not purely an interface) will have the string Code inside the file somewhere. Interfaces and abstract methods have no Code attribute, though a class will typically have a default constructor injected by the javac compiler.

Stack

The stack forms a key part of the Java bytecode. Operations are consumed from the stack, and results are pushed onto the stack. At the end of the method’s return, the top level of the stack is the return result. Simple math operations (e.g. iadd, fmul) consume two stack elements and then push the result back; some, like ineg pull and push a single value.

One quirk of the JVM is that long and double values occupy two slots on the stack; that is, there’s a missing stack element value which can’t be accessed each time you push or pull one of these values. This was an implementation workaround when JVMs were 32-bit; unnecessary for today, but kept for backwards compatibility and to prevent requiring re-compiling Java code.

There are some 2 operators that deal with two slots at a time (like dup2) which exist as an optimisation to duplicate a long or double value; otherwise, dup is used.

Locals

Locals are accessed with various iload or aload operators to pull the values from the local variables onto the stack. You’ll typically see programs pulling with aload_0 which pulls the first local variable - for instance fields, this is the this parameter. The aload instructions deal with objects by reference (address load) as well as arrays; there’s separate aaload for accessing an object in an object array (such as that you’d process with main(String args[])).

Since bytecodes operate on the stack, if a variable is to be used, it needs to be pulled there first. The only time this isn’t needed is for incrementing (or decrementing) an integer value – there’s a special instruction which is used to do that – and that’s typically used for loops where pulling and stashing the loop counter each time would be unproductive.

Classfiles

A classfile is a tightly packed mechanism for representing Java classes (and interfaces, and some special containers like package-info and module-info). It contains several variable-length sections, so it can’t be randomly accessed directly when loading; it has to be parsed to be understood.

The constant pool is a key component of a class file. It contains a list of typed data values; UTF-8 strings, long values, double values etc. that are used in the method’s code or as field initialisers. There are some instructions which encode specific values – for example, iconst_5 will push 5 onto the stack – but if you are out of luck with your value, you can encode in the constant pool.

As well as UTF-8 strings and numeric values (for int/long/float/double – char/short/byte/boolean are figments of the JVM’s imagination) there are fields which define what it means to be a Class, what a FieldRef or MethodRef is, and a pairing called NameAndType which is essentially used to bind together a method name like equals with its descriptor type (Ljava/lang/Object;)Z – or as programmers know it as, boolean equals(Object). Java decomposes its methods this way, because if there are any other methods that have the same signature of boolean something(Object) then they can use the same type descriptor in the file and simply pair it with a different name.

All of the constant pool references are cross-checked by index number, which starts at 1 – the special slot 0 is only used to encode no parent for the java.lang.Object class as far as I can tell. It encodes a tree-like structure through the power of indices; the this class and super class are merely a short value pointing into the pool, so to understand what a class file is from its bytes you have to parse the full constant pool first of all.

I put together this infographic showing how the class file looks in my presentation referenced at the top of this post, which hopefully paints a picture.

Class file format infographic

Other tools for introspecting bytecode are available; I’d recommend starting off with javap and using the -c and -v options to give you a bunch of information on the class. If you want to see stepping through real bytecode on a real JVM then I recommend looking at Chris Newland’s JITWatch which shows you the bytecode as it executes and how it maps back onto the source files, using the LineNumberTable attributes encoded in the bytecode.

Compiling classes in memory

Bytecode can be read in from previously generated .class files, but you can also generate it on the fly. Many JVM languages have the ability to generate .class files, but if you want to stick with Java you can use the built-in JavaC compiler to generate code:

var javac = ToolProvider.getSystemJavaCompiler();
var fileManager = javac.getStandardFileManager(null,null,null);
var sources = fileManager.getJavaFileObjects(new File(...));
javac.getTask(null,fileManager,null,null,null,sources).compile();

You can create a file manager from the tool, but you can provide your own as well. I’ve written an InMemoryFileManager which allows you to compile Java source from a String and then obtain the appropriate .class bytes as a byte array, or even load it dynamically in a class file with a classloader. The example fits on a slide if you’re interested.

Bytecode can also be created on the fly using tools like Mockito, or through generation agents like the higher level ByteBuddy or the lower level ASM. Many of these types of tools provide simple transformation operations on existing classes, like inserting instrumentation, and there are constraints about methods (including the ability to generate accurate object maps for the compiler) which can be challenging.

Summary

The bytecode format used by classfiles is remarkably compact yet extensible. Of the constant pool types, very few new entries have arrived and only one removal since the JVM was created; the majority of new features have been added through attributes, either on the class as a whole or on the individual methods.

Bytecode has remained very similar as well; much of the innovation has come from higher up the stack in the Java compiler. The only significant changes were the introduction of invokedynamic in Java 7 (which set the ground for Lambdas arriving later) and building on top of that the CONSTANT_Dynamic_Info and CONSTANT_InvokeDynamic constant pool types.

There was a political decision to increment the bytecode number upon each major release since Java 8, although the bytecode hasn’t changed that much. One argument for doing this is you know when you have a class file that requires Java 11 runtime features, even if the bytecode could run on a Java 8 VM. Since it’s also possible to get a Java compiler to output bytecode with a lower level, it doesn’t make much of a difference, and it also allows you to use javap to find out what version of Java is required to run a particular class.

Getting started with understanding bytecode is easy; just run javap -c -v java.lang.Object or javap -c -v java.lang.String and see if you can understand what it tells you. Then try stepping through some compiled bytecode with JITWatch or the JVMulator. Finally, use the code snippets above or in the presentation to compile some Java code on the fly and then execute it. Once you’ve done that, you’ll have a much greater appreciation of what the JVM does for you every day.

Java's 25th Anniversary

2020 java

On this day, 25 years ago, Java was released as beta to great fanfare across the world. (Java 1.0 itself wouldn’t be released until 23rd January 1996). The key aspect of its appeal was the fact that programs could be created in a simple text editor, such as NotePad or vi, and then compiled into ‘bytecode’ which could then be loaded remotely into web browsers such as Netscape Navigator which had only been out for 6 months at the time, and was bundled by default with the release of 2.0 (along with its on-again, off-again nemesis JavaScript).

The fact that programs could be written, compiled on your own computer, and then hosted in a web page through the use of the (now deprecated) <applet> tag, was a massive step change in the utility of the web at the time. These days, it doesn’t seem so groundbreaking – as the web has become dominated by JavaScript and derivatives for interactivity. Of course, many demos can now be hosted with animated gifs. In fact, for your viewing pleasure, here’s what the Java 1.1 release with demos looked like:

Java Sorting Demo

The above demo content can be found in a Java 8 download in the SortDemo directory.

What’s particularly fascinating is that when Java was first released, Windows had a near monopoly on computing, with few Apple Macs clinging on for dear life, and Solaris already on its way out on the desktop. (There was NeXT, which died shortly after Java’s release, only to be reborn as Apple – that story played out a lot better than Sun’s did.)

The idea of ‘write once, run anywhere’ was never known before Java came along; at least, not for compiled languages. (C had much fewer dialects back then, but still ran into incompatibilities and there weren’t good cross-platform compilers that supported Windows as well.) The idea that you could not only write portable code, but have it downloaded and executed on another computer across the globe without recompilation was a massive boost for the early adoption, and coupled with the rise of browsers and the internet, made Java an early darling in this era.

Unfortunately, the ‘write once, run anywhere’ didn’t always live up to expectations, especially when Microsoft stopped updating their Java 1.1.3 runtime in Internet Explorer, and started to add Microsoft specific classes instead. That lawsuit dragged on, by which time it may not have mattered any more.

The other key flaw was having a collection of security vulnerabilities in the browser thanks to Java’s ubiquitous presence and general ambivalence to being updated by the end user. By the time Applets became deprecated (and why you can’t run the example in the browser today) many CVEs had been raised against the humble Java runtime in the browser, a title taken over by Flash until its demise some decades later.

But Java’s early popularity and widespread adoption meant that it spread to the server, where it still reigns supreme today. We’ve gone from single-core, 32-bit systems running on Windows and (Classic) Mac, to multi-core, 64-bit systems running predominantly Linux and NeXT derivatives, all without missing a beat.

The majority of the language will still compile just as it did in the first 1.0 releases, and the majority of code written for those early days still runs fine on today’s beefy JVMs – except a lot, lot faster. (The sort demos still run at the same speed they were programmed to as there’s a Thread.sleep with a fixed time in the code; if they were running at full speed, you wouldn’t have seen the animation, even then.)

The language (and the bytecode) hasn’t remained static over the time though – we’ve had Swing (formerly Java Foundation Classes or JFC) introduced in 1.2, generics introduced in 1.5, the invokedynamic bytecode added in Java 7 and subsequently lambdas in Java 8, with modules and others added by Java 11. The GCs have grown as well, from the simple serial collector and mark-and-sweep, to concurrent versions and multi-gigabyte concurrent collectors (now reaching terabyte limits). Concurrent programming has become ever more pervasive, and new language technologies are being added to the language a rate not seen since the early days.

However, the thing that really makes Java shine is the fact that all of the language is available as open-source. Even in the beginning, the source was available for the core Java libraries (if not the JVM implementation) so you could debug into problems that occurred in your app without losing your place in a debugger. Now that the JVM has become fully open-sourced, many other companies are working on the Java language and the JVM runtime in order to make it bigger and better than before.

The last 25 years has brought a lot of joy to millions of developers and billions of users, including a formative part of my career. I started with Java’s early betas in 1995, helping a friend to learn the basics of object-orientation before it was cool (and perhaps subsequently, uncool). We wrote a crossword applet that allowed you to parse ASCII representations of a board and display them in a graphical manner, and allow you to type and fill in the crossword via a web page. Nowadays, of course every newspaper worth their reputation has got an on-line crossword with much the same thing; but this was in the days before Google and Wikipedia existed.

My career followed with becoming a Java developer, trainer and consultant; I started a business with a friend and rode the wave as people migrated from C++ to Java in their droves. (These days, everyone comes out of university already knowing Java and on-line tutorials and videos have made training a niche subject.) I then worked my way up and along Java, writing for InfoQ and many blog posts, as well as contributing to open-source projects like Apache and Eclipse. Indeed, my involvement with Eclipse goes back to the early days of working delivering IBM’s training OB73 and OB74 courses, and using Visual Age for Java followed by WebSphere Studio which was based on Visual Age’s technologies but made open-source in Eclipse in November 2001.

My Java career reached its zenith earlier this year when I became a Java Champion thanks to the support and kind votes of the other champions. It’s really not overblown to say that I owe my career and livelihood to a small language formerly called Oak or Green, and it will be fascinating to see what happens to it over the next 25 years of development. I’ve truly been Moved By Java.

Using dnsmasq to block DNS requests

2020

Recently I have noticed that there’s an uptick in spamvertising on my home network, and as such, I decided to spend some time investigating using a Raspberry Pi to host a DNS service which would filter such spam sites out.

I initially looked at Pi-Hole which provides a really simple out-of-the-box experience for doing DNS blocking, with a nifty user interface for adding black or whitelist hosts on the fly, and a report of how well the DNS is working. For those with limited technical experience, it’s an easy-to-use experience and I can recommend it on those points. Version 5 has just been released as I write this, so you may want to look at that.

Pi-Hole

Pi-Hole has some advantages – for example, it doesn’t just run on a Raspberry PI – and it has some built-in lists that it uses to do ad blocking, which is a good start.

However, after using it for a while, it became clear to me that in essence it’s a soft fork of dnsmasq which is available on many upstream repositories already. The Pi-Hole fork of dnsmasq is called pihole-FTL and is, in essence, a dnsmasq process running inside something with a network front end for doing querying and dynamic reloading of data.

The other problem with Pi-Hole is that it is, in essence, a bunch of PHP web pages (potentially running as root) in a server and communicating via a binary protocol to the local daemon, so there’s a lot of potential places where security issues could occur. It also fires up a lightweight HTTP server running on the host (there’s a configuration option to turn it off) which again increases the chance of something going wrong.

Dnsmasq

As a result, I decided to move away from the Pi-Hole configuration, and use a vanilla dnsmasq implementation instead; which has the following advantages:

  • Less custom code running in dnsmasq, so less potential for security issues
  • Supported by upstream repositories natively, rather than an add-on
  • All of the hard work by Pi-Hole is actually handled by dnsmasq anyway
  • Less maintenance to worry about in the future

There are of course disadvantages – it doesn’t provide a helpful webpage which says “This domain is blocked; click here to unblock it”, but I didn’t want that anyway. The other minor disadvantage is that the hosts parsing format supports a missing address, which is parsed as NXDOMAIN – this doesn’t apply to the vanilla dnsmasq daemon.

Configuration

The default configuration for dnsmasq is pretty well commented, and there’s a great manpage with all the configuration options defined in there. However, I thought I would split it up into different sections that you could enable or customise what you want, and I’ve made it available on GitHub.

Despite the name, dnsmasq also handles TFTP, PXE and DHCP operations on both IPv6 and IPv4 addresses. I have customised mine to allow for DHCP and DNS on both IPv4 and IPv6 at the moment, but I’m going to investigate the boot options at a later stage.

Some of the defaults have been updated to allow sensible values in place; they are all commented in the individual files which you can pick-and-choose from. For example, there are multiple options for the DNS server:

Recently, Cloudflare has announced “Cloudflare for Families” which builds on the 1.1.1.1 name server with a 1.1.1.2 name server that blocks malware attempts, and a 1.1.1.3 that also blocks porn. These may be useful for families, and over time, I expect the range of blocks to grow rather than fade – which will be especially useful for fast-moving targets like those trying to fool you into handing over your Apple/Facebook logins. I’ve set up a configuration file for those as well.

The idea is that you can clone the example repository, adjust it for your local environment, and then run it in your /etc/dnsmasq.d/ directory.

Blocking hosts

The main reason I did this was to block hosts. In dnsmasq, the syntax for blocking a host is:

address=/example.com/

which roughly translated means ‘For domains ending in example.com, send the result to NXDOMAIN. You can also specify a particular address; other block hosts use 0.0.0.0 as a host:

address=/example.com/0.0.0.0

Secondly, there’s a form that can read in a hosts file (man 5 hosts) that can be used to serve 0.0.0.0 addresses (but not NXDOMAIN).

addn-hosts=/path/to/blocked.hosts

The advantage of an additional hosts file is that a SIGHUP will ask the dnsmasq daemon to reload the hosts file dynamically without stopping and starting the service.

If you want to ensure that a local domain doesn’t get forwarded out, or you have an alternative server, then you can use the server option instead:

server=/mynet/192.168.1.1

This will delegate all mynet domains to the 192.168.1.1 server, but then cache the results and serve them to clients quicker.

Finally, if you want to blackhole entire TLDs, you can do so with either the local directive (i.e. make the machine responsible for the domain) or with the address=/.biz/ directive. Many newer TLDs are full of spam sites, and countries that you don’t regularly visit or shop from could be on this list.

DHCP

Dnsmasq provides a DHCP service for you, in case you want to use it to allocate your hosts on demand. One advantage of having this wired into the server is that it will give you a mapping between the DHCP allocated hosts and DNS names for free, so you only have to have them in one place.

The DHCP hosts have a range that you can specify along with a lease time:

dhcp-range=192.168.0.50,192.168.0.150,12h

You can configure the DHCP server to send out its address for the router, for both IPv4 and IPv6, which means that hosts using DHCP will automatically pick up the host:

dhcp-option=option:router,192.168.1.1
dhcp-option=option6:dns-server,[::]

You can specify the host-record for binding a particular name, and a dhcp-host for setting a machine based on its mac address:

host-record=dns.example.com,192.168.1.1
dhcp-host=aa:bb:cc:dd:ee:ff,192.168.1.1,dns.example.com

It is also possible to have a cname record, but only for those hosts that are already known to dnsmasq … so it’s not possible, for example, to have a cname pointing to a host elsewhere.

cname=ns.example.com,dns.example.com

There are other configuration options, such as setting an auth-server and auth-zone along with an auth-soa which will synthesise records for the SOA name type; these are left to the reader’s investigation. For those using zeroconf/bonjour/mDNS or SPF, you can also add generic SRV and TXT records to the DNS zones hosted. These are left as an exercise for the reader.

Summary

Pi-Hole is a great out-of-the-box experience, but has a wider security attack area and drags in interpreted languages, which may not be appropriate on a DMZ hosts. Using dnsmasq is perfectly capable, provided that you can curate your own blacklists for domains, or write scripts that do that for you. (An older version of Pi-Hole used native dnsmasq, but was replaced to give stats that could otherwise be grepped from the lookup logs.)

Hopefully the partitioned configuration files in https://github.com/alblue/dnsmasq-example will give you a start point to run your own DNS/DHCP server, optionally with blocking capabilities.