On the web
Nick Stephen's blog
Archives
« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today
Click me to subscribe
Search

Links
 

Today's Page Hits: 16

« Sun - Back from... | Main | Copyrights, Licenses... »
20060929 Friday September 29, 2006

Java - why does my JVM use CPU when idle?

Have you ever wondered why an otherwise idle Java process sits there in an endless loop scheduling a thread, calling poll, regularly eating a small amount of CPU?

I always used to think that this was because of things like the garbage-collector and the JIT compiler, but it's not...

On a Unix box, an otherwise idle JVM calls poll() 20 times per second, creating a measurable CPU load as seen with 'uptime'

You can see this CPU usage by writing a simple Java application that does nothing except wait - you'll have to interrupt this application by killing it with control-c, since it just sleeps for a very long time:

package idle;

public class Main {
    
    public static void main(String[] args) {
        try {
            Thread.currentThread().sleep(99999999);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
 

If you compile this up, start the process and then look at what it's doing, you see this (from a Solaris machine - but the same holds true on other platforms):

$ java idle.Main &
$ jps
43204 Jps
41818 Main
$ truss -u libc -dp 41818 2>&1|head -50
Base time stamp:  1159529119.5139  [ Fri Sep 29 13:25:19 MEST 2006 ]
/14:     0.3257 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.3261   -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.3437   <- libc:gethrtime() = 0x46c64b
/14@14:  0.3441   -> libc:gethrtime(0xc6, 0xf1d129d8, 0xfef41d48, 0xfeef0000)
/14@14:  0.3443   <- libc:gethrtime() = 0x46c64b
/14@14:  0.3446 <- libc:poll() = 0
/14@14:  0.3448 -> libc:poll(0x0, 0x0, 0x32, 0xf4240)
/6:      0.3260 lwp_cond_wait(0x00036648, 0x00036630, 0xF877FA48, 0) Err#62 ETIM
E
/6@6:    0.3600 -> libc:gettimeofday(0xf877fb18, 0x0, 0xb, 0x3e8)
/6@6:    0.3605 <- libc:_lwp_cond_timedwait() = 0x36648
/6@6:    0.3608 -> libc:_lwp_cond_timedwait(0x36648, 0x36630, 0xf877fb20, 0x3e8)
/14:     0.4110 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.4113 <- libc:poll() = 0
/14@14:  0.4126 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.4132 <- libc:gethrtime() = 0x46c64b
/14@14:  0.4141 -> libc:gethrtime(0xc6, 0xf5f613fc, 0xfef41d48, 0xfeef0000)
/14@14:  0.4149 <- libc:gethrtime() = 0x46c64b
/14@14:  0.4162 -> libc:poll(0x0, 0x0, 0x32, 0xf4240)
/14:     0.4670 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.4672 <- libc:poll() = 0
/14@14:  0.4685 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.4695 <- libc:gethrtime() = 0x46c64b
/14@14:  0.4699 -> libc:gethrtime(0xc6, 0xf95099a0, 0xfef41d48, 0xfeef0000)
/14@14:  0.4705 <- libc:gethrtime() = 0x46c64b
/14@14:  0.4717 -> libc:poll(0x0, 0x0, 0x32, 0xf4240)
/14:     0.5230 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.5232 <- libc:poll() = 0
/14@14:  0.5245 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.5253 <- libc:gethrtime() = 0x46c64b
/14@14:  0.5262 -> libc:gethrtime(0xc6, 0xfc9f810c, 0xfef41d48, 0xfeef0000)
/14@14:  0.5271 <- libc:gethrtime() = 0x46c64b
/14@14:  0.5298 -> libc:poll(0x0, 0x0, 0x32, 0xf4240)
/14:     0.5820 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.5822 <- libc:poll() = 0
/14@14:  0.5834 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.5839 <- libc:gethrtime() = 0x46c64b
/14@14:  0.5848 -> libc:gethrtime(0xc7, 0x22e594, 0xfef41d48, 0xfeef0000)
/14@14:  0.5855 <- libc:gethrtime() = 0x46c64b
/14@14:  0.5874 -> libc:poll(0x0, 0x0, 0x32, 0xf4240)
/14:     0.6390 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.6392 <- libc:poll() = 0
/14@14:  0.6411 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.6417 <- libc:gethrtime() = 0x46c64b
/14@14:  0.6426 -> libc:gethrtime(0xc7, 0x395075c, 0xfef41d48, 0xfeef0000)
/14@14:  0.6436 <- libc:gethrtime() = 0x46c64b
/14@14:  0.6448 -> libc:poll(0x0, 0x0, 0x32, 0xf4240)
/14:     0.6959 pollsys(0x00000000, 0, 0xB3A7FCF0, 0x00000000)  = 0
/14@14:  0.6962 <- libc:poll() = 0
/14@14:  0.6975 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.6989 <- libc:gethrtime() = 0x46c64b
/14@14:  0.6997 -> libc:gethrtime(0xc7, 0x6fb093c, 0xfef41d48, 0xfeef0000)
 

A thread is being woken up 20 times per second and is calling poll() ; getthrtime(); getthrtime() each time it is woken up. This causes the application to remain rather 'hot' for the scheduler, and to eat up unnecessary CPU cycles.

I always thought that the CPU usage during idle times of a JVM were for garbage collection and JIT compilation - that's the only thing that should be going on behind my back, right? But my application is doing nothing - so is GC being called so often? What for? And what might be JIT compiling? There's no code being called!

It turns out that tha vast majority of these system calls are not for GC and not for JIT compilation - they're to wake up a thread that is for gathering statistics about the VM, which are written to a temporary directory with a name such as /tmp/hsperfdata_${USER}/${PID}, and which can be explored using the jstat tool.

Let's take a look at some of this data - note that the various jstat options are self-documented, I just chose one of them:

$ jstat -class file:///tmp/hsperfdata_nstephen/41818
Loaded  Bytes  Unloaded  Bytes     Time   
   312   322.8        0     0.0       0.11
 

Now, it's all very well collecting this data if I want it collected, and it's OK for this to have an impact on the platform behavior to collect the data (after all, we cannot escape the observer effect), but I'd really like to be able to decide not to collect this data, and hence not incur the cost of doing so.

It turns out that this was actually much worse in much older JVMs, since the JVM timer thread would wake up every tick to see if there was work to do - there was a bug filed against it then:

... and there's a new bug filed against the current behavior too:

So how can you switch this off?

Well, the honest answer is that I don't think that you can switch this off, but you can work around the problem by changing the frequency at which this thread is woken up, such that it becomes background noise.

By using a Sun-specific java command-line option, you can change the rate of sampling to be much lower - as described in the bug entry's workaround, it suggests you change the frequency to be that of the other background activities of the JVM for much-needed things like GC and JIT compilation. The hidden extended option to add to your command line is this: -XX:PerfDataSamplingInterval=500.

For a complete list of all the JVM flags, including this one, you could do worse than look here. Remember, many of these flags are non-standard and hence won't be supported on all versions of JVMs.

So does it work? Let's find out!

$ java -XX:PerfDataSamplingInterval=500 idle.Main &
$ jps
43666 Jps
43555 Main
$ truss -u libc -dp 43555 2>&1|head -50
Base time stamp:  1159530801.2075  [ Fri Sep 29 13:53:21 MEST 2006 ]
/6:      0.4730 lwp_cond_wait(0x00036678, 0x00036660, 0xF877F9C8, 0) Err#62 ETIME
/14:     0.4730 pollsys(0x00000000, 0, 0xB3A7FD70, 0x00000000)  = 0
/6@6:    0.4751 -> libc:gettimeofday(0xf877fa98, 0x0, 0xb, 0x3e8)
/14@14:  0.4755   -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  0.4935   <- libc:gethrtime() = 0x46c7d3
/6@6:    0.4942 <- libc:_lwp_cond_timedwait() = 0x36678
/6@6:    0.5005 -> libc:_lwp_cond_timedwait(0x36678, 0x36660, 0xf877faa0, 0x3e8)
/14@14:  0.5136   -> libc:gethrtime(0xa, 0x14bf2034, 0xfef41d48, 0xfeef0000)
/14@14:  0.5167   <- libc:gethrtime() = 0x46c7d3
/14@14:  0.5175 <- libc:poll() = 0
/14@14:  0.5181 -> libc:poll(0x0, 0x0, 0x1f4, 0xf4240)
/14:     1.0312 pollsys(0x00000000, 0, 0xB3A7FD70, 0x00000000)  = 0
/14@14:  1.0314 <- libc:poll() = 0
/14@14:  1.0329 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  1.0337 <- libc:gethrtime() = 0x46c7d3
/14@14:  1.0343 -> libc:gethrtime(0xa, 0x34f2ef70, 0xfef41d48, 0xfeef0000)
/14@14:  1.0347 <- libc:gethrtime() = 0x46c7d3
/14@14:  1.0362 -> libc:poll(0x0, 0x0, 0x1f4, 0xf4240)
/6:      1.4961 lwp_cond_wait(0x00036678, 0x00036660, 0xF877F9C8, 0) Err#62 ETIME
/6@6:    1.4974 <- libc:_lwp_cond_timedwait() = 62
/6@6:    1.4979 -> libc:gettimeofday(0xf877fa98, 0x0, 0xb, 0x3e8)
/6@6:    1.5099 <- libc:gettimeofday() = 0
/6@6:    1.5124 -> libc:_lwp_cond_timedwait(0x36678, 0x36660, 0xf877faa0, 0x3e8)
/14:     1.5372 pollsys(0x00000000, 0, 0xB3A7FD70, 0x00000000)  = 0
/14@14:  1.5374 <- libc:poll() = 0
/14@14:  1.5386 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  1.5397 <- libc:gethrtime() = 0x46c7d3
/14@14:  1.5403 -> libc:gethrtime(0xa, 0x53164efc, 0xfef41d48, 0xfeef0000)
/14@14:  1.5407 <- libc:gethrtime() = 0x46c7d3
/14@14:  1.5416 -> libc:poll(0x0, 0x0, 0x1f4, 0xf4240)
/14:     2.0432 pollsys(0x00000000, 0, 0xB3A7FD70, 0x00000000)  = 0
/14@14:  2.0434 <- libc:poll() = 0
/14@14:  2.0445 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  2.0455 <- libc:gethrtime() = 0x46c7d3
/14@14:  2.0464 -> libc:gethrtime(0xa, 0x7140c240, 0xfef41d48, 0xfeef0000)
/14@14:  2.0470 <- libc:gethrtime() = 0x46c7d3
/14@14:  2.0483 -> libc:poll(0x0, 0x0, 0x1f4, 0xf4240)
/6:      2.5111 lwp_cond_wait(0x00036678, 0x00036660, 0xF877F9C8, 0) Err#62 ETIME
/6@6:    2.5127 <- libc:_lwp_cond_timedwait() = 62
/6@6:    2.5134 -> libc:gettimeofday(0xf877fa98, 0x0, 0xb, 0x3e8)
/6@6:    2.5139 <- libc:gettimeofday() = 0
/6@6:    2.5151 -> libc:_lwp_cond_timedwait(0x36678, 0x36660, 0xf877faa0, 0x3e8)
/14:     2.5492 pollsys(0x00000000, 0, 0xB3A7FD70, 0x00000000)  = 0
/14@14:  2.5495 <- libc:poll() = 0
/14@14:  2.5504 -> libc:gethrtime(0x0, 0x0, 0x0, 0x0)
/14@14:  2.5510 <- libc:gethrtime() = 0x46c7d3
 

Well, yes, it'd appear to work just fine - we've reduced frequency from twenty times a second to twice a second - a factor 10 better than before.

But does jstat still work? Yes it does! I can't guarantee that it's as accurate as before for everything, but it's still collecting data and this data is still accessible:

$ jstat -class file:///tmp/hsperfdata_nstephen/43555
Loaded  Bytes  Unloaded  Bytes     Time   
   312   322.8        0     0.0       0.07
 

So, with my daemonized Java process, my machine's CPU usage as indicated by 'uptime', when otherwise idle, is now 10 times closer to zero, which is good news as far as I'm concerned.

( Sep 29 2006, 03:00:00 PM CEST ) Permalink

Comments:

Post a Comment:
Comments are closed for this entry.