uJVM: platform for running Java applications on microcontrollers (MCUs)

  • Tutorial

ИллюстрацияHello dear readers. In this article I will try to demonstrate how to run a Java Virtual Machine and Java applications on microcontrollers. This idea may sound quite outlandish by itself: why use Java on microcontrollers where each byte of RAM and each CPU cycle are precious commodity? There's nothing like native C for microcontrollers — even C++ is rarely used! Yes, I've heard that discussion (and took part in it) for many years. Well, I will try to explain «why», as well as «how», in this article. So, anyone who wants to understand how MCU implementation of JVM works; see an unusual approach to multi-platform project development; take part in Open Source project; or just have fun — welcome onboard, uJVM is ready to take off! People who are ready to criticize can stay near the runway and watch us climb (or crash) from a safe distance


Intro


As a starting point, please watch the video below.


As far as I understand, this video describes something that consists of OS, JVM and libraries. It can be run on a variety of microcontroller boards (you can find and watch other videos published by the same company). However, this solution is not open-source, and I wanted to make some experiments, so I started to look for alternatives. I've chosen uJVM — source code is available, build instructions are good, and anyone who wants to participate in development is welcome.

In this article I will describe overall architecture, byte code loading process, environment setup and getting first results: compiled JVM and a traditional HelloWorld.java application that we will run on x86_64/Linux platform. In subsequent articles we will see how to run uJVM on various microcontroller boards, port uJVM to new platforms, add new Java classes and native classes. So let's start.

uJVM


uJVM is designed in accordance to overall architecture requirements described in The Java Virtual Machine Specification Java SE 7 Edition and consists of 3 parts:

  • Class loader uJVM is responsible for loading classes stored in .class or .jar files into RAM, linking and initialization.
  • Managed memory areas uJVM supports threads, storing of special registers, and memory allocation for stack, heap and thread-specific storage.
  • Execution engine. Due to stringent RAM limitations, uJVM offers only bytecode interpreter as an execution engine — there are no JIT- or AOT-compilers. Engine uJVM can access Java source code, H/W drivers (e.g., UART, GPIO and SysTick for most of supported platforms) and native methods.

As of now uJVM is available for the following H/W platforms:

image

uJVM can be ported as an application for a variety of RTOSes, for example:


— however, we will not discuss use of uJVM under RTOSes in this article.

uJVM is an open source implementation of JVM for embedded systems with stringent resource limitations. In a minimal configuration, it can run in about 6 kB of RAM and requires less than 45 kB of ROM for storage. Build system allows the developers to configure uJVM at a build time, disabling unneeded features to conserve resources or enabling support of specific H/W devices (for example, FPU support to accelerate calculations). Let's see how to build and run it…

Setting up the environment and downloading the source code


1. Installing the necessary Linux packages


To build uJVM from source code, I use Ubuntu 16.04, but there are no strict Linux version requirements. Packages listed below should be installed:

$ sudo apt-get install git gcc make openjdk-8-jdk-headless gperf flex bison libncurses5-dev texinfo g++ curl pkg-config autoconf libtool libtool-bin libc6:i386 libc6-dev:i386 gcc-multilib doxygen doxygen-gui

2. Specifying Java compiler location (value of JAVA_HOME variable)


Please note that Java compiler (javac) directory must be known to build system. To achieve that, you need (according to Oracle Java documentation) set the value of environment variable specifying the compiler location. A simple way to set this variable is to use shell commands:

$ export JAVA_HOME = _java_compiler_directory_

Here _java_compiler_directory_ must not include last bin directory in the path; e.g., if compiler is located in /usr/bin directory, JAVA_HOME value must be /usr. If you want to assign JAVA_HOME value permanently for every user in the system, you can add this variable into /etc/environment file by using the following command:

$ echo "JAVA_HOME=\"/usr\"" | sudo tee -a /etc/environment

Same effect can be achieved by adding JAVA_HOME variable into /etc/profile file by using the following command:

$ echo  "export JAVA_HOME=\"/usr\"" | sudo tee -a /etc/profile

After executing either of these commands, you need to log out of shell and log in again.

3. Building kconfig-frontends for Linux (optional)


Building kconfig-frontends for Linux is a simple and straightforward process using autotools. Usually you just need to download the source archive, unpack it and start build:

$ ./configure && make && sudo make install


3.1 Downloading and unpacking the source code archive

$ curl -O http://ymorin.is-a-geek.org/download/kconfig-frontends/kconfig-frontends-3.12.0.0.tar.xz
$ tar -xf kconfig-frontends-3.12.0.0.tar.xz
$ cd kconfig-frontends-3.12.0.0

3.2 Patching

If your system uses gperf 3.0.4 or earlier, just go to part 3.3. Otherwise, read on.

gperf 3.1 (released on January 5 2017) changed the type used for specifying data length in generated functions — instead of unsigned int, it started to use size_t. This causes the build to fail with a following message:

CC     libkconfig_parser_la-yconf.lo
    In file included from yconf.c:234:0:
    hconf.gperf:141:1: error: conflicting types for 'kconf_id_lookup'
    hconf.gperf:12:31: note: previous declaration of 'kconf_id_lookup' was here
     static const struct kconf_id *kconf_id_lookup(register const char *str, register unsigned int len);
                                   ^~~~~~~~~~~~~~~
    make[3]: *** [Makefile:456: libkconfig_parser_la-yconf.lo] Error 1
    make[2]: *** [Makefile:350: all] Error 2
    make[1]: *** [Makefile:334: all-recursive] Error 1
    make: *** [Makefile:385: all-recursive] Error 1

To fix this issue, execute the following commands:

$ curl -O https://gist.githubusercontent.com/KamilSzczygiel/d16a5d88075939578f7bd8fadd0907aa/raw/1928495cfb6a6141365d545a23d66203222d28c0/kconfig-frontends.patch
$ patch -p1 -i kconfig-frontends.patch
$ autoreconf -fi

3.3 Configuring kconfig-frontends

Recommended configuration for kconfig-frontends is:

$ ./configure --enable-conf --enable-mconf --disable-shared --enable-static

3.4 Compiling and installing kconfig-frontends

$ make
$ sudo make install
$ sudo strip /usr/local/bin/kconfig-*

4. Cloning the uJVM repository:


$ git clone https://github.com/Samsung/uJVM.git

Compiling the uJVM


Select PLATFORM x86_64_linux. After that, create default build context by executing following command:

$ cd uJVM/
$ make PLATFORM=x86_64_linux create_context

Include HelloWorld.java into build. To do that, open configuration util:

$ make menuconfig

Go to Java application Configuration menu



and select «Hello world example»



This will include HelloWorld.java into build sequence:

/**
 * @file		japps/hello_world/HelloWorld.java
 * @brief		Example how to use String and SysLog classes
 *
 * @copyright	Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved.
 * @author		Taras Drozdovskyi t.drozdovsky@samsung.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

import ujvm.lang.*;

public class HelloWorld {
	public static void main() {

		SysLog.log("Hello world!\n");

		String hello = "Hello world!\n";
		SysLog.log(hello);

		byte[] bytes = { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n' };
		String str = new String(bytes);
		SysLog.log(str);
	}
}

Don't let the SysLog.log(str) confuse you — it's simply a low-level analog of System.out.println(str). Standard Java library is not fully implemented in uJVMyet, but I hope someone will do that someday.

Save and compile:

$ make

And, finally, run the application:

$ make run



Other applications listed in build system can be built and run in the same fashion.

Next article will cover building and running uJVM on microcontroller boards. However, if you're impatient to try, just read the documentation — it covers the usual sequence of operations pretty well.

Links:
The Journal of Open Source Software — uJVM: Lightweight Java Virtual Machine for embedded systems;
uJVM Documentation.
Share post

Similar posts

Comments 2

    0

    Author of uJ recently blamed Samsung that uJVM used his project without approval and broke uJ licence. Could you please comment this situation? original post

      0
      Firstly, licensing terms of uJ source files remain valide — uJVM documentation explicitly mentions them as third-party components with separate licensing terms. In COPYING file, there is a list of files for which specific licensing terms apply, including uJ source. Secondly, uJVM was never meant as a commercial project and is not sold, so we are not trying to rip off the uJ author — objectives of uJVM project were research, evaluation and proof-of-concepts. Thirdly, we've updated licensing part of readme file to clarify the issues risen by uJ author.

      Only users with full accounts can post comments. Log in, please.