GSE Nordic 2015 CICS Java – Beauty and the Beast After giving sessions about Java in the past few years that tell system programmers they should do Java on IBM z Systems and that Java is just like every other language, this session tries to explain why Java is a bit different in operating and handling. We will compare COBOL/PLI/Assembler with Java and provide insight into how the Java technology works on z Systems. Come along if you want to find out the answers to questions such as: Why can't you phase in a Java program? What does the JIT compiler do and how does it work? What is the development process of Java applications? How do I debugging and logging in Java applications?
GSE Nordic Region Conference 2015
3rd June 2015
Ian Burnett
IBM CICS TS for z/OS Performance Test Lead
ian.burnett@uk.ibm.com
@IanBurnett
This section will cover at a very high level some concepts behind the Java language.
Highlighted boxes show languages (or their direct derivatives) which are supported within CICS.
FORTRAN – 1954
ALGOL – 1958
Lisp – 1958
COBOL – 1959
PL/I – 1964
Smalltalk – 1971
C – 1971
C++ – 1983
Oak – 1991
Java – 1995
Source: www.levenez.com/lang/
Java differs in some parts quite a lot from traditional languages like assembler, PL/I or COBOL.
This slide covers some of the major differences – some of these differences are covered in greater depth later in this presentation.
In this case, "traditional" refers to COBOL, PL/I, C, and LE assembler applications. References to COBOL in this presentation are not strictly limited to that particular language – it is merely shorthand for "CICS applications written without the Java Runtime Environment".
Before starting to compare Java with other languages, this section will recap how a traditional application manages storage, how it receives control, and how it fits within the rest of the CICS and z/OS environment.
I am a CICS person, so will be concentrating on the runtime operation of CICS, rather than batch or IMS.
The program is loaded into storage from disk, the relocatable references are resolved and then the load module is ready for use.
When the program is called from CICS, a new LE enclave is created and initialised.
But what is LE and an enclave?
A runtime library brings all the code that is provided by a language and not part of the user program (storage management, I/O routines, math functions...). This approach has the advantage that only the library code is tied to the underlying hardware, and without forcing the compiler or the application to require too many adaptations for the environment in which it is executing.
When all the languages brought their own runtime, they expected to be alone in the world. Customers had problems to run different languages side by side, and with CICS, it is possible to run many languages side-by-side in the same environment, often calling each other.
Initialising a new Language Environment enclave involves:
Obtaining initial storage
Condition handlers initialized
Active member language specific run-time is initialised
Once the application ends and 'returns' to LE:
The LE environment is terminated
System resources obtained during initialisation and throughout the execution of the application are cleaned-up
LE definitions:
Region : the range of storage the application set runs in
Process : set of applications that accomplish a task
Enclave : an application - set of modules that accomplish some subtask
(Not shown) Thread : dispatchable unit of work that shares storage with others in the enclave
An LE enclave is created for the first program within a CICS task, and for each subsequent EXEC CICS LINK command. Subroutines called (e.g. using COBOL's CALL statement) do not get their own LE enclave.
Remember: each process is a CICS task. In the CICS environment, an enclave is often referred to as a "run-unit".
WEB = Work Element Block
ASCB = Address Space Control Block
SRB = Service Request Block
TCB = Task Control Block
OTE = Open Transaction Environment (e.g. L8, L9, X8, X9 TCBs)
In CICS, a task has one or more LE enclaves that are separated from all other Tasks in the address space.
A task can have multiple LE enclaves if control is transferred from one program to another using an EXEC CICS LINK or EXEC CICS XCTL command.
Java packages are a virtual form organisation, based around Java's Object-Oriented principles.
A classpath will be covered later.
There are some features missing from this slide: jar visibility, versioning, and dependencies. OSGi addresses all of these issues.
Everything in Java is executed within a virtual machine.
One of the key aspects of Java is that memory management is performed transparently by the JVM.
A daemon thread is one which executes in the background, and not part of the main user application.
Java is never compiled to machine instructions upfront - it is interpreted and compiled during runtime. This provides platform neutrality, leading to the claim that Java is "Write once, run anywhere".
A Java application usually only interacts with the JVM and the operating system via the standard Java runtime libraries.
The runtime libraries often run as pure Java, but some (such as the I/O routines) include native code to access the underlying operating system and hardware.
It is possible for a Java application to access native code (e.g. assembler) through the Java Native Interface (JNI) to access platform-specific functionality. Using native code restricts the portability of a Java application, because by their very nature, they are specific to hardware and O/S combinations.
In this section we look at what is happening under the covers. Java developers rarely need to know about the bytecode, but it gives an understanding of the parts at work.
The compilation step often happens transparently on the development workstation, whenever the source file is saved.
Unlike many other compiled languages, a Java compiler produces platform-neutral bytecode. This bytecode from the compilation step is suitable for running unmodified on any platform.
In the case of Java applications in CICS, this bytecode is stored on HFS, usually packaged within a jar file.
When a class is required at runtime, the JVM loads the bytecode for that class from HFS.
As with a traditional load module, the loading stage finds the class file on disk and puts a copy into main memory.
The linking stage is broken into three stages:
Verifying – ensuring the class file adheres to the Java and JVM specifications.
Preparing – allocating storage for the various components within a class
Resolving – updating all indirect references to direct references within the JVM
Initialising involves setting class fields to their initial values, and running the class initialiser methods.
An analogy to a Java classpath is a load library concatenation.
When attempting to load a class, the Java runtime searches all the jar files found in the classpath, in order, until a match is found.
If no match is found, then the environment throws a java.lang.ClassNotFoundException which is propagated to the user application.
Traditional classpath was per-JVM, which introduces problems when refreshing classes.
OSGi removes some of the static nature of a classpath, plus adds multi-versioning and dynamic life-cycling of applications. This allows an application to be dynamically updated during the lifespan of the JVM. See the discussion later about why PHASEIN is not supported within a JVM.
A simple Java method to accept two integer numbers and return their sum.
The left side shows the Java source code, while the right side shows the resulting bytecode mnemonics after compilation.
Java is stack-based: below is a breakdown of the bytecode:
iconst_0 – load an integer value of zero onto the stack
istore_3 – store the top element of the stack into local variable 3
iload_1 / iload_2 – push local variables 1 and 2 onto the stack
iadd – pop top two integer values off the stack, add them together, and push the result
ireturn – pop top integer value off the stack and return control to the method's invoker
Old and naive JVM implementations simply take the bytecode, step through it one instruction at a time, and then perform the stack manipulations using native code. This is the old "interpreted" approach, and is very slow.
These initial implementations contributed in no small part to the widespread belief that Java as a language is slow.
In this diagram, the "Java Application" box represents both the user's code, and the runtime libraries.
The JIT Frontend is common in all IBM JVM Implementations, the backend is specific to System z.
The JIT compiler is continuously sampling the methods being executed inside the JVM.
Optimisation is a non-trivial process, so only frequently-called methods are optimised to the higher levels. The more frequently a method is invoked, the more aggressive the optimisation.
There are multiple levels of compilation: noOpt, cold, warm (initial compilation usually occurs to this level), hot, veryHot, and scorching.
This progressive optimisation means that you are likely to see performance improve over the first tens of thousands of Java transactions into CICS. For this reason be careful when undertaking performance comparisons to ensure that the JVM is fully "warmed up" before taking measurements.
IBM uses its own implementation of a JVM on mainframes that uses the underlying platform architecture.
Unlike compiling a traditional application, a JVM is aware of the hardware on which it is executing. In turn, bytecode is compiled to the most optimal series of System z instructions for the level of hardware in use. For example, Java 8 can exploit SIMD on the IBM z13.
Within CICS, a JVM is a very complex C application running within its own LE enclave.
As part of the definition for a JVMSERVER, one of the attributes is the compiled LE options configuration. The default supplied options (DFHAXRO) are correct in almost every scenario.
The LE Enclave is configured to always obtain new Heap Storage
Bear in mind that the JVM Heap Size is managed by the JVM, so a JVM can run out of memory
initial 64-bit heap of 100MB
initial 31-bit heap of 4MB
Both areas allow incremental expansion to increase
The Files generated by the JVM are controlled by:
DC C'ENVAR("_EDC_UMASK_DFLT=nnn")'
The output that is produced by the RPTO and RPTS options is written to the CESE transient data queue
We visited this diagram earlier in the presentation when examining how a COBOL program is dispatched within CICS.
Removing the unchanged items, we can see that a JVM server within CICS is one single LE enclave.
Java applications executing in CICS are always dispatched on a T8 TCB. This TCB then corresponds to a pthread within the JVM server. This then corresponds to a Java thread, which executes the Java application code.
Aside from the application threads, there are several other threads which execute in the background of a JVM, such as the Garbage Collection (GC), or JIT compiler threads.
gencon = generational plus concurrent garbage collection
Garbage collections are more frequent, but have less impact (shorter) in the nursery area.
Garbage collections are less frequent, but have a bigger impact (longer) in the tenured area.
See http://www.ibm.com/developerworks/websphere/techjournal/1106_bailey/1106_bailey.html
Shared classes can be configured using the JVM options and is transparent to CICS operation. See the Java manuals for details.
Restricts Java heap sizes up to 29 GB, but makes much more effective use of the space available by reducing the size of each object.
You can use JDWP to replace code at runtime but that is a debug-oriented configuration, not for production use.