Showing posts with label size. Show all posts
Showing posts with label size. Show all posts

Saturday, May 23, 2015

Java Object Memory Layout/Anatomy [Deep Size of an Object]

Even to the mind of the most experienced Java Developer, the way JVM organises or allocates memory for an Object may not come intuitively. This also includes activities like measuring the size of an Object.

When we use 'new' to instantiate an Object, there is much more data size allocated than is required to just hold the value. For example, if you choose to create java.lang.Integer, the actual int value will be only 1:4 parts of the Object. The remaining is used to hold the metadata - The metadata includes the following:

1. Class: A pointer to the type of the class. In our case, a pointer to the java.lang.Integer class.
2. Flags: A collection of flags that describe the state of the Object, including the hash code of the Object and the shape of the Object (If it is an Array or not)
3. Lock: The synchronization information of the Object, including if it is synchronized currently.

The Object metadata is followed by the actual Object data. In this specific case, an int value.

Example layout of a java.lang.Integer object for a 32-bit Java process

Example layout of a java.lang.Integer object for a 32-bit Java process

[Reference: http://www.ibm.com/developerworks/library/j-codetoheap/]


I tried to explore all possibilities to measure the Object Memory usage as per the above understanding of the Object Allocation. There are many tools, blogs and write-ups on the internet, but all of them provide approximation, which may not be reliably usable. Also, the values which may be returned by each of these will not concur across these tools. 

The only tool that I came across that seems to be reliable and has the source code public is the Java Agent for Memory Measurements [https://github.com/jbellis/jamm]. The author also seems to be the committer for the Cassandra project, and it is being used in Cassandra - Hence, it is a safe bet and should be close to as exact as possible. I went through the source and the logic seems to concur with the memory layout above, Also, since it is being used in very important (and widely used) projects, [i skipped trying it out myself]. In all, this should be your safest [reliablity and performance] bet if you want to use it to introduce custom cache measurements, custom object profiling, server profiling or simply memory profiling tools.

[I plan to write my own tool to measure Object Sizes in Java as reliably as possible - using the above understanding of Java Memory Allocation . Keep checking this Blog to Download the Source and Binary]
 
 

Thursday, May 7, 2015

Enforce Size Constraints in Infinispan - Java Instrumentation API!

Infinispan does not provide size constraints out of the box, just like almost all of the other standard caching frameworks. You may use the Java Instrumentation API as follows to enforce cache size constraints for your product or project that uses Infinispan.

1. Introduce Instrumentation API in your code
a. Develop a class that has the following method in it. The VM will automatically insert an implementation of the Instrumentation API - once it encounters this method.
 public class ServerInstrumentation {  
      private Instrumentation instrumentation = null;   
      private static final Logger LOGGER = Logger.getLogger(ServerInstrumentation.class);  
      private static ServerInstrumentation serverInstrumentation;  

      public static void premain(String args, Instrumentation pInstrumentation) {  
           serverInstrumentation = new ServerInstrumentation();  
           serverInstrumentation.instrumentation=pInstrumentation;  
      }  
      public static Instrumentation getInstance() {  
           return serverInstrumentation.instrumentation;  
      }  
}

b. You may choose to add the following EJB (optionally) to make this object available throughout your Java EE application. Re-Implement the methods that you need to use from the Instrumentation API. For example, below we have implemented the sizeOf() which actually internally invokes Instrumentation.getObjectSize().
 @Remote  
 public interface ServerInstrumentationInterface {  
      public long sizeOf(Object toBeSized);  
 }  

 @Singleton  
 @Startup  
 public class ServerInstrumentationEJB implements ServerInstrumentationInterface {  
      private static Instrumentation instrumentation;  
      {  
           ClassLoader classLoader=this.getClass().getClassLoader().getParent();  
           try {  
                Class clazz = classLoader.loadClass(ServerInstrumentation.class.getName());  
                instrumentation=(Instrumentation)clazz.getDeclaredMethod("getInstance").invoke(null);                   
           } catch (Exception e) {  
                System.out.println("FFFFFF");  
           }  
      }  
      public long sizeOf(Object toBeSized) {  
           return instrumentation.getObjectSize(toBeSized);  
      }  
 }  

Package this code as 'agent.jar' with the following contents in MANIFEST.MF
 Premain-Class: ServerInstrumentation  
 Agent-Class: ServerInstrumentation  

c. You should place this in the normal deployment location (standalone\deployments) of Wildfly and also mention it as the -javaagent during server startup.The following contents should be in Wildfly standalone.bat
 "%JAVA%" %JAVA_OPTS% "-javaagent:%JBOSS_HOME%\standalone\deployments\agent.jar" ^  
  "-Dorg.jboss.boot.log.file=%JBOSS_LOG_DIR%\server.log" ^  
  "-Dlogging.configuration=file:%JBOSS_CONFIG_DIR%/logging.properties" ^  
   -jar "%JBOSS_HOME%\jboss-modules.jar" ^  
   -mp "%JBOSS_MODULEPATH%" ^  
    org.jboss.as.standalone ^  
   "-Djboss.home.dir=%JBOSS_HOME%" ^  
    %SERVER_OPTS%  

At the end of this, the Instrumentation methods are available throughout your Java EE application for all instrumentation purposes. The EJB will act as a facade over this functionality. Make sure that the following settings exist in your standalone.conf.bat
set "JAVA_OPTS=%JAVA_OPTS% -Djava.net.preferIPv4Stack=true -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Xbootclasspath/p:d:\wildfly\modules\system\layers\base\org\jboss\log4j\logmanager\main\log4j-jboss-logmanager-1.1.0.Final.jar;d:\wildfly\modules\system\layers\base\org\jboss\logmanager\main\jboss-logmanager-1.5.2.Final.jar" 
set "JAVA_OPTS=%JAVA_OPTS% -Djboss.modules.system.pkgs=org.jboss.byteman,org.jboss.logmanager"


2. Use this in your Actual Application (Client)
a. The lookup code is provided below to perform methods using Instrumentation, such as 'Object Size'. This may be used for multiple purposes in your Java EE products or solutions, apart form caching constraints.
 // let's do the lookup           
ServerInstrumentationInterface cacheInstrumentationService = (ServerInstrumentationInterface)   
               initialContext.lookup("java:global/agent/" + ServerInstrumentationEJB.class.getSimpleName());  
cacheInstrumentationService.sizeOf(new String("Java and Infinispan Caching Innovation")));  

b. The following method can be implemented in a different class (CacheInstrumentation.java) to perform the following function everytime you add an element to the cache, to check whether the Allocated Size is exceeded. This will have the above EJB Reference/Instance encapsulated in it.
 public boolean isEntityCacheLimitExceeded(Cache cache) {  
           boolean exceeded=false;  
           long currSize = cacheInstrumentationService.sizeOf(cache);  
           long allocSize = 10*1000*1024;  // 10 MB = 10 * 1000 * 1024 bytes
           if(currSize >= allocSize) {  
                exceeded=true;  
           }  
           return exceeded;  
}  


3. Use ConfigurationBuilder to Introduce Constraints
a. The following method will be used to setConfiguration() which will help to notify Infinispan that the configuration has changed, so that it can perform eviction dynamically.
 public class CacheManagerUtilities {  
      public static Configuration setConfiguration(int maxNumberofEntries) {  
           return new ConfigurationBuilder().eviction().maxEntries(maxNumberofEntries)  
                     .strategy(EvictionStrategy.valueOf("LIRS")).expiration()  
                     .lifespan(2000)  
                     .wakeUpInterval(500)  
                     .maxIdle(1000).build();  
      }  
 }  


4. Request/Enforce Eviction using EvictionManager [defineConfiguration()]
a. Everytime you put/add an element in your cache, you can use the sizeOf() from the EJB to check if the size constraints are exceeded. If they are, then dyanmically change the configuration to make the max-entries limit to the currently existing max-entries. We will now rely on Infinispan's Eviction Algorithms to kick-in and do its duty.
 public void put(K key, V value) {            
           while (true) {  
                if (!cacheInstrumentation.isEntityCacheLimitExceeded(cache)) {  
                     cache.put(key, value);  
                     break;  
                } else {  
                     // set the configuration to the infinispan cache programmatically  
                     Configuration configuration = CacheManagerUtilities.setConfiguration(cache.size());  
                     cache.getCacheManager().defineConfiguration(cache.getName(), configuration);  
                    
                     // start eviction manually  
                     EvictionManager evictionManager = cache.getAdvancedCache().getEvictionManager();  
                     evictionManager.processEviction();  
                     
                     // set the configuration to the infinispan cache programmatically - large number such as 9999  
                     configuration = CacheManagerUtilities.setConfiguration(9999);  
                     cache.getCacheManager().defineConfiguration(cache.getName(), configuration);  
                }  
           }  
}

The above usually performs three things:
1. Expire all elements that have reached expiration time (Data Container)
2. Expire all elements that have reached expiration time (Cache Store)
2. Remove all elements as per the eviction policy - Prunes to max-entries

You may also test the above using Arquillian to try out whether the eviction works by creating data within test cases that adhere to LIRS, LRU and Size Constraints.


Note that the above code will also make sure that only 'Serializable' objects are allowed to be Cached. Suitable for all Java/Java EE products and solutions that need to Enforce Size as a Constraint mechanism for Caching. Happy Caching!