Showing posts with label jvm. Show all posts
Showing posts with label jvm. Show all posts

Friday, 24 June 2011

How different variables stored in java - on Heap and stack

  • Instance variables and objects live on the heap.
  • Local variables live on the stack.

Let’s take a look at a Java program, and how its various pieces are created and map into the stack and the heap:

1. class Cub{ }
2.
3. class Lion {
4. Maine c; // instance variable
5. String name; // instance variable
6.
7. public static void main(String [] args) {
8.
9. Lion d; // local variable: d
10. d = new Lion();
11. d.go(d);
12. }
13. void go(Lion lion) { // local variable: Lion
14. c = new Cub();
15. lion.setName("Bakait");
16. }
17. void setName(String LionName) { // local var: LionName
18. name = LionName;
19. // do more stuff
20. }
21. }

  This is how the variables and methods get placed in the stack and heap during execution of the above piece of code.
  • Line 7—main() is placed on the stack.
  • Line 9—reference variable d is created on the stack, but there’s no Lion object yet.
  • Line 10—a new Lion object is created and is assigned to the d reference variable.
  • Line 11—a copy of the reference variable d is passed to the go() method.
  • Line 13—the go() method is placed on the stack, with the Lion parameter as a local variable.
  • Line 14—a new Maine object is created on the heap, and assigned to Lion’s instance variable.
  • Line 17—setName() is added to the stack, with the LionName parameter as its local variable.
  • Line 18—the name instance variable now also refers to the String object.
  • Notice that two different local variables refer to the same Lion object.
  • Notice that one local variable and one instance variable both refer to the same String Aiko.
  • After Line 19 completes, setName() completes and is removed from the stack. At this point the local variable LionName disappears too, although the String object it referred to is still on the heap.

new vs newInstance() performace

Everybody knows (or should) that any operation performed using reflections is bound to be slower than its static, compiled counterpart. But as jdk versions are increasing, this time is decreasing.

I was making the classic micro-benchmarking error of not warming up the VM, so here’s the slightly modified version of the code:

public class TheCostOfReflection {

public static void main(String[] args) throws Exception {
int nObjects = 100;
Object[] objects = new Object[nObjects];

// warm up a bit
int warmupLoops = Math.min(nObjects, 100);
for (int i = 0; i < warmupLoops; i++) {
testNewOperator(nObjects, objects);
testNewInstance(nObjects, objects);
}

System.gc();

// using new
System.out.println("Testing 'new'...");
long newTime = testNewOperator(nObjects, objects);
System.out.println("Took " + (newTime / 1000000f) + "ms to create " +
nObjects + " objects via 'new' (" +
(nObjects / (newTime / 1000000f)) +
" objects per ms)");
System.gc();
// using newInstance() on class
System.out.println("Testing 'newInstance()'...");
long niTime = testNewInstance(nObjects, objects);
System.out.println("Took " + (niTime / 1000000f) + "ms to create " +
nObjects + " objects via reflections (" +
(nObjects / (niTime / 1000000f)) +
" objects per ms)");
// ratio
System.out.println("'new' is " + (niTime / (float) newTime) +
" times faster than 'newInstance()'.");
}

private static long testNewInstance(int nObjects, Object[] objects)
throws Exception {
long start = System.nanoTime();
for (int i = 0; i < nObjects; i++) {
objects[i] = Object.class.newInstance();
}
return System.nanoTime() - start;
}

private static long testNewOperator(int nObjects, Object[] objects) {
long start = System.nanoTime();
for (int i = 0; i < nObjects; i++) {
objects[i] = new Object();
}
return System.nanoTime() - start;
}
}

Running this with -Xms512m -Xmx512m* yields the following result:

Testing 'new'...
Took 7.610116ms to create 1000000 objects via 'new' (131404.05 objects per ms)
Testing 'newInstance()'...
Took 184.72641ms to create 1000000 objects via reflections (5413.41 objects per ms)
'new' is 24.273798 times faster than 'newInstance()'.

Nearly 25x slower. If you lower the value of nObjects the difference gets smaller, though.
Testing 'new'...
Took 0.002794ms to create 100 objects via 'new' (35790.98 objects per ms)
Testing 'newInstance()'...
Took 0.021581ms to create 100 objects via reflections (4633.7056 objects per ms)
'new' is 7.7240515 times faster than 'newInstance()'.

Adding a -server flag to the VM parameters did the trick, though.
Testing 'new'...
Took 8.862299ms to create 1000000 objects via 'new' (112837.54 objects per ms)
Testing 'newInstance()'...
Took 13.91287ms to create 1000000 objects via reflections (71875.88 objects per ms)
'new' is 1.5698942 times faster than 'newInstance()'.

And for 100 object instantiations:
Testing 'new'...
Took 0.002864ms to create 100 objects via 'new' (34916.20 objects per ms)
Testing 'newInstance()'...
Took 0.006007ms to create 100 objects via reflections (16647.24 objects per ms)
'new' is 2.0974162 times faster than 'newInstance()'.

When the JIT magic kicks in, you get some SERIOUS improvements.
* NOTE: 512m to avoid garbage collection interference. You can run with -XX:+PrintGCDetails to ensure that the GC isn’t acting during the instantiation loops. You should see two full GC passages though, because of lines 14 and 23.
I did run the tests a dozen times for each test; the outputs posted fall within the averages.
Tip of the day: Even though I got far better results after introducing the warmup phase, I still suggest you use the good ol’ Factory pattern. Unless you’re creating a few dozen instances in a non-critical zones of your code, the factory is the way to go, as newInstance() can never (?) get as fast as new.


Saturday, 11 June 2011

Memory Leak : static collection object may be the reason

A very simple example of a memory leak would be a java.util.Collection object (for example, a HashMap) that is acting as a cache but which is growing without any bounds.
public class MyClass {
static HashSet myContainer = new HashSet();
public void leak(int numObjects) {
for (int i = 0; i < numObjects; ++i) {
String leakingUnit = new String("this is leaking object: " + i);
myContainer.add(leakingUnit);
}
}

Now in the main method:
public static void main(String[] args) throws Exception {
{
MyClass myObj = new MyClass();
myObj.leak(100000); // One hundred thousand }
System.gc();
}


In the above program, there is a class with the name MyClass which has a static reference to HashSet by the name of myContainer. In the main method of the class: MyClass, (in bold text) within which an instance of the class: MyClass is instantiated and its member operation: leak is invoked. This results in the addition of a hundred thousand String objects into the container: myContainer. After the program control exits the subscope, the instance of the MyClass object is garbage collected, because there are no references to that instance of the MyClass object outside that subscope. However, the MyClass class object has a static reference to the member variable called myContainer. Due to this static reference, the myContainer HashSet continues to persist in the Java heap even after the sole instance of the MyClass object has been garbage collected and, along with the HashSet, all the String objects inside the HashSet continue to persist, holding up a significant portion of the Java heap until the program exits the main method.

This program demonstrates a basic memory leaking operation involving an unbounded growth in a cache object. Most caches are implemented using the Singleton pattern involving a static reference to a top level Cache class as shown in this example.


Here is the GC information for you for the above snippet....

[GC 512K->253K(1984K), 0.0018368 secs]
[GC 765K->467K(1984K), 0.0015165 secs]
[GC 979K->682K(1984K), 0.0016116 secs]
[GC 1194K->900K(1984K), 0.0015495 secs]
[GC 1412K->1112K(1984K), 0.0015553 secs]
[GC 1624K->1324K(1984K), 0.0014902 secs]
[GC 1836K->1537K(2112K), 0.0016068 secs]
[Full GC 1537K->1537K(2112K), 0.0120419 secs]
[GC 2047K->1824K(3136K), 0.0019275 secs]
[GC 2336K->2035K(3136K), 0.0016584 secs]
[GC 2547K->2248K(3136K), 0.0015602 secs]
[GC 2760K->2461K(3136K), 0.0015517 secs]
[GC 2973K->2673K(3264K), 0.0015695 secs]
[Full GC 2673K->2673K(3264K), 0.0144533 secs]
[GC 3185K->2886K(5036K), 0.0013183 secs]
[GC 3398K->3098K(5036K), 0.0015822 secs]
[GC 3610K->3461K(5036K), 0.0028318 secs]
[GC 3973K->3673K(5036K), 0.0019273 secs]
[GC 4185K->3885K(5036K), 0.0019377 secs]
[GC 4397K->4097K(5036K), 0.0012906 secs]
[GC 4609K->4309K(5036K), 0.0017647 secs]
[GC 4821K->4521K(5036K), 0.0017731 secs]
[Full GC 4521K->4521K(5036K), 0.0222485 secs]
[GC 4971K->4708K(8012K), 0.0042461 secs]
[GC 5220K->4920K(8012K), 0.0018258 secs]
[GC 5432K->5133K(8012K), 0.0018648 secs]
[GC 5645K->5345K(8012K), 0.0018069 secs]
[GC 5857K->5558K(8012K), 0.0017825 secs]
[GC 6070K->5771K(8012K), 0.0018911 secs]
[GC 6283K->5984K(8012K), 0.0016350 secs]
[GC 6496K->6197K(8012K), 0.0020342 secs]
[GC 6475K->6312K(8012K), 0.0013560 secs]
[Full GC 6312K->6118K(8012K), 0.0341375 secs]
[GC 6886K->6737K(11032K), 0.0045417 secs]
[GC 7505K->7055K(11032K), 0.0027473 secs]
[GC 7823K->7374K(11032K), 0.0028045 secs]
[GC 8142K->7693K(11032K), 0.0029234 secs]
[GC 8461K->8012K(11032K), 0.0027353 secs]
[GC 8780K->8331K(11032K), 0.0027790 secs]
[GC 9099K->8651K(11032K), 0.0028329 secs]
[GC 9419K->8970K(11032K), 0.0027895 secs]
[GC 9738K->9289K(11032K), 0.0028037 secs]
[GC 10057K->9608K(11032K), 0.0028161 secs]
[GC 10376K->9927K(11032K), 0.0028482 secs]
[GC 10695K->10246K(11032K), 0.0028858 secs]
[GC 11014K->10565K(11416K), 0.0029284 secs]
[Full GC 10565K->10565K(11416K), 0.0506198 secs]
[GC 11781K->11071K(18956K), 0.0035594 secs]
[GC 12287K->11577K(18956K), 0.0042315 secs]
[GC 12793K->12082K(18956K), 0.0043194 secs]
[GC 12843K->12390K(18956K), 0.0030633 secs]
[GC 13606K->13494K(18956K), 0.0085937 secs]
[Full GC 13782K->13613K(18956K), 0.0646513 secs]

Monday, 2 May 2011

How many JVM can be run on an operating system (OS) ?

You can have one JVM per process. Since an OS supports many processes, you can have many JVMs running.
When ever we start a new java process by invoking java.exe (i.e. java [class-name] ) a new instance of JVM is created. Each java process executes in its separate JVM environment – we can specify different JVM parameter for each process.
So you can open as many as command prompt your machine allows and run as many JVM as possible. It also means you can run different versions of JVM.

Monday, 25 April 2011

Playing with JVM heam size

Java programs executes in JVM uses Heap of memory to manage the data. If your Java program requires a large amount of memory, it is possible that the virtual machine will begin to throw OutOfMemoryError instances when attempting to instantiate an object. The default heap size if 1 MB and can increase as much as 16 MB.

Java heap space setting from command line:
  1. -Xms for initial heap size
  2. -Xmx for maximum heap size 
  3. -Xss<size>   for set java stack size
     


Initial heap size 128m, max heap size 256m:
java -Xms128m -Xmx256m MyApp


Initial heap size 256m, max heap size 256m:
java -Xms128m -Xmx256m MyApp


Max heap size can't be less than initial heap size:
java -ms256m -mx128m MyAppError occurred during initialization of VM
Incompatible initial and maximum heap sizes specified

Monday, 18 April 2011

Java memory frames

Java Frames are a particularly important concept. Here's are a few bullet point definitions of a Java Frame:

  • A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.
  • A new frame is created each time a method is invoked. A frame is destroyed when its method invocation completes.
  • Frames are allocated from the JVM stack of the thread creating the frame. Each frame has its own array of local variables, its own operand stack, and a reference to the runtime constant pool of the class of the current method.
  • The sizes of the local variable array and the operand stack are determined at compile time and are supplied along with the code for the method associated with the frame.
  • Only one frame, the frame for the executing method, is active at any point in a given thread of control. This frame is referred to as the current frame, and its method is known as the current method. The class in which the current method is defined is the current class.
  • A frame ceases to be current if its method invokes another method or if its method completes.
  • A frame created by a thread is local to that thread and cannot be referenced by any other thread.

There's more than this to know about Java Frames, but I tried to whittle the Sun description down to make it easier to digest initially.

Sunday, 17 April 2011

Getting heap memory size in Java program


// Get current size of heap in bytes
long heapSize = Runtime.getRuntime().totalMemory()
Runtime.getRuntime().totalMemory();

// Get maximum size of heap in bytes. The heap cannot grow beyond this size.
// Any attempt will result in an OutOfMemoryException.
long heapMaxSize = Runtime.getRuntime().maxMemory();

// Get amount of free memory within the heap in bytes. This size will increase
// after garbage collection and decrease as new objects are created.
long heapFreeSize = Runtime.getRuntime().freeMemory();

Saturday, 16 April 2011

Address of a Java Object

Java has no pointers, but still there are methods which let us know address of objects.
Unsafe is a class that belongs to sun.misc package, to which may be you are new.

When a class name is “Unsafe” in java, it calls for your attention immediately. Then I decided to dig deep into it and find what is unsafe about that class. Its difficult to find the source of Unsafe. Get the source and look at the methods you will know what I am referring to.

Java’s security manager provides sufficient cover and ensures you don’t fiddle with memory that easily. As a first step, I thought of getting the memory location of a java object. But lets see.

Sun’s Unsafe.java api documentation shows us an opportunity to get the address using the method objectFieldOffset. That method says, “Report the location of a given field in the storage allocation of its class“. It also says, “it is just a cookie which is passed to the unsafe heap memory accessors“. Whatsoever, I am able to get the storage memory location of an object from the storage allocation of its class.

You can argue that, what we have got is not the absolute physical memory address of an object. But we have got the logical memory address. The following program will be quite interesting for you!
The program had following steps:

  • As a first step, I have to get an object of Unsafe class. It is quite difficult as the constructor is private. 
  • There is a method named getUnsafe which returns the unsafe object. Java’s security manager asks you to make your java source code privileged. I used little bit of reflection and got an instance out. I know there are better ways to get the instance. But to bypass the security easily I chose the following.
  • Using Unsafe’s object just invoke objectFieldOffset and staticFieldOffset. The result is address / location of object in the storage allocation of its class.

Following example program runs well on JDK 1.6:

import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class ObjectLocation {
private static int apple = 10;
private int orange = 10;
public static void main(String[] args)throws Exception {
Unsafe unsafe = getUnsafeInstance();
Field appleField = ObjectLocation.class.getDeclaredField("apple");
System.out.println("Location of Apple: "
+ unsafe.staticFieldOffset(appleField));
Field orangeField = ObjectLocation.class.getDeclaredField("orange");
System.out.println("Location of Orange: "
+ unsafe.objectFieldOffset(orangeField));
}
private static Unsafe getUnsafeInstance()throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);
return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}
}

JVM - heap, non heap memory and other terminologies

Java has only two types of memory when it comes to JVM. Heap memory and Non-heap memory. All the other memory jargons you hear are logical part of either of these two.

Heap Memory
In lay man's term class instances and arrays are stored in heap memory. Heap memory is also called as shared memory. As this is the place where multiple threads will share the same data.

Some points to consider:

The Java virtual machine has a heap that is shared among all Java virtual machine threads. The heap is the runtime data area from which memory for all class instances and arrays is allocated.


The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated.
The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
A Java virtual machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.

Non-heap Memory
It comprises of ‘Method Area’ and other memory required for internal processing. So here the major player is ‘Method Area’.

Method Area
As given in the last line, method area is part of non-heap memory. It stores per-class structures, code for methods and constructors. Per-class structure means runtime constants and static fields.

The above three (heap memory, non-heap memory and method area) are the main jargon when it comes to memory and JVM. There are some other technical jargon you might have heard and I will summarize them below.

Memory Pool
Memory pools are created by JVM memory managers during runtime. Memory pool may belong to either heap or non-heap memory.

Runtime Constant Pool
A run time constant pool is a per-class or per-interface run time representation of the constant_pool table in a class file. Each runtime constant pool is allocated from the Java virtual machine’s method area.

Java Stacks or Frames
Java stacks are created private to a thread. Every thread will have a program counter (PC) and a java stack. PC will use the java stack to store the intermediate values, dynamic linking, return values for methods and dispatch exceptions. This is used in the place of registers.
Definition of stack:

Each Java virtual machine thread has a private Java virtual machine stack, created at the same time as the thread. A Java virtual machine stack stores frames. A Java virtual machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java virtual machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated.


See more on frames


Memory Generations
HotSpot VM’s garbage collector uses generational garbage collection. It separates the JVM’s memory into and they are called young generation and old generation.

Young Generation
Young generation memory consists of two parts, Eden space and survivor space. Shortlived objects will be available in Eden space. Every object starts its life from Eden space. When GC happens, if an object is still alive and it will be moved to survivor space and other dereferenced objects will be removed.

Old Generation – Tenured and PermGen
Old generation memory has two parts, tenured generation and permanent generation (PermGen). PermGen is a popular term. We used to error like PermGen space not sufficient.
GC moves live objects from survivor space to tenured generation. The permanent generation contains meta data of the virtual machine, class and method objects.

Discussion:
Java specification doesn’t give hard and fast rules about the design of JVM with respect to memory. So it is completely left to the JVM implementers. The types of memory and which kind of variable / objects and where they will be stored is specific to the JVM implementation.


Key Takeaways

  • Local Variables are stored in Frames during runtime.
  • Static Variables are stored in Method Area.
  • Arrays are stored in heap memory.


References:

Wednesday, 22 December 2010

Getting java run time memory statistics

   public static void logJVMStatistics()
    {
        System.out.println("JVM Statistics -- Max : " + Runtime.getRuntime().maxMemory() + "; " +
                            "Free: " + Runtime.getRuntime().freeMemory() + "; " +
                            "Total: " + Runtime.getRuntime().totalMemory());
    }

Sunday, 26 September 2010

The Java Class File Format

Introduction

Compiled binary executables for different platforms usually differ not only in the instruction set, libraries, and APIs at which they are aimed, but also by the file format which is used to represent the program code. For instance, Windows uses the COFF file format, while Linux uses the ELF file format. Because Java aims at binary compatibility, it needs a universal file format for its programs - the Class File format.
The class file consists of some data of fixed length and lots of data of variable length and quantity, often nested inside other data of variable length. Therefore it is generally necessary to parse the whole file to read any one piece of data, because you will not know where that data is until you have worked your way through all the data before it. The JVM would just read the class file once and either store the data from the class file temporarily in a more easily accessed (but larger) format, or just remember where everything is in the class file. For this reason, a surprisingly large amount of the code of any JVM will be concerned with the interpretation, mapping, and possibly caching of this class file format.
Please note that this document is just an overview. The actual Class File format description is in the 'Java Virtual Machine Specification' which can be found online here, or in printed form here.

The Start

The Class file starts with the following bytes:
Length (number of bytes)Example
magic40xCAFEBABE
minor_version20x0003
major_version20x002D
The 'magic' bytes are always set to 0xCAFEBABE and are simply a way for the JVM to check that it has loaded a class file rather than some other set of bytes.
The version bytes identify the version of the Class File format which this file conforms to. Obviously a JVM would have trouble reading a class file format which was defined after that JVM was written. Each new version of the JVM specification generally says what range of Class File versions it should be able to process.

Constant Pool

This major_version is followed by the constant_pool_count (2 bytes), and the constant_pool table.
The constant_pool table consists of several entries which can be of various types, and therefore of variable length. There are constant_pool_count - 1 entries, and each entries is referred to by its 1-indexed position in the table. Therefore, the first item is referred to as Constant Pool item 1. An index into the Constant Pool table can be store in 2 bytes.
Each entry can be one of the following, and is identified by the tag byte at the start of the entry.
TagContents
CONSTANT_Class7The name of a class 
CONSTANT_Fieldref 9The name and type of a Field, and the class of which it is a member.
CONSTANT_Methodref 10The name and type of a Method, and the class of which it is a member.
CONSTANT_InterfaceMethodref 11The name and type of a Interface Method, and the Interface of which it is a member.
CONSTANT_String8The index of a CONSTANT_Utf8 entry.
CONSTANT_Integer34 bytes representing a Java integer.
CONSTANT_Float 44 bytes representing a Java float.
CONSTANT_Long58 bytes representing a Java long.
CONSTANT_Double68 bytes representing a Java double.
CONSTANT_NameAndType12The Name and Type entry for a field, method, or interface.
CONSTANT_Utf812 bytes for the length, then a string in Utf8 (Unicode) format.
Note that the primitive types, such as CONSTANT_Integer, are stored in big-endian format, with the most significant bits first. This is the most obvious and intuitive way of storing values, but some processors (in particular, Intel x86 processors) use values in little-endian format, so the JVM may need to manipulate these bytes to get the data into the correct form.
Many of these entries refer to other entries, but they generally end up referring to one or more Utf8 entries.
For instance, here is are the levels of containment for a CONSTANT_Fieldref entry:
CONSTANT_Fieldref
    index to a CONSTANT_Class entry
        index to a CONSTANT_Utf8 entry
    index to a CONSTANT_NameAndType entry
        index to a CONSTANT_Utf8 entry (name)
        index to a CONSTANT_Utf8 entry (type descriptor)
Note that simple text names are used to identify entities such as classes, fields, and methods. This greatly simplifies the task of linking them together both externally and internally.

The Middle Bit

access_flags (2 bytes)

This shows provide information about the class, by ORing the following flags together:
  • ACC_PUBLIC
  • ACC_FINAL
  • ACC_SUPER
  • ACC_INTERFACE
  • ACC_ABSTRACT

this_class

These 2 bytes are an index to a CONSTANT_Class entry in the constant_pool, which should provide the name of this class.

super_class

Like this_class, but provides the name of the class's parent class. Remember that Java only has single-inheritance, so there can not be more than one immediate base class.

Interfaces

2 bytes for the interfaces_count, and then a table of CONSTANT_InterfaceRef indexes, showing which interfaces this class 'implements' or 'extends'..

Fields

After the interfaces table, there are 2 bytes for the fields_count, followed by a table of field_info tables.
Each field_info table contains the following information:
Length (number of bytes)Description
access_flags2e.g. ACC_PUBLIC, ACC_PRIVATE, etc
name_index2Index of a CONSTANT_Utf8
descriptor_index2Index of a CONSTANT_Utf8 (see type descriptors)
attributes_count2
attributesvariese.g. Constant Value. (see attributes)

Methods

 After the fields table, there are 2 bytes for the methods_count, followed by a table of method_info tables. This has the same entries as the field_info table, with the following differences:
  • The access_flags are slightly different.
  • The descriptor has a slightly different format (see type descriptors)
  • A different set attributes are included in the attributes table - most importantly the 'code' attribute which contains the Java bytecode for the method. (see attributes)

Type Descriptors

Field and Method types are represented, in a special notation, by a string. This notation is described below.

Primitive Types

Primitive types are represented by one of the following characters:
byteB
charC
doubleD
floatF
intI
longJ
shortS
booleanZ
For instance, an integer field would have a descriptor of "I".

Classes

Classes are indicated by an 'L', followed by the path to the class name, then a semi-colon to mark the end of the class name.
For instance, a String field would have a descriptor of "Ljava/lang/String;"

Arrays

Arrays are indicated with a '[' character.
For instance an array of Integers would have a descriptor of "[I".
Multi-dimensional arrays simply have extra '[' characters. For instance, "[[I".

Field Descriptors

A field has just one type, described in a string in the above notation. e.g. "I", or "Ljava/lang/String".

Method Descriptors

Because methods involve several types - the arguments and the return type - their type descriptor notation is slightly different. The argument types are at the start of the string inside brackets, concatenated together. Note that the type descriptors are concatenated without any separator character. The return type is after the closing bracket.
For instance, "int someMethod(long lValue, boolean bRefresh);" would have a descriptor of "(JZ)I". 

Attributes

Both the field_info table and the method_info table include a list of attributes. Each attribute starts with the index of a CONSTANT_Utf8 (2 bytes) and then the length of the following data (4 bytes). The structure of the following data depends on the particular attribute type. This allows new or custom attributes to be included in the class file without disrupting the existing structure, and without requiring recognition in the JVM specification. Any unrecognised attribute types will simply be ignored.
Attributes can contain sub-attributes. For instance, the code attribute can contain a LineNumberTable attribut
Here are some possible attributes:
CodeDetails, including bytecode, of a method's code.
ConstantValueUsed by 'final' fields
ExceptionsExceptions thrown by a method.
InnerClassesA class's inner classes.
LineNumberTableDebugging information
LocalVariableTableDebugging information.
SourceFileSource file name.
SyntheticShows that the field or method was generated by the compiler.

Code attribute

The Code attribute is used by the method_info table. It is where you will find the actual bytecodes (opcodes an operands) of the method's classes.
The attributes has the following structure:
Length (number of bytes)Description
max_stack2Size of stack required by the method's code.
max_locals2Number of local variables required by the method's code.
code_length2
codecode_lengthThe method's executable bytecodes
exception_table_length2
exception_tablevariesThe exceptions which the method can throw.
attributes_count2
attributesvariese.g. LineNumberTable
Each exception table entry has the following structure, each describing one exception catch:
Length (number of bytes)Description
start_pc2Offset of start of try/catch range.
end_pc2Offset of end of try/catch range.
handler_pc2Offset of start of exception handler code.
catch_type2Type of exception handled.
These entries for the Code attribute will probably only make sense to you if you are familiar with the rest of the JVM specification.
source: http://www.murrayc.com/learning/java/java_classfileformat.shtml