Tuesday, February 19, 2008

JVM Performance Tuning

Last week was JBoss World, and it was exciting to be a part of it. I gave a presentation on performance tuning our Enterprise Application Platform or EAP, and it was packed. In fact, people were sitting on the floor in pretty much all available space. What struck me about the presentation, and much of the discussion I had with individuals afterwards, is that JVM tuning is a big topic. So, I thought I would share some of what I learned over the past couple of months as I was preparing for my presentation.

In preparing for my presentation, I wrote an EJB 3 application, wrote a load test for it, and applied optimizations to various configuration parameters within the EAP, the JVM and the operating system. In particular, one JVM and OS setting really made a huge difference in throughput, and its something that I wanted to share here.

When using a 64-bit OS, in my case Fedora 8 and RHEL 5.1, I wanted to investigate the usage of large page memory support, or HugeTLB as its referred to within the Linux kernel. What I found was very scarce documentation around using this, and that that documentation was incomplete to actually make it work. What I also found, is it makes a huge difference in overall throughput and response times of an application, when using heap sizes above 2GB.

So, without further ado, let's dive into how to set this up. These instructions are for Linux, specifically for Fedora 8 and RHEL 5.1, but the results should be generally applicable to any 64-bit OS and 64-bit JVM that supports large page memory (which all the proprietary UNIX's do, and I found an MSDN article describing how to use this on 64-bit Windows).

You must have root access for these settings. First, you need to set the kernel parameter for shared memory to be at least as big as you need for the amount of memory you want to set aside for the JVM to use as large page memory. Personally, I like to just set it to the maximum amount of memory in the server, so I can play with different heap sizes without having to adjust this every time. You set this by putting the following entry into /etc/sysctl.conf:

kernel.shmmax = n

where n is the number of bytes. So, if you have a server with 8GB of RAM, then you would set it to 8589934592, or 1024*1024*1024*8, which is 8GB.

Second, you need to set a virtual memory kernel parameter to tell the OS how many large memory pages you want to set aside. You set this by putting the following entry into /etc/sysctl.conf:

vm.nr_hugepages = n

where n is the number of pages, based on the page size listed in /proc/meminfo. If you cat /proc/meminfo you will see the large page size of your particular system. This varies depending on the architecture of the system. Mine, is an old Opteron system, and it has a page size of 2048 KB, as shown by the following line in /proc/meminfo:

Hugepagesize: 2048 kB

So, I wanted to set this to 6GB. I set the parameter to 3072, which is (1024*1024*1024*6)/(1024*1024*2). Which is 6GB divided by 2MB, since 2048 KB is 2MB.

After this, you need to set another virtual memory parameter, to give permission for your process to access the shared memory segment. In /etc/group, I created a new group, called hugetlb, you can call it whatever you like, as long as it doesn't collide with any other group name, and it had a value of 501 on my system (it will vary, depending on whether you use the GUI tool, like I did, or whether you do it at the command line, and vary depending on what groups you already have defined). You put that group id in /etc/sysctl.conf as follows:

vm.hugetlb_shm_group = gid

where gid, in my case was 501. You also add that group to whatever your user id is that the JVM will be running as. In my case this was a user called jboss.

Now, that concludes the kernel parameter setup, but there is still one more OS setting, which changes the users security permissions to allow the user to use the memlock system call, to access the shared memory. Large page shared memory is locked into memory, and cannot be swapped to disk. Another major advantage to using large page memory. Having your heap space swapped to disk can be catastrophic for an application. So, you set this parameter in /etc/security/limits.conf as follows:

jboss soft memlock n
jboss hard memlock n

where n is equal to the number of huge pages, set in vm.nr_hugepages, times the page size from /proc/meminfo, which in my example would be, 3072*2048 = 629146. This concludes the OS setup, and now we can actually configure the JVM.

The JVM parameter for the Sun JVM is -XX:+UseLargePages (for BEA JRocket its -XXlargePages, and for IBM's JVM its -lp). If you have everything setup correctly, then you should be able to look at /proc/meminfo and see that the large pages are being used after starting up the JVM.

A couple of additional caveats and warnings. First, you can dynamically have the kernel settings take affect by using sysctl -p. In most cases, if the server has been running for almost any length of time, you may not get all the pages you requested, because large pages requires contiguous memory. You may have to reboot to have the settings take affect. Second, when you allocate this memory, it is removed from the general memory pool and is not accessible to applications that don't have explicit support for large page memory, and are configured to access it. So, what kind of results can you expect?

Well, in my case, I was able to achieve an over 3x improvement in my EJB 3 application, of which fully 60 to 70% of that was due to using large page memory with a 3.5GB heap. Now, a 3.5GB heap without the large memory pages didn't provide any benefit over smaller heaps without large pages. Besides the throughput improvements, I also noticed that GC frequency was cut down by two-thirds, and GC time was also cut down by a similar percentage (each individual GC event was much shorter in duration). Of course, your mileage will vary, but this one optimization is worth looking at for any high throughput application.

Good luck!

19 comments:

hotsun said...

Andrig,
Thank you good info about OS and JVM tuning.
You said you have achieved over 3x improvement after tuning. Could you provide a little more info about how about original setting before tuning.
What tuning tools you are using.
Thanks in advance.

Jim

Andrig T Miller said...

The 3x improvement was due to other configuration changes besides the JVM that were specific to my application running on the JBoss EAP 4.2 release. They were mostly adjustments to the pool sizes for the EJB 3 objects.

Having said that, the changes for the JVM resulted in around 70% of that total change in throughput.

The starting point was using -server -ms3584m -mx3584m.

This is simply putting the HotSpot JVM into server mode, and using a minimum and maximum heap size of 3.5GB.

After this I added -XX:+UseLargePages. Of course, in order for this to work, you have to go through the OS setup instructions in the blog. That was all I did. I believe in keeping things as simply as possible as your starting point, and adding one thing to see what it does for you. The simpler you keep the options, the better off you are going to be, IMHO.

rostiarso said...

Thanks for the tips Andrig.

Additional info for those who still stuck with 2.4 kernels (e.g. RHEL 3), use vm.hugetlb_pool parameter to set the huge page size (in MB). So to configure 6GB page size, the sysctl parameter is vm.hugetlb_pool=6144

Andrig T Miller said...

Thanks for the additional RHEL 3 information. That will probably help quite a few folks, considering the cycle time to upgrade the OS that most organizations have.

Thanks again!

arnold_mad said...

Hi !

I tied to setup my jboss the same way you did but I always get this erro when starting up the JVM:

Java HotSpot(TM) 64-Bit Server VM warning: Failed to reserve shared memory (errno = 12).

Do u know what could be wrong ?

Andrig T Miller said...

You get the errno=12 when you don't have the HugeTLB setup correctly, or you don't have enough pages.

If you want to show me specifically what you setup on the Linux side of things, and what you are passing on the JVM command-line I can probably tell you what's wrong.

Someone said...

Hi Andrig,

Hopefully you still read your blog (and this post!). I have a RH Linux machine that is configured for large pages. If I run my application (with 24gb) using Jrockit, I can access the large pages. If I use Sun's JVM, I get the errno = 12. Any idea what to look at?

Andrig T Miller said...

Are you sure you are really accessing the large pages with JRockit? If you look at /proc/meminfo, what do you see? You should see non-zero values in HugePages_Rsvd, _Free, etc.

I wouldn't doubt that its not working with JRockit, but that it fails silently versus the Sun JVM.

Besides just verifying that it is actually working with JRocket, and the HugePages_ values in /proc/meminfo, I would take a look at:

/etc/security/limits.conf - do you have the user that you are running the JVM as defined there with the proper values for memlock?

/etc/sysctl.conf - do you have the following defined:

# Change maximum shared memory segment size to 8GB
kernel.shmmax = 8589934592

# Add the gid to the hugetlb_shm_group to give access to the users
vm.hugetlb_shm_group = 501

# Add 6GB of in 2MB pages to be shared between the JVM and MySQL
vm.nr_hugepages = 3072

Of course your values might be different and the group value will have to be added in /etc/group to the specific user you are running the JVM as.

Anyway, if you want to post the values you have in /etc/sysctl, /etc/security/limits.conf, /etc/group, and tell me what user you are using for running the JVM, I can probably spot what is wrong. Oh, also the /proc/meminfo, and how much memory you want to use as large pages.

cadmo said...

hi andrig - thanks for the very useful post.

i have applied your suggestion to my jboss system (sun jvm1.5 on redhat 4 on AMD) and it is working at the OS level, but the JVM does not seem to use any large pages.

the -XX:+UseLargePages is used.

any suggestion?

Andrig T Miller said...

Hmm... How do you know that the JVM is not using the large pages?

If you include the text of what's in /proc/meminfo, that might be helpful as well.

gdimitrov said...

Very helpful.
It is important to update the kernel, glibc and maybe other packages in order this to work.
I tried it with original RHEL 4 update 2 and I had to update to the latest update 6 before it works. Even if I did all settings correctly before this.

Andrig T Miller said...

Thanks for the tip. Yes, keeping your RHEL installation up to date is definitely key to making sure this works. I'm not surprised there were issues, and that you had to upgrade to update 6 for it to work.

rafaelcba said...

Hello!

I setup my env. as you decribed in this post but when I start my JVM it show the following WARN:

Java HotSpot(TM) 64-Bit Server VM warning: Failed to reserve shared memory (errno = 22).

My JAVA_OPTS is:

JAVA_OPTS: -Dprogram.name=run.sh -Dflag.kill.jboss= -server -Duser.language=pt -Duser.region=BR -Dfile.encoding=ISO8859_1 -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/var/log/JVMGC/gc.log -XX:+DisableExplicitGC -XX:+UseSpinning -Xms64g -Xmx64g -XX:NewSize=16g -XX:MaxNewSize=16g -XX:SurvivorRatio=6 -XX:+PrintTenuringDistribution -XX:+UseLargePages -XX:PermSize=512m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -XX:+UseTLAB -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferIPv4Stack=true

my /proc/meminfo is:
HugePages_Total: 32768
HugePages_Free: 32768
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB

my system limits is:
# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 1056768
max locked memory (kbytes, -l) 67108864
max memory size (kbytes, -m) unlimited
open files (-n) 99999
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1056768
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

I am using Debian (Linux Version 2.6.26-2-amd64).

Can you help me?

Thanks.

rafaelcba said...

Hello!

I setup my env. as you decribed in this post but when I start my JVM it show the following WARN:

Java HotSpot(TM) 64-Bit Server VM warning: Failed to reserve shared memory (errno = 22).

My JAVA_OPTS is:
JAVA_OPTS: -Dprogram.name=run.sh -Dflag.kill.jboss= -server -Duser.language=pt -Duser.region=BR -Dfile.encoding=ISO8859_1 -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/var/log/JVMGC/gc.log -XX:+DisableExplicitGC -XX:+UseSpinning -Xms64g -Xmx64g -XX:NewSize=16g -XX:MaxNewSize=16g -XX:SurvivorRatio=6 -XX:+PrintTenuringDistribution -XX:+UseLargePages -XX:PermSize=512m -XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -XX:+UseTLAB -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferIPv4Stack=true

my /proc/meminfo is:
HugePages_Total: 32768
HugePages_Free: 32768
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB

my system limits is:
# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 1056768
max locked memory (kbytes, -l) 67108864
max memory size (kbytes, -m) unlimited
open files (-n) 99999
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1056768
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

I am using Debian (Linux Version 2.6.26-2-amd64).

Can you help me?

Thanks.

Andrig T Miller said...

In looking at this rather long set of options, here is what is wrong. While I have found conflicting documentation around this, the truth is you have only allocated 64g of huge pages, but your command line is asking for that plus another 512m, because of the perm size parameter. You need to add another half a gig to your settings to have this work. The perm size is additive to your heap settings (max).

rafaelcba said...

Hello.

This same configuration worked fine on RHL 5.3 64-bit.

On Debian amd64 the maximum JVM HEAP which I could set was 8GB. I don't know why yet :(

Thanks.

Andrig T Miller said...

Interesting that it worked at all. For the Debian side of the equation, do you know how the kernel was built? There is certainly something different, but I don't have a clue what.

Darin Pope said...

I'm having issues getting the large pages to be used. Here's the error:

Java HotSpot(TM) 64-Bit Server VM warning: Failed to reserve shared memory (errno = 12).

We are running Tomcat 6.0.18 running Sun JDK 1.6.0_12 configured with a 6GB heap on a CentOS 5.3 server with just under 8GB. I would like to use 6GB for large pages.

I have followed your instructions, including rebooting after making all the changes, but the error persists.

Here's all my pertinent info:

JVM opts:
-verbose:gc
-XX:+UseLargePages
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:ParallelGCThreads=2
-XX:NewRatio=2
-XX:+PrintGCDetails
-Xloggc:/opt/tomcat-instance/tomcat1/logs/gc_parallel.log
-Xms6144m
-Xmx6144m

/proc/meminfo:
MemTotal: 7927716 kB
MemFree: 1147532 kB
Buffers: 14740 kB
Cached: 169492 kB
SwapCached: 0 kB
Active: 290448 kB
Inactive: 149664 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 7927716 kB
LowFree: 1147532 kB
SwapTotal: 4095992 kB
SwapFree: 4095992 kB
Dirty: 24 kB
Writeback: 0 kB
AnonPages: 255864 kB
Mapped: 20124 kB
Slab: 22000 kB
PageTables: 4116 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
CommitLimit: 4914120 kB
Committed_AS: 6802352 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 2940 kB
VmallocChunk: 34359733659 kB
HugePages_Total: 3072
HugePages_Free: 3072
HugePages_Rsvd: 0
Hugepagesize: 2048 kB

OS:
Linux vm-tomcat-prd16 2.6.18-128.7.1.el5 #1 SMP Mon Aug 24 08:21:56 EDT 2009 x86_64 x86_64 x86_64 GNU/Linux

/etc/sysctl.conf
# Kernel sysctl configuration file for Red Hat Linux
#
# For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and
# sysctl.conf(5) for more details.

# Controls IP packet forwarding
net.ipv4.ip_forward = 0

# Controls source route verification
net.ipv4.conf.default.rp_filter = 1

# Do not accept source routing
net.ipv4.conf.default.accept_source_route = 0

# Controls the System Request debugging functionality of the kernel
kernel.sysrq = 0

# Controls whether core dumps will append the PID to the core filename
# Useful for debugging multi-threaded applications
kernel.core_uses_pid = 1

# Controls the use of TCP syncookies
net.ipv4.tcp_syncookies = 1

# Controls the maximum size of a message, in bytes
kernel.msgmnb = 65536

# Controls the default maxmimum size of a mesage queue
kernel.msgmax = 65536

# Controls the maximum shared segment size, in bytes
#kernel.shmmax = 68719476736

# Controls the maximum number of shared memory segments, in pages
kernel.shmall = 4294967296

#
# settings for huge page support
#
# http://andrigoss.blogspot.com/2008/02/jvm-performance-tuning.html
#
# since we have just under 8GB of memory on the server, set shmmax to 7GB:
kernel.shmmax = 7516192768
vm.nr_hugepages = 3072
vm.hugetlb_shm_group = 506

/etc/security/limits.conf:
tomcat soft memlock 629146
tomcat hard memlock 629146

/etc/group:
tomcat:x:505:
hugetlb:x:506:tomcat

id tomcat:
uid=505(tomcat) gid=505(tomcat) groups=505(tomcat),506(hugetlb) context=user_u:system_r:unconfined_t

ulimit -a:
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 65536
max locked memory (kbytes, -l) 629146
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 65536
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

Andrig T Miller said...

Darin,

Your kernel parameter for setting the maximum amount of shared memory is commented out:

# Controls the maximum shared segment size, in bytes
#kernel.shmmax = 68719476736

Remove the # from kernel.shmmax and try it again.

Sorry for the late reply. My inbox has been a mess lately.