Pages

dimanche 28 octobre 2012

Tweet
During the latest Hackergarten, I worked with Pierre-Yves Ricau on the project AndroidAnnotations. This project consists in an annotation processor for our Android applications. If one can easily find documentation on how to debug an annotation processor using Eclipse, it is not the case for other IDE such as IntelliJ. In this article, we will see how we can achieve such a thing regardless of the IDE.

Introduction

First things first, a definition. An annotation processor is a library that can be added to the javac compiler and that can enhance our source code if the right annotations are used. It allors us to write more concise and mode expressive code.

Note that "enhancing source code" needs to be clarified, as JSR-269 does not allow modifications of existing .java files. However, it is possible to create new .java files, just like AndroidAnnotations does, or to modify generated bytecode, like project lombok does (with a little bit of hacking, though).

A great variety of IDE (Eclipse, NetBeans, IntelliJ) supports annotation processors, whether using or developing it. But there is a lack of documentation on debugging such tools. For instance, very few pages references debugging an annotation processor in Eclipse, and all mention the creation of a plugin.

During the HackerGarten, we chose a different method : since annotation processors are a java library that is added to javac, we "only" have to launch javac in debug mode, and connect to it. In this article, we will focus on IntelliJ but the solution will work for any IDE.

Adding the annotation processor to the compiler

Once the project is imported in Intellij, we can add the annotation processor to the compilation process. Do to so, we have to enter the fully-qualified name of AndroidAnnotations processor, the module on which we want to process the annotations and an output folder. There are lots of test classes in the module functionnal-test-1-5, let's use this one and specify the output directory target/processed.

Checking that annotations are processed

When we process the class BeanInjectedActivity.java, we can see that a new java source file named BeanInjectedActivity_.java is created in the folder target/processed. This new file contains the code of the original class and many more statements that have been added by the annotation processor.

Setting javac in debug mode

We know that any JVM instance can be started in debug mode using some JVM Options, such as -Xdebug. Unfortunately, in our case, we have to use the javac command which is not itself a JVM instance, so it is not possible to pass it such options.

However, javac *creates* a JVM instance in which the compilation process takes place. We can send JVM options to this instance using the command line parameter -J, as stated in the manpage :.

-J option
Pass option to the java launcher called by javac. For example, -J-Xms48m sets
the startup memory to 48 megabytes. It is a common convention for -J to pass
options to the underlying VM executing applications written in Java.

We can therefore use the options -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 to run the compilation JVM in debug mode. Let's add those options in IntelliJ and set a breakpoint in the init method of AndroidAnnotationProcessor.java.

Adding a debugging configuration and debugging

We can now add a remote debugging configuration on port 5005 in IntelliJ and process the class BeanInjectedActivity.java. We see that the compilation process hangs on "make", which is as expected since the compiler is now waiting a connection from a debugger. When we start the remote debugging configuration in IntelliJ, we see that :

the compiler stops at the breakpoint we added in AndroidAnnotationProcessor.init

the stacktrace mentions the javac compiler itself.

Conclusion

We saw that the javac compiler itself could be run in debug mode and therefore, any annotation processor can be debugged using any IDE that supports setting additional javac arguments.

Do you have other techniques do debug annotation processors ? A particular opinion about them ? Let's talk about it in the comments !

samedi 12 mai 2012

Last week, I was looking for a complete list of the Java Hotspot JVM options. Problem : the information you may find on the web will mostly be related to -Xmx or -XX:MaxPermSize, and this is typically what I was not looking for :).

I found the Oracle Hotspot page where about 90 options are described, but I was not happy with it for there is more than 600 available options. So I downloaded the source of OpenJDK and extracted the relevant information. Please find here the complete list of every "product" JVM option.

These options are available as of Hotspot JVM 1.7.0_04.

The different types of option

The options listed in this article are extracted from the global configuration file "globals.hpp" of the Hotspot source. This file contains several types of options, among which :

product - These options are available in every JVM build

diagnostic - These options are available in every JVM build only if the option -XX:+UnlockDiagnosticVMOptions is specified

develop - These options are only available in the debug JVM build

experimental - These options are only available in the debug JVM build and can be used if the option -XX:+UnlockExperimentalVMOptions is specified

In this article, I will only list the "product" JVM options in alphabetical order.

InitialSurvivorRatio

InitialTenuringThreshold

Type : IntegerDefault value : 7

Description : Initial value for tenuring threshold

InitiatingHeapOccupancyPercent

Type : Positive IntegerDefault value : 45

Description : Percentage of the (entire) heap occupancy to start a concurrent GC cycle. It us used by GCs that trigger a concurrent GC cycle based on the occupancy of the entire heap, not just one of the generations (e.g., G1). A value of 0 denotes 'do constant GC cycles'.

Inline

Type : BooleanDefault value : true

Description : enable inlining

InterpreterProfilePercentage

Type : IntegerDefault value : 33

Description : NON_TIERED number of method invocations/branches (expressed as % of CompileThreshold) before profiling in the interpreter

MarkStackSizeMax

MarkSweepAlwaysCompactCount

Type : IntegerDefault value : 4

Description : How often should we fully compact the heap (ignoring the dead space parameters)

MarkSweepDeadRatio

Type : Positive IntegerDefault value : 5

Description : Percentage (0-100) of the old gen allowed as dead wood. Serial mark sweep treats this as both the min and max value. CMS uses this value only if it falls back to mark sweep. Par compact uses a variable scale based on the density of the generation and treats this as the max value when the heap is either completely full or completely empty. Par compact also has a smaller default value; see arguments.cpp.

MaxBCEAEstimateLevel

Type : IntegerDefault value : 5

Description : Maximum number of nested calls that are analyzed by BC EA.

MaxBCEAEstimateSize

Type : IntegerDefault value : 150

Description : Maximum bytecode size of a method to be analyzed by BC EA.

TLABWasteTargetPercent

TargetPLABWastePct

Description : target wasted space in last buffer as pct of overall allocation

TargetSurvivorRatio

Type : IntegerDefault value : 50

Description : Desired percentage of survivor space used after scavenge

TenuredGenerationSizeIncrement

Type : Positive IntegerDefault value : 20

Description : Adaptive size percentage change in tenured generation

TenuredGenerationSizeSupplement

Type : Positive IntegerDefault value : 80

Description : Supplement to TenuredGenerationSizeIncrement used at startup

TenuredGenerationSizeSupplementDecay

Type : Positive IntegerDefault value : 2

Description : Decay factor to TenuredGenerationSizeIncrement

ThreadPriorityPolicy

Type : IntegerDefault value : 0

Description : 0 : Normal. VM chooses priorities that are appropriate for normal applications. On Solaris NORM_PRIORITY and above are mapped to normal native priority. Java priorities below NORM_PRIORITY map to lower native priority values. On Windows applications are allowed to use higher native priorities. However, with ThreadPriorityPolicy=0, VM will not use the highest possible native priority, THREAD_PRIORITY_TIME_CRITICAL, as it may interfere with system threads. On Linux thread priorities are ignored because the OS does not support static priority in SCHED_OTHER scheduling class which is the only choice for non-root, non-realtime applications. 1 : Aggressive. Java thread priorities map over to the entire range of native thread priorities. Higher Java thread priorities map to higher native thread priorities. This policy should be used with care, as sometimes it can cause performance degradation in the application and/or the entire system. On Linux this policy requires root privilege.

ThreadPriorityVerbose

Type : BooleanDefault value : false

Description : Print priority changes

ThreadSafetyMargin

Type : Positive IntegerDefault value : 50*M

Description : Thread safety margin is used on fixed-stack LinuxThreads (on Linux/x86 only) to prevent heap-stack collision. Set to 0 to disable this feature