Friday, February 3, 2017

Oracle JVM Java OutOfMemoryError and lazy GC

Java Stored Procedure running in Oracle RDBMS embedded JVM can throw OutOfMemoryError in connection with lazy GC.
All tests are done in Oracle 12.1.0.2.0.


1. Test Setup



create or replace java source named "OracleJavaOOM512" as
public class OracleJavaOOM512 {
  // final static byte[] SB = new byte[1024*1024*500];  // workaround
  public static void createBuffer(int bufferSize) {
    // OracleRuntime.setMaxRunspaceSize(1024*1024*1024) // set 1GB, no help
    // System.gc();
    byte[] SB = new byte[bufferSize];
  }
}
/

alter JAVA CLASS "OracleJavaOOM512" COMPILE;

create or replace procedure createBuffer512(p_buffer_size in number) is language java
name 'OracleJavaOOM512.createBuffer(int)';
/

create or replace procedure createBuffer512_loop 
  (p_cnt number, p_start number, p_delta number, p_sleep_seconds number := 0) as
  l_size number := p_start;
begin
  for i in 1..p_cnt loop
    dbms_lock.sleep(p_sleep_seconds);
    dbms_output.put_line('Step -- '||i||' --, Buffer Size (MB) = '||l_size);
    createBuffer512(1024*1024*l_size);
    l_size := l_size + p_delta;
  end loop;
end;
/


2. Tests


Run first test to allocate 511MB:

Sqlplus > exec createBuffer512(1024*1024*511);
    PL/SQL procedure successfully completed

succeeded without Error.

Run second test for 512MB:

Sqlplus > exec createBuffer512(1024*1024*512);
    ORA-29532: Java call terminated by uncaught Java exception: java.lang.OutOfMemoryError
    ORA-06512: at "S.CREATEBUFFER512", line 1

It hits the general Java error: ORA-29532 java.lang.OutOfMemoryError, which probably indicates that the JVM is limited by 512MB for one single object instance, in this test, it is "new byte[bufferSize]".

Calling OracleRuntime.setMaxRunspaceSize(1024*1024*1024) to set MaxRunspaceSize to 1GB does not help.

If interested, a 29532 event trace can also be made:

Sqlplus > alter session set max_dump_file_size = UNLIMITED;
Sqlplus > alter session set tracefile_identifier='OOM512_29532'; 
Sqlplus > alter session set events '29532 trace name errorstack level 3'; 
Sqlplus > exec createBuffer512(1024*1024*512);
    ORA-29532: Java call terminated by uncaught Java exception: java.lang.OutOfMemoryError

Open a new Oracle session, run third test case, which gradually allocates memory from 1MB to 511MB, each time increases 1MB per call.

Sqlplus > exec createBuffer512_loop(511, 1, 1);

    Step -- 1 --, Buffer Size (MB) = 1
    Step -- 2 --, Buffer Size (MB) = 2
    ...
    Step -- 264 --, Buffer Size (MB) = 264
    Step -- 265 --, Buffer Size (MB) = 265
    BEGIN createBuffer512_loop(511, 1, 1); END;
    
    *
    ERROR at line 1:
    ORA-29532: Java call terminated by uncaught Java exception: java.lang.OutOfMemoryError
    ORA-04030: out of process memory when trying to allocate 282220624 bytes (joxu pga heap,f:OldSpace)
    ORA-06512: at "S.CREATEBUFFER512", line 1
    ORA-06512: at "S.CREATEBUFFER512_LOOP", line 8
    ORA-06512: at line 1

It throws OutOfMemoryError when allocating 266MB (the number can be varied, but before reaching 511), and generates trace file and incident file. Note that in this test, besides the above general Java error: ORA-29532, it throws also ORA-04030.


3. Oracle JVM Garbage Collection


Oracle 12c Document: Database Java Developer's Guide Chapter 1 Introduction to Java in Oracle Database, Section Memory Spaces Management depicts a 3-Layer Java memory Management:
  1.  New Space in Call space
  2.  Old Space in Call space
  3.  Session Memory in Session space
and 3 corresponding garbage collection algorithms:
  1.  Generational scavenging for short-lived objects
  2.  Mark and lazy sweep collection for objects that exist for the life of a single call
  3.  Copying collector for long-lived objects, that is, objects that live across calls within a session
It also mentions that Oracle JVM Garbage collection uses Oracle Database scheduling facilities.

Book: Oracle Database Programming using Java and Web Services (Kuassi Mensah) Section 2.2 Java Memory Management (Page 41-51) reveals internal GC mechanisms (probably written for Oracle 10g).

Garbage Collection Techniques
  The OracleJVM memory manager uses a set of GC techniques for its various memory structures (listed in Table 2.1), including the generational GC, mark-sweep GC, and copy GC.

These 3 GC techniques in Oracle 10g look similar to GC algorithms in 12c.

Mark-sweep GC
  A mark-sweep GC consists of two phases: (1) the mark phase (garbage detection) and the (2) sweep phase (Garbage Collection). The sweep phase places the “garbaged” memory on the freelists. The allocation is serviced out of the freelists. Lazy sweeping is a variation technique that marks objects normally, but, in the sweep phase, leaves it up to the allocation phase to determine whether the marked objects are live (no need to reclaim) or not (can be reclaimed), rather than sweeping the object memory at that time.

Java Memory Areas
  Oldspace is an object memory used for holding long-lived or large objects (i.e., larger than 1-K Bytes) for the duration of a call. It is cleaned up using Marksweep GC (described earlier) for large objects; it uses a variation of “lazy sweeping” for small objects.

Looking up Table 2.1 "Summary of OracleJVM Memory Structure" (Page 50), since our test allocation is more than 1-K Bytes, it should be allocated into Old-space using Buddy memory allocation, and garbage collected by Mark-Sweep.


4. Reasoning


The error message in Sqlplus Window indicates that memory problem is caused by:
    joxu pga heap,f:OldSpace
which also appears in trace file.

An excerpt of incident file looks like:

=======================================
TOP 10 MEMORY USES FOR THIS PROCESS
---------------------------------------
84%   91 MB,  17 chunks: "f:OldSpace                "  JAVA
         joxu pga heap   ds=fffffd7ffbe8bcc8  dsprt=fffffd7ffc0a9078
 2% 2432 KB,  92 chunks: "free memory               "  
         pga heap        ds=fffffd7ffc345640  dsprt=0
 2% 1767 KB, 437 chunks: "free memory               "  
         session heap    ds=fffffd7ffc02d728  dsprt=fffffd7ffc358350
 1% 1064 KB,   2 chunks: "permanent memory          "  JAVA
         joxu pga heap   ds=fffffd7ffbe8bcc8  dsprt=fffffd7ffc0a9078
...
=======================================
PRIVATE MEMORY SUMMARY FOR THIS PROCESS
---------------------------------------
******************************************************
PRIVATE HEAP SUMMARY DUMP
108 MB total:
   104 MB commented, 519 KB permanent
  4125 KB free (0 KB in empty extents),
      97 MB,   1 heap:    "joxp heap      "            2315 KB free held
    8894 KB,   1 heap:    "session heap   "            679 KB free held
------------------------------------------------------
Summary of subheaps at depth 1
104 MB total:
   101 MB commented, 952 KB permanent
  1814 KB free (25 KB in empty extents),
      94 MB,   1 heap:    "joxu pga heap  "           
------------------------------------------------------
Summary of subheaps at depth 2
99 MB total:
    97 MB commented, 1408 KB permanent
   272 KB free (0 KB in empty extents),
      91 MB,  17 chunks:  "f:OldSpace                "

=========================================
REAL-FREE ALLOCATOR DUMP FOR THIS PROCESS
-----------------------------------------
 
Dump of Real-Free Memory Allocator Heap [0xfffffd7ffbfed000]
mag=0xfefe0001 flg=0x5000003 fds=0x0 blksz=65536
blkdstbl=0xfffffd7ffbfed010, iniblk=523264 maxblk=524288 numsegs=255
In-use num=133 siz=112984064, Freeable num=224 siz=2914910208, Free num=15 siz=21889024

...
-------------------------
Top 10 processes:
-------------------------
(percentage is of 27 GB total allocated memory)
99% pid 66: 105 MB used of 27 GB allocated (27 GB freeable) <= CURRENT PROC
 0% pid 11: 7792 KB used of 8294 KB allocated 
...
======================================================
ESTIMATED MEMORY USES FOR ALL PROCESSES
------------------------------------------------------
(from 1 snapshot out of 67 processes)
99%     27 GB,   66 processes: "unsnapshotted               "   
 1%    172 MB,    1 process  : "free memory                 "   
           pga heap             95 chunks
 0%     93 MB,    1 process  : "f:OldSpace                  "  JAVA
...

******************* Dumping process map ****************
    Start addr     -      End addr       Size    PgSz       Shmid    Perms  Object name
------------------ -  ----------------- -------  ----     ---------- ------------------------------
...
        0x80000000 -        0x1af000000 4964352K    4K     0x40000039 rwxs-  [ anon ] osm 
...
0xfffffd792326d000 - 0xfffffd79333bd000 263488K    4K     0xffffffff rw---  [ anon ] 
0xfffffd793355d000 - 0xfffffd794365d000 263168K    4K     0xffffffff rw---  [ anon ] 
0xfffffd794374d000 - 0xfffffd795374d000 262144K    4K     0xffffffff rw---  [ anon ] 
0xfffffd795377d000 - 0xfffffd796293d000 247552K    4K     0xffffffff rw---  [ anon ] 
0xfffffd7962aad000 - 0xfffffd7971b6d000 246528K    4K     0xffffffff rw---  [ anon ]
...

There are about 30GB segments marked as:
   rw--- [ anon ]
which are session's PGA.

Above output shows:
   105 MB used of 27 GB allocated (27 GB freeable) <= CURRENT PROC
among 27 GB allocated, almost all of which is freeable.

However due to Oracle JVM lazy GC, they are not immediately released, and eventually leads to:
   ORA-04030: out of process memory when trying to allocate 283285584 bytes (joxu pga heap,f:OldSpace)

(By the way, biggest chunk 4964352K is SGA, created with Optimized Shared Memory (OSM) available since Oracle 12c when sga_max_size is not configured, or is set greater than the computed value.
See ISM, DISM in Oracle 11g Blog: PGA, SGA memory usage watching from UNIX)

Concerning the freeable memory, V$PROCESS_MEMORY documentation said:

    For the "Freeable" category, Column ALLOCATED is the amount of free PGA memory eligible to be released to the operating system.

Note that OutOfMemoryError process still holds the "Freeable" memory and does not immediately put them back to freelists since the process is still alive. It takes time for GC to reclaim them gradually. If the process (Oracle session) terminated (disconnected), it is immediately reusable by other processes.

You can query V$PROCESS_MEMORY or V$PROCESS to monitor it.

Crosschecking with Solaris pmap output:

oracle@test$ pmap -x 1122
1122:  testdb (LOCAL=NO)
         Address     Kbytes        RSS       Anon     Locked Mode   Mapped File
...         
0000000080000000    4964352    4964352          -    4964352 rwxs-    [ osm shmid=0x40000039 ]
...
FFFFFD785FC1D000     306816     306812     306812          - rw---    [ anon ]
FFFFFD787296D000     305792     305788     305788          - rw---    [ anon ]
FFFFFD78854BD000     297472     297452     297452          - rw---    [ anon ]
FFFFFD7897F1D000     296448     296412     296412          - rw---    [ anon ]
FFFFFD78AA76D000     295424     295420     295420          - rw---    [ anon ]
FFFFFD78BCECD000     294336     294332     294332          - rw---    [ anon ]
...
---------------- ---------- ---------- ---------- ----------
        total Kb   31990020   31933340   26615772    4982784

(you can even use Solaris MDB Formatting Dcmds to display content at one above virtual address space)

Incident file and pmap output show that the majority of memory are allocated as "anon" pages, but not removed from the mappings, which means that there are more mmap than munmap.

We can see this un-matching mmap and munmap with:

oracle@test$ truss -cp -t mmap,munmap 1122

syscall               seconds   calls  errors
mmap                     .056    1014
munmap                  6.447      68

-- Linux trace mmap and munmap: strace -e trace=memory -p pid

The output shows that munmap is 1716 (=(6.447/68)/(0.056/1014)) times expensive. That is probably one reason why the GC strategy is lazy delayed.

or watch the details by:

oracle@test$ truss -p -t mmap,munmap 1122  

...
mmap(0xFFFFFD7C6E8ED000, 202375168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7C6E8ED000
mmap(0xFFFFFD7C625ED000, 203423744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7C625ED000
mmap(0xFFFFFD7C561FD000, 204537856, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7C561FD000
...
mmap(0xFFFFFD7A43ACD000, 243924992, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7A43ACD000
mmap(0xFFFFFD7A34F7D000, 244973568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7A34F7D000
mmap(0xFFFFFD7A2632D000, 246022144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7A2632D000
mmap(0xFFFFFD7A175DD000, 247136256, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, 4294967295, 0) = 0xFFFFFD7A175DD000
munmap(0xFFFFFD7A2632D000, 246022144)           = 0
munmap(0xFFFFFD7A34F7D000, 244973568)           = 0
munmap(0xFFFFFD7A43ACD000, 243924992)           = 0
...

It shows that not all mapped chunks are un-mapped, and unmapping occurs in reverse order (last mapping is first unmapped).

With Solaris dtrace, we can count number of mmap and munmap, as well as sum of their amount (in MB). The large difference between mmap and munmap is probably pointing to the unreleased "Freeable" memory.

oracle@test$ dtrace -n 'syscall::mmap:entry,syscall::munmap:entry/pid==1122 && arg0 != 0x0/
{
 @CNT[probefunc] = count();
 @SUM[probefunc] = sum(arg1/1024/1024);
}
END
{
 printf("\n%10s %10s  %10s\n",  "NAME", "COUNT", "SUM(MB)");
 printa(  "%10s %10@d %10@d\n",         @CNT,   @SUM);
}'

------------ output -------------
      NAME      COUNT     SUM(MB)
    munmap         44       3987
      mmap        404      32400

There are 360 (404-44) more mmap, 32400 MB allocated, but only 3987 MB de-allocated. which results in 28413 (32400-3987) MB unreleased allocation.

With Section 7 appended Dtrace script, we can trace the details of each mmap and munmap. For the OutOfMemoryError execution:

Sqlplus > exec createBuffer512_loop(511, 1, 1);

    Step -- 1 --, Buffer Size (MB) = 1
    Step -- 2 --, Buffer Size (MB) = 2
    ...
    Step -- 262 --, Buffer Size (MB) = 262
    Step -- 263 --, Buffer Size (MB) = 263
    Step -- 264 --, Buffer Size (MB) = 264
    Step -- 265 --, Buffer Size (MB) = 265
    BEGIN createBuffer512_loop(511, 1, 1); END;
    
    *
    ERROR at line 1:
    ORA-29532: Java call terminated by uncaught Java exception: java.lang.OutOfMemoryError
    ORA-04030: out of process memory when trying to allocate 282220624 bytes (joxu pga heap,f:OldSpace)
    ORA-06512: at "S.CREATEBUFFER512", line 1
    ORA-06512: at "S.CREATEBUFFER512_LOOP", line 8
    ORA-06512: at line 1
Dtrace output looks like:
    
  => mmap  2017 Feb  5 14:16:01: mmap(addr=0xFFFF80FFBA5CF000, len=1114112, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:16:01: 0xFFFF80FFBA5CF000
**++** increase =   1114112(  1(MB)), mmap# = 1, mmap_MB = 1, munmap# = 0, munmap_MB = 0, sum = 1

  => mmap  2017 Feb  5 14:16:01: mmap(addr=0xFFFF80FFBA1AF000, len=1114112, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:16:01: 0xFFFF80FFBA1AF000
**++** increase =   1114112(  1(MB)), mmap# = 2, mmap_MB = 2, munmap# = 0, munmap_MB = 0, sum = 2

...

  => mmap  2017 Feb  5 14:19:43: mmap(addr=0xFFFF80FAA317F000, len=253493248, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:19:43: 0xFFFF80FAA317F000
**++** increase = 253493248(241(MB)), mmap# = 359, mmap_MB = 28726, munmap# = 44, munmap_MB = 8671, sum = 20055

  => munmap 2017 Feb  5 14:19:54: munmap(addr=0xFFFF80FAB24AF000, len=252444672)
  <= munmap 2017 Feb  5 14:19:54: 0.
**--** decrease = 252444672(240(MB)), mmap# = 359, mmap_MB = 28726, munmap# = 45, munmap_MB = 8911, sum = 19815

...

  => mmap  2017 Feb  5 14:20:04: mmap(addr=0xFFFF80FAE901F000, len=276955136, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:20:04: 0xFFFF80FAE901F000
**++** increase = 276955136(264(MB)), mmap# = 391, mmap_MB = 31308, munmap# = 58, munmap_MB = 11940, sum = 19368

  => mmap  2017 Feb  5 14:20:04: mmap(addr=0xFFFF80FAD864F000, len=278003712, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:20:04: 0xFFFF80FAD864F000
**++** increase = 278003712(265(MB)), mmap# = 393, mmap_MB = 31573, munmap# = 58, munmap_MB = 11940, sum = 19633

  => mmap  2017 Feb  5 14:20:06: mmap(addr=0xFFFF80FAB6FAF000, len=280100864, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:20:06: 0xFFFF80FAB6FAF000
**++** increase = 280100864(267(MB)), mmap# = 397, mmap_MB = 32106, munmap# = 58, munmap_MB = 11940, sum = 20166

  => mmap  2017 Feb  5 14:20:09: mmap(addr=0xFFFF80FA924DF000, len=281214976, prot=0x3, flags=0x112, fildes=4294967295, off=0)
  <= mmap  2017 Feb  5 14:20:09: 0xFFFF80FA924DF000
**++** increase = 281214976(268(MB)), mmap# = 399, mmap_MB = 32374, munmap# = 58, munmap_MB = 11940, sum = 20434

...

  :END
      NAME      COUNT     SUM(MB)
    munmap         93      15793
      mmap        405      32375
The error message indicates that OutOfMemoryError is raised when trying to allocate 282,220,624 bytes (joxu pga heap,f:OldSpace), where 282,220,624 bytes is about 269 MB. If we look the last line of Dtrace output, the last successful allocation is 268 MB, the next allocation of one more MB (p_delta of createBuffer512_loop call is 1 MB) hits the error.

Above createBuffer512_loop(511, 1, 1) is a loop of allocation with each step increasing 1 MB. It seems that each step requires a new memory chunk to be allocated since the previous step allocated memory is 1 MB smaller, which can not be reused and hence assigned to Category "Freeable". If such steps continue, in step 256, it will reach 32 GB (256*(1+256)/2=32,768 MB). But as shown above, RDBMS also makes un-allocation (munmap) to release certain allocated memory, that probably why it results in error in Step 265.

One worst speculation could also be that RDBMS only sums up allocated memory (ignore un-allocated), when reaching limit of 32 GB, it raises OutOfMemoryError.

The last line in Dtrace output shows: sum = 20,434 MB, which computed by 32,374 (mmap_MB) - 11,940 (munmap_MB). It corresponds to the UNIX command output:

  top.RES, prstat.RSS, "ps aux".RSS, "pmap -x".Anon total
In most tests, it is about 15% more than v$process_memory Category "Freeable" ("Freeable" is almost total of session PGA memory), for example:

select round(allocated/1e6) allocated_mb, v.* from v$process_memory v
where pid in (22) 
order by allocated desc;

  ALLOCATED_MB PID  SERIAL#  CATEGORY       ALLOCATED       Used  MAX_ALLOCATED
  ------------ ---- -------  --------  --------------  ---------  -------------
        17,180   22      53  Freeable  17,179,869,184          0  
           398   22      53  Other        397,914,636             3,533,074,452
             4   22      53  JAVA           4,167,792  4,160,648    548,833,672
             3   22      53  PL/SQL         2,622,904  2,533,008      2,924,192
             0   22      53  SQL              224,248     17,432      7,021,552
Note that v$process_memory.MAX_ALLOCATED is not filled (missed) for Category "Freeable", we will give a further look of this field in its historical view: dba_hist_process_mem_summary.

Probably We can say that this OutOfMemoryError is indeed a PGA Freeable memory problem, instead of OraleJVM Java.

For mmap prot / flags arguments, we can find the mapping in truss command output and sys/mman.h.
   
---------- truss output and mmap prot/flags  ----------
mmap(0xFFFF80FFA95D2000, 23461888, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANON, -1, 0) = 0xFFFF80FFA95D2000

  prot=0x3    (PROT_READ|PROT_WRITE)
  flags=0x112 (MAP_PRIVATE|MAP_FIXED|MAP_ANON)

mmap(0xFFFF80FFB99E2000, 2818048, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_NORESERVE|MAP_ANON, -1, 12460032) = 0xFFFF80FFB99E2000

  prot=0x0    (PROT_NONE)
  flags=0x152 (MAP_PRIVATE|MAP_FIXED|MAP_NORESERVE|MAP_ANON)
  
---------- /usr/include/sys/mman.h ----------
#define PROT_READ       0x1             /* pages can be read */
#define PROT_WRITE      0x2             /* pages can be written */
#define PROT_EXEC       0x4             /* pages can be executed */

#define PROT_NONE       0x0             /* pages cannot be accessed */

/* sharing types:  must choose either SHARED or PRIVATE */
#define MAP_SHARED      1               /* share changes */
#define MAP_PRIVATE     2               /* changes are private */
#define MAP_TYPE        0xf             /* mask for share type */

/* other flags to mmap (or-ed in to MAP_SHARED or MAP_PRIVATE) */
#define MAP_FIXED       0x10            /* user assigns address */
#define MAP_NORESERVE   0x40            /* don't reserve needed swap area */
#define MAP_ANON        0x100           /* map anonymous pages directly */
#define MAP_ANONYMOUS   MAP_ANON        /* (source compatibility) */
Other dtrace commands can help us understand largeobj (oldSpace) allocation and GC activity.

oracle@test$ dtrace -n 'pid$target::eoa_new_largeobj:entry {@[pid, ustack(40, 0)] = count(); }' -p 1122

oracle@test$ dtrace -n 'pid$target::eoa_oldspace_gc_scan_xt:entry {@[pid, ustack(40, 0)] = count(); }' -p 1122

Two tests below have no problem since the first step already allocated the max needed memory, which is sufficient for the usage of all later steps (no more need to any allocation):

Sqlplus > exec createBuffer512_loop(511, 511, -1);

Sqlplus > exec createBuffer512_loop(511, 511, 0);

Both above tests propose us a quick workaround by introducing a class (static) variable and setting it to a enough big value as follows:
    final static byte[] SB = new byte[1024*1024*500];
Note that java class variable in Java Stored Procedure is stateful object, which survives across Java calls in the same RDBMS session (like PL/SQL Package variable), and can be reset by dbms_java.endsession (similar to dbms_session.reset_package), which clears any Java session state remaining from previous execution of Java in the current RDBMS session (introduced in 11g release 1, see Java Developer's Guide - 4.5 Two-Tier Duration for Java Session State ). Therefore, if JAVA VM memory is under pressure, dbms_java.endsession can be called after certain threshold to release the memory, a trade-off between Memory (JavaGC) and Performance.

Oracle Plsql starts OracleJVM by the call stack:

  kkxmjexe
  jox_invoke_java_
  joe_run_vm
JVM initializes Java session, loads classes, and finally clears session with:

  joevm_init_session_imcache       -- imcache in-memory cache
  joevm_in_clinit                  -- multi calls, one call for one class init
  joevm_clear_session_imcache
If we call dbms_java.endsession after the excution of java stored procedure (createBuffer512 in our example, see later test code), Java session state is cleaned (hence Java memory freed). The next excution of createBuffer512 makes kkxmjexe to re-execute above three steps (initialize, load, clear). hence performance sacrifice. It should be called in a controlled manner, see later example: procedure createBuffer512_loop calling dbms_java.endsession.

Here the call stacks of init_session and clear_session, which are re-run if Java session state is reset by dbms_java.endsession:

---------- joevm_init_session_imcache Call Stack ----------
#0  0x000000000585d5e0 in joevm_init_session_imcache ()
#1  0x00000000056ea471 in eoa_startup_default_objmems ()
#2  0x00000000056e9dd9 in ioesub_init_call ()
#3  0x00000000057304d2 in seoa_note_stack_outside ()
#4  0x00000000056e9ae1 in ioe_init_call ()
#5  0x0000000004d7ce51 in jox03_init_call ()
#6  0x0000000004d02f67 in jox_invoke_java_ ()
#7  0x0000000009c6db05 in kkxmjexe ()
#8  0x0000000004bed088 in kgmexcb ()
#9  0x000000000275a64b in kkxmswu ()
#10 0x0000000004beabc3 in kgmexwi ()
#11 0x0000000004be997a in kgmexec ()
#12 0x0000000011889684 in pefjavacal ()
#13 0x0000000005628dd1 in pefcal ()
#14 0x00000000054cda7b in pevm_FCAL ()
#15 0x00000000054aeb4e in pfrinstr_FCAL ()
#16 0x00000000129e612c in pfrrun_no_tool ()
#17 0x00000000129e4a96 in pfrrun ()
#18 0x00000000129edac0 in plsql_run ()

---------- joevm_clear_session_imcache Call Stack ----------
#0  0x0000000005859340 in joevm_clear_session_imcache ()
#1  0x00000000056e8209 in eoa_vm_call_cleanup ()
#2  0x00000000056e7b64 in ioesub_end_call ()
#3  0x00000000057304d2 in seoa_note_stack_outside ()
#4  0x00000000056e8809 in ioe_end_call ()
#5  0x0000000004c6a4b5 in joxenc ()
#6  0x0000000004c69609 in joxdlc_internal ()
#7  0x0000000004c68e73 in jox_flush_java_session ()
#8  0x0000000004d2cb10 in jox_ioe_call_java_ ()
#9  0x000000000c44c587 in pcklfun ()
#10 0x00000000056cb29b in spefcpfa ()
#11 0x000000000568d267 in spefmccallstd ()
#12 0x000000000562f45b in peftrusted ()
#13 0x0000000004113278 in psdexsp ()
#14 0x00000000126edae2 in rpiswu2 ()
#15 0x000000000373cdea in kxe_push_env_internal_pp_ ()
#16 0x00000000037a4bb5 in kkx_push_env_for_ICD_for_new_session ()
#17 0x0000000004112c63 in psdextp ()
#18 0x0000000005629427 in pefccal ()
#19 0x0000000005628cdf in pefcal ()
#20 0x00000000054cda7b in pevm_FCAL ()
#21 0x00000000054aeb4e in pfrinstr_FCAL ()
#22 0x00000000129e612c in pfrrun_no_tool ()
#23 0x00000000129e4a96 in pfrrun ()
#24 0x00000000129edac0 in plsql_run ()


5. Possible Causes and Fixes


When OracleJVM hitting OutOfMemoryError, it often falls into two v$process_memory categorie: Java or Freeable. Here two possible fixes.


5.1. Category JAVA


If v$process_memory for the session (Oracle PID) shows that high memory usage is Category JAVA, or incident file (ORA-04030: out of process memory) has the top consumer JAVA:

  100%   32 GB, 292 chunks: "f:OldSpace                "  JAVA
           joxu pga heap   ds=ffff80ffbc1abc60  dsprt=ffff80ffbacb9078
we can call dbms_java.endsession to clear any Java session state, which makes createBuffer512_loop(511, 1, 1) successfully completed:

create or replace procedure createBuffer512_loop 
  (p_cnt    number, p_start number, p_delta number, p_sleep_seconds number := 0) as
  l_size    number := p_start;
  l_ret     varchar2(100);
  l_chuncks number := 1;    --10  -- control dbms_java.endsession call frequency
begin
  for i in 1..p_cnt loop
    dbms_lock.sleep(p_sleep_seconds);
    dbms_output.put_line('Step -- '||i||' --, Buffer Size (MB) = '||l_size);
    createBuffer512(1024*1024*l_size);
    l_size := l_size + p_delta;
    if mod(i, l_chuncks) = 0 then
      l_ret  := dbms_java.endsession;
      --l_ret  := dbms_java.endsession_and_related_state;   
      --dbms_session.free_unused_user_memory;              -- no help
      dbms_output.put_line('dbms_java.endsession return = '||l_ret);
    end if;
  end loop;
end;
/

Sqlplus > exec createBuffer512_loop(511, 1, 1);
    Step -- 1 --, Buffer Size (MB) = 1
    dbms_java.endsession return = java session ended
    ...
    Step -- 510 --, Buffer Size (MB) = 510
    dbms_java.endsession return = java session ended
    Step -- 511 --, Buffer Size (MB) = 511
    dbms_java.endsession return = java session ended
    
    PL/SQL procedure successfully completed.


5.2. Category Freeable


If v$process_memory for the session (Oracle PID) shows that high memory usage is Category Freeable, or incident file (ORA-04030: out of process memory) has the top consumer "unsnapshotted":

  100%     17 GB,   36 processes: "unsnapshotted               "   
    0%   1247 KB,    1 process  : "f:OldSpace                  "  JAVA
             joxu pga heap        14 chunks
We can try to allocate max reuired memory in the very first call of OracleJVM, for example, above test:

  exec createBuffer512_loop(511, 511, 0);
We can also try to make certain pause (sleeping) and and expect that RDBMS can have time to collect the freeable space (not Java GC):

  exec createBuffer512_loop(511, 1, 1, 0.4);
As tested, this does not always work since the freeable space is marked as "unsnapshotted", and only released when the session disconnected.


6. Oracle Performance Views


Query GC statistics and process Java memory by:

select s.program, s.sid, n.name p_name, t.value, round(t.value/1e6, 2) mb
  from v$session s, v$sesstat t, v$statname n 
 where 1=1
   and s.sid=t.sid 
   and n.statistic# = t.statistic# 
   and name = 'java call heap gc count'
   and s.sid in (920);

select round(allocated/1e6) mb, v.* from v$process_memory v
 where pid in (66) 
 order by allocated desc;

select h.end_interval_time, allocated_max, num_processes, p.* 
  from dba_hist_process_mem_summary p, dba_hist_snapshot h 
 where h.snap_id = p.snap_id 
   and category ='JAVA' 
 order by h.end_interval_time desc;

Populate v$process_memory_detail by:
(see dbms_session.get_package_memory_utilization and limitations)

Sqlplus > exec pga_sampling(920, 120, 1);

and then run query:

select round(bytes/1024/1024, 2) mb, v.* from  process_memory_detail_v v
where 1=1
  and name in ('f:OldSpace', 'free memory')
  and round(bytes/1024/1024, 2) > 10
order by timestamp;

With following query, we can displays historical information about dynamic PGA memory usage and map them to AWR Section "Process Memory Summary" column names. One problem with this view is that max_allocated_max (AWR "Hist Max Alloc (MB)") is always missed for Category "Freeable" (that could be a bug). As previously discussed, this "Freeable" is exactly the figures about unreleased memory (leaks).

select h.end_interval_time, m.category
        -- total allocated for all processes in that Category at the snap_id
      ,round(allocated_total/1024/1024)   "Alloc (MB)"   
        -- max allocated for one single process in that Category at the snap_id       
      ,round(allocated_max/1024/1024)     "Max Alloc (MB)"    
        -- max ever allocated for one single process in that Category during snap interval  
        -- max of v$process_memory.MAX_ALLOCATED. It is null for Category "Freeable" 
      ,round(max_allocated_max/1024/1024) "Hist Max Alloc (MB)" 
      ,m.* 
from dba_hist_process_mem_summary m, dba_hist_snapshot h 
where m.snap_id = h.snap_id 
order by m.snap_id desc, m.category;
Oracle performance views v$process, v$process_memory reports "freeable" memory, whereas view:
   v$sesstat, v$active_session_history, dba_hist_active_sess_history
do not record this real figure, probably due to "Freeable" memory (27GB) which is signaled by the 27 GB "unsnapshotted" line in the incident file:
   99% 27 GB, 66 processes: "unsnapshotted "


7. Linux Test


All above tests are performed on Solaris. Repeat the same test on a Linux with 24GB of total usable memory.

Sqlplus > exec createBuffer512_loop(511, 1, 1);

    ORA-03113: end-of-file on communication channel
    Process ID: 32251

-- Linux trace all memory mapping related system calls: mmap and munmap
--    strace -e trace=memory -p 32251

There are no trace file or incident file generated. Checking dmesg out, it looks like that process is killed by PSP0 (call oom-killer) when reaching system limit of 24GB, but before reaching 32GB PGA limit.

[08:07:36] ora_psp0_td invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
[08:07:36] ora_psp0_td cpuset=/ mems_allowed=0
[08:07:36] Pid: 23201, comm: ora_psp0_td Not tainted 2.6.32-642.6.2.el6.x86_64 #1
[08:07:36] Call Trace:
[08:07:36] [] ? dump_header+0x90/0x1b0
[08:07:36] [] ? security_real_capable_noaudit+0x3c/0x70
[08:07:36] [] ? oom_kill_process+0x82/0x2a0
[08:07:36] [] ? select_bad_process+0xe1/0x120
[08:07:36] [] ? out_of_memory+0x220/0x3c0
[08:07:36] [] ? __alloc_pages_nodemask+0x93c/0x950
[08:07:36] [] ? alloc_pages_current+0xaa/0x110
[08:07:36] [] ? __page_cache_alloc+0x87/0x90
[08:07:36] [] ? find_get_page+0x1e/0xa0
[08:07:36] [] ? filemap_fault+0x1a7/0x500
[08:07:36] [] ? __do_fault+0x54/0x530
[08:07:36] [] ? handle_pte_fault+0xf7/0xb20
[08:07:36] [] ? sem_lock+0x6c/0x130
[08:07:36] [] ? sys_semtimedop+0x338/0xae0
[08:07:36] [] ? handle_mm_fault+0x299/0x3d0
[08:07:36] [] ? __do_page_fault+0x146/0x500
[08:07:36] [] ? wait_consider_task+0x7e6/0xb20
[08:07:36] [] ? read_tsc+0x9/0x10
[08:07:36] [] ? ktime_get_ts+0xbf/0x100
[08:07:36] [] ? poll_select_copy_remaining+0xf8/0x150
[08:07:36] [] ? do_page_fault+0x3e/0xa0
[08:07:36] [] ? page_fault+0x25/0x30
...
[08:07:36] Out of memory: Kill process 32251 (oracle_32251_td) score 619 or sacrifice child
[08:07:36] Killed process 32251, UID 100, (oracle_32251_td) total-vm:20111724kB, anon-rss:15083248kB, file-rss:215188kB
[08:07:36] oracle_32251_td: page allocation failure. order:0, mode:0x280da
[08:07:36] Pid: 32251, comm: oracle_32251_td Not tainted 2.6.32-642.6.2.el6.x86_64 #1

Note Buddy memory request "order=0", and oom-killer reason: "score 619"


7. Dtrace for mmap and munmap



sudo dtrace -F -n '
BEGIN  {
   delta_size   = 0;
   mmap_mb      = 0;
   munmap_mb    = 0;
   mmap_count   = 0;
   munmap_count = 0;
} 
syscall::mmap:entry/pid == $1 && arg0 != 0x0/
{
   delta_size = arg1;    mmap_count++;
   mmap_mb    = mmap_mb + delta_size/1024/1024;
   printf("%Y: %s(addr=0x%X, len=%d, prot=0x%X, flags=0x%X, fildes=%d, off=%d)", 
           walltimestamp, probefunc, arg0, arg1, arg2, arg3, arg4, arg5);
   @CNT[probefunc] = count();
   @SUM[probefunc] = sum(arg1/1024/1024);
 }
syscall::mmap:return/pid == $1 && delta_size > 0/ 
{
   printf("%Y: 0x%X\n", walltimestamp, arg0);
   printf("**++** increase = %9d(%3d(MB)), mmap# = %d, mmap_MB = %d, munmap# = %d, munmap_MB = %d, sum = %d\n", 
           delta_size, delta_size/1024/1024, mmap_count, mmap_mb, munmap_count, munmap_mb, mmap_mb-munmap_mb);
   delta_size = 0;
}
syscall::munmap:entry/pid == $1/
{
   delta_size = arg1;    munmap_count++;
   munmap_mb   = munmap_mb + delta_size/1024/1024;
   printf("%Y: %s(addr=0x%X, len=%d)", walltimestamp, probefunc, arg0, arg1);
   @CNT[probefunc] = count();
   @SUM[probefunc] = sum(arg1/1024/1024);
}
syscall::munmap:return/pid == $1 && delta_size > 0/ 
{
   printf("%Y: %d.\n", walltimestamp, arg0);
   printf("**--** decrease = %9d(%3d(MB)), mmap# = %d, mmap_MB = %d, munmap# = %d, munmap_MB = %d, sum = %d\n", 
           delta_size, delta_size/1024/1024, mmap_count, mmap_mb, munmap_count, munmap_mb, mmap_mb-munmap_mb);
   delta_size = 0;
}
END
{
   printf("\n%10s %10s  %10s\n",  "NAME", "COUNT", "SUM(MB)");
   printa(  "%10s %10@d %10@d\n",         @CNT,   @SUM);
}' 1122


References


1. Database Java Developer's Guide Chapter 1 Introduction to Java in Oracle Database, Section Memory Spaces Management (Old Space)

2.Oracle Database Programming using Java and Web Services (Kuassi Mensah) Section 2.2 Java Memory Management (Page 41-49)

3. dbms_session.get_package_memory_utilization and limitations

4.Oracle Java OutOfMemoryError (11g Blog, no more reproducible in 12c)

5. PGA, SGA memory usage watching from UNIX

Monday, January 23, 2017

ORA-600 [4156] SAVEPOINT and PL/SQL Exception Handling

Oracle MOS (RDBMS 10.2.0.4)
     Bug 9471070 : ORA-600 [4156] GENERATED WITH EXECUTE IMMEDIATE AND SAVEPOINT
contains a TEST CASE:

create table t(id number, label varchar2(10));
insert into t(id, label) values(1, 'label');
commit;

-- ORIGINAL --
begin
  savepoint sp;
  update t set label = label where id = 1;
  execute immediate '
    begin
      raise_application_error(-20000, ''error-sp'');
    exception
      when others then
        rollback to savepoint sp;
        update t set label = label where id = 1;
        raise;
    end;';
end;
/

which outputs the Error:

ORA-00600: internal error code, arguments: [4156], [], [], [], [], [], [], [], [], [], [], []

and DIAGNOSTIC ANALYSIS wrote:

The ORA-600[4156] error means that we are checking the savepoint undo block address while rolling back the transaction. The savepoint does not currently belong to this transaction. So the operation in this testcase does not seem supported, but there is no documentation on this behavior.

In this Blog, we will try to deduce 4 further Variants which all generate the same error, and demonstrate that neither Exception Handler nor Execute Immediate is a necessary condition of such error.

In the original Test Case,

raise_application_error(-20000, ''error-sp''); 

is only to transfer code flow to exception handler, remove it, we get:

-- VARIANT-1 --
begin
  savepoint sp;
  update t set label = label where id = 1;
  execute immediate '
    begin
      rollback to savepoint sp;
      update t set label = label where id = 1;
      raise_application_error(-20000, ''error-sp'');
    end;';
end;
/

Unshelling EXECUTE IMMEDIATE, it is a derived Test Case:

-- VARIANT-2 --
savepoint sp;
update t set label = label where id = 1;           
begin
  rollback to savepoint sp;
  update t set label = label where id = 1;
  raise_application_error(-20000, 'error-sp');
end;
/

all generate the same ORA-600 [4156].

Applying the equivalent transformation to factor out:

raise_application_error(-20000, 'error-sp'); 

(See Book Expert Oracle Database Architecture (3rd Edition, Thomas Kyte, Darl Kuhn) - Chapter 8, Section: Atomicity (Page 277-283))

Savepoint sp;
statement;
If error then rollback to sp;

We render the Test Case as:

-- VARIANT-3 --
savepoint sp;
update t set label = label where id = 1;   
savepoint spx;        
begin
  rollback to savepoint sp;
  update t set label = label where id = 1;
  rollback to savepoint spx;    -- spx cleared away by "rollback to savepoint sp;"
  dbms_output.put_line('error-sp');
end;
/

it results in the same error:

ORA-00600: internal error code, arguments: [4156], [], [], [], [], [], [], [], [], [], [], []
ORA-01086: savepoint 'SPX' never established in this session or is invalid

which contains a second line indicating that the error is related to savepoint 'SPX'.

None of Exception Handler and Execute Immediate is involved in the above Test Code.

All above Test Cases generate the similar incident dumps as follows:

kturRollbackToSavepoint perm undokturRollbackToSavepoint savepoint uba: 0x00c0525c.0708.30 xid: 0x0051.021.00002fcd
kturRollbackToSavepoint current call savepoint: ksucaspt num: 1737  uba: 0x00c0525c.0708.30


Reasoning


Looking at the last transformed code,
  "rollback to savepoint sp"
jumps back to line
  "savepoint sp"
and hence scopes out line "savepoint spx" and make savepoint 'SPX' invisible. When running to the line "rollback to savepoint spx", 'SPX' is not able to find, and therefore the error: never established.

The graphic below depicts the partial intersection of savepoints' pair, which erases the 'savepoint spx' with runtime "goto" logic and makes it never reachable. A savepoints' pair is valid if they are pairwise disjoint, or one is a proper subset of another.

|------> savepoint sp;
|        update t set label = label where id = 1;   
|  |---> savepoint spx;        
|  |     begin
|--|---->  rollback to savepoint sp;
   |       update t set label = label where id = 1;
   |--->   rollback to savepoint spx; 
         end;

With the aid of above deliberate code transformations, we obtained 4 versions of code which produced the same error. It is not clear if they are all semantically equivalent, and internally identical.

Referring back to DIAGNOSTIC ANALYSIS again, it said:
   So the operation in this testcase does not seem supported, but there is no documentation on this behavior.

All tests are done in Oracle 11.2.0.4.0 and 12.1.0.2.0.

Implicit Savepoint and Execute immediate 


When unshelling EXECUTE IMMEDIATE in the second transformation, outermost BEGIN END is intentionally removed.

Without removing it, the equivalent code looks like:

savepoint spx;
begin
  savepoint sp;
  update t set label = label where id = 1;           
  begin
    rollback to savepoint sp;
    update t set label = label where id = 1;
    rollback to savepoint spx; 
    dbms_output.put_line('error-sp');
  end;
end;
/
--If error then rollback to spx;

It runs without error since there does not exist savepoint partially overlapping ('SP' is proper subset of 'SPX').

It seems that Oracle sets an implicit savepoint before EXECUTE IMMEDIATE to perform the dynamic statement parsing and executing. Factoring out EXECUTE IMMEDIATE by the same principle, we get a fourth variant which hits the same error:

-- VARIANT-4 --
begin
  savepoint sp;
  update t set label = label where id = 1;
  savepoint spx;
  execute immediate '
    begin
        rollback to savepoint sp;
        update t set label = label where id = 1;
        rollback to savepoint spx;
        dbms_output.put_line(''error-sp'');
    end;';
end;
/
--If error then rollback to spx;

Oracle Statement-Level Rollbacks said:
   Before executing any SQL statement, Oracle marks an implicit savepoint (not available to you).

Update (September 13, 2020), see Next Blog: The Second Test of ORA-600 [4156] Rolling Back To Savepoint.

ORA-01086


One ORA-01086 can be simply reproduced by:

SQL> rollback to savepoint spy;
  ORA-01086: savepoint 'SPY' never established in this session or is invalid


Savepoint Watching


Savepoint is probably implemented as Stack push and pop operations.

Looking ORA-600 [4156] trace and incident dumps, stack trace shows that subroutine psdtsv calls xctsav to set Savepoint, ksupop to popup Savepoint and xctrsp to rollback savepoint..

One can also run code below,

begin
  for i in 1..1000000 loop
    dbms_transaction.savepoint('sp'||i);
    dbms_transaction.rollback_savepoint('sp'||i);
  end loop;
end;
/

and print process stack trace by UNIX command, for example, Solaris, pstack.

References

1. Expert Oracle Database Architecture(Page 277-283)
2. Oracle Statement-Level Rollbacks
3. SQL DML Exceptions, Rollbacks and PL/SQL Exception Handlers

Tuesday, November 1, 2016

"library cache: mutex X" and Application Context

Heavy Event: "library cache: mutex X" is observed when Application Context is frequently changed. The application is using Oracle Virtual Private Database to regulate data access with driving application context, which determines which policy group is in effect for each use case.

In this Blog, Application Context is used as a concrete case to discuss Oracle "library cache: mutex X".

Note: All tests are done in Oracle 12.1.0.2 on AIX, Solaris, Linux with 6 physical processors.


1. Test


Run the appended Test Code by launching 4 Jobs:
  exec ctx_set_jobs(4);

Monitor Job sessions:

select sid, program, event, p1text, p1, p2text, p2, p3text, p3
  from v$session where program like '%(J%';

  SID PROGRAM              EVENT                     P1TEXT          P1 P2TEXT             P2 P3TEXT                P3
----- -------------------- ------------------------- ------ ----------- ------ -------------- ------ -----------------
   38 oracle@testdb (J003) library cache: mutex X    idn     1317011825 value   3968549781504 where   9041305591414788
  890 oracle@testdb (J000) library cache: mutex X    idn     1317011825 value    163208757248 where   9041305591414874
  924 oracle@testdb (J001) library cache: mutex X    idn     1317011825 value   4556960301056 where   9041305591414874
 1061 oracle@testdb (J002) library cache: mutex X    idn     1317011825 value   3968549781504 where   9041305591414879

Pick idn (P1): 1317011825, and query v$db_object_cache:

select name, namespace, type, hash_value, locks, pins, locked_total, pinned_total
from v$db_object_cache where hash_value in (1317011825);

NAME       NAMESPACE       TYPE             HASH_VALUE       LOCKS        PINS LOCKED_TOTAL PINNED_TOTAL
---------- --------------- --------------- ----------- ----------- ----------- ------------ ------------
TEST_CTX   APP CONTEXT     APP CONTEXT      1317011825           4           0            4    257802287

It shows that "library cache: mutex X" is on application context: TEST_CTX, and PINNED_TOTAL is probably increased for each access.
Although TEST_CTX is a local context and its values is stored in the User Global Area (UGA), the content of "library cache: mutex X" is globally on its definition.

select namespace, package, type from dba_context where namespace = 'TEST_CTX';

NAMESPACE  PACKAGE       TYPE
---------- ------------  ----------------
TEST_CTX   TEST_CTX_PKG  ACCESSED LOCALLY

After test, clean-up all jobs by:
  exec clean_jobs;


2. Mutex Contention and Performance


Run the test, monitor Mutex Contention and Performance:

SQL > exec ctx_set_jobs(4);

column owner format a6
column name format a10
column property format a10
column namespace format a12
column type format a12

SQL > select owner, name, property, hash_value, locks, pins, locked_total, pinned_total, executions, sharable_mem, namespace, type, full_hash_value
      from  v$db_object_cache v
      where (name in ('TEST_CTX') or hash_value in (1317011825) or property like '%HOT%');

OWNER  NAME       PROPERTY   HASH_VALUE  LOCKS  PINS LOCKED_TOTAL PINNED_TOTAL EXECUTIONS SHARABLE_MEM NAMESPACE    TYPE         FULL_HASH_VALUE
------ ---------- ---------- ---------- ------ ----- ------------ ------------ ---------- ------------ ------------ ------------ --------------------------------
SYS    TEST_CTX              1317011825      4     0            4   1191106893 1191106886         4096 APP CONTEXT  APP CONTEXT  3581f5a97dfac7485a3330954e800171

SQL > select * from v$mutex_sleep order by sleeps desc, location;

MUTEX_TYPE        LOCATION                        SLEEPS  WAIT_TIME 
----------------- --------------------------- ---------- ---------- 
Library Cache     kglpndl1  95                    760307   76983943 
Library Cache     kglpin1   4                     288210  172310041 
Library Cache     kglpnal1  90                    193282   38493570 
Library Cache     kglGetHandleReference 123            1          0 

--display mutex REQUESTING/BLOCKING details for each session
SQL > select * from v$mutex_sleep_history order by sleep_timestamp desc, location;

MUTEX_IDENTIFIER SLEEP_TIMESTAMP      MUTEX_TYPE          GETS  SLEEPS REQUESTING_SESSION BLOCKING_SESSION LOCATION                    MUTEX_VALUE       P1  P1RAW           
---------------- -------------------- ------------- ---------- ------- ------------------ ---------------- -------------------------- ------------------ --- ----------------
      1317011825 24-OCT-2017 14:15:40 Library Cache  675726540  449377                  7                0 kglpin1   4                 00                 0  0000000176D1E860
      1317011825 24-OCT-2017 14:15:40 Library Cache  675726542  442641                368                0 kglpndl1  95                00                 0  0000000176D1E860
      1317011825 24-OCT-2017 14:15:40 Library Cache  675711683  444299                901                7 kglpndl1  95                0000000700000000   0  0000000176D1E860
      1317011825 24-OCT-2017 14:15:40 Library Cache  675709618  438207                187                0 kglpin1   4                 00                 0  0000000176D1E860
      1317011825 24-OCT-2017 14:09:06 Library Cache    2806872       1                900                0 kglGetHandleReference 123   00                 0  0000000176D1E860


Pick spid of one Oracle session, for example, 10684, get callstack:

$ > pstack 10684
10684:  ora_j000_testdb 
 fffffd7ffc9d3e3b semsys   (4, e000013, fffffd7fffdf5658, 1, fffffd7fffdf5660)
 0000000001ab9008 sskgpwwait () + f8
 0000000001ab8c95 skgpwwait () + c5
 0000000001c710d5 ksliwat () + 8f5
 0000000001c70410 kslwaitctx () + 90
 0000000001e6ffb0 kgxWait () + 520
 000000000dd1ae6f kgxExclusive () + 1cf
 00000000021cc025 kglGetMutex () + b5
 000000000212400e kglpin () + 2fe
 00000000026aa159 kglpnp () + 269
 00000000026a71ab kgiina () + 1db
 000000000dd118b9 kgintu_named_toplevel_unit () + 39
 0000000007ac16a6 kzctxBInfoGet () + 746
 0000000007ac38ed kzctxChkTyp () + fd
 0000000007ac43f0 kzctxesc () + 510
 0000000002781d9d pevm_icd_call_common () + 29d
 0000000002781930 pfrinstr_ICAL () + 90
 0000000001a435ca pfrrun_no_tool () + 12a
 0000000001a411e0 pfrrun () + 4c0
 0000000001a3fb48 plsql_run () + 288

Where semsys(4, ...) is semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timespec *timeout) specified in syscall.h.

Run a small dtrace script:

$ > sudo dtrace -n \
'BEGIN {self->start_wts = walltimestamp; self->start_ts = timestamp;}
pid$target::kglpndl:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::kgxExclusive:entry /execname == "oracle" && self->rc == 1/ { self->ts = timestamp; }
pid$target::kgxExclusive:return /self->ts > 0/ {  
 @lquant["ns"] = lquantize(timestamp - self->ts, 0, 10000, 1000); 
 @avgs["AVG_ns"] = avg(timestamp - self->ts);
 @mins["MIN_ns"] = min(timestamp - self->ts);
 @maxs["MAX_ns"] = max(timestamp - self->ts);
 @sums["SUM_ms"] = sum((timestamp - self->ts)/1000000);
 @counts[ustack(10, 0)] = count(); 
 self->rc = 0; self->ts = 0;}
END { printf("Start: %Y, End: %Y, Elapsed_ms: %d\n", self->start_wts, walltimestamp, (timestamp - self->start_ts)/1000000);}
' -p 10684

dtrace: description 'BEGIN ' matched 8 probes
Start: 2017 Oct 24 14:30:02, End: 2017 Oct 24 14:31:08, Elapsed_ms: 66183

  ns
     value  ------------- Distribution ------------- count
       < 0 |                                         0
         0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@         3352394
      1000 |@@@@@@@@                                 803168
      2000 |                                         11598
      3000 |                                         1484
      4000 |                                         890
      5000 |                                         626
      6000 |                                         460
      7000 |                                         315
      8000 |                                         265
      9000 |                                         147
  >= 10000 |                                         2227

  AVG_ns                                                         1999
  MIN_ns                                                          777
  MAX_ns                                                     20411473
  SUM_ms                                                         4214

      oracle`kgxExclusive+0x105
      oracle`kglpndl+0x1fe
      oracle`kglUnPin+0x101
      a.out`kzctxChkTyp+0x14e
      a.out`kzctxesc+0x510
      a.out`pevm_icd_call_common+0x29d
      a.out`pfrinstr_ICAL+0x90
      oracle`pfrrun_no_tool+0x12a
      oracle`pfrrun+0x4c0
      oracle`plsql_run+0x288
  4173574
The above CallStack shows that kgxExclusive is triggered by kglpin via kglGetMutex.

Solaris "prstat -mL" show about 30% percentage of time the process has spent sleeping (SLP).


3. Hot library cache objects


Learning from Blog: Divide and conquer the true mutex contention
 
    KGLPIN:   KGL: PIN heaps and load data pieces of an object 
    KGLPNDL:  KGL PiN DeLete
    KGLPNAL1: KGL PiN ALlOcate
    
    KGLHBH1 63, KGLHDGN2 106: Invalid Password, Application Context(eg: SYS_CONTEXT)
"library cache: mutex X" can be allevaited by creating multiple copies of hot objects, which are controlled by two hidden parameters:
    _kgl_hot_object_copies: controls the maximum number of copies
    _kgl_debug:             marks hot library cache objects as a candidate for cloning
Configure two hidden parameters:

SQL > alter system set "_kgl_hot_object_copies"= 255 scope=spfile;

      --alter system reset "_kgl_hot_object_copies" scope=spfile;

SQL > alter system set "_kgl_debug"=
      "name='TEST_CTX' schema='SYS'    namespace=21 debug=33554432", 
      "name='PLITBLM'  schema='PUBLIC' namespace=1  debug=33554432"
      scope=spfile;
      
      --alter system reset "_kgl_debug" scope=spfile;

--- added in 25-Apr-2021 ---
  MOS Bug 19373224 - dbms_shared_pool.unmarkhot spins if _kgl_hot_object_copies is 255 (Doc ID 19373224.8) wrote
    Executing dbms_shared_pool.unmarkhot() might have entered a spin under kglget()  if 
     _kgl_hot_object_copies was set (or derived) to a value of 255. 
    Workaround
      Reduce the value of _kgl_hot_object_copies to 254 or below.
PUBLIC SYNONYM (namespace=1) 'PLITBLM' is added here to show multiple library cache objects can be specified in _kgl_debug. PLITBLM is package for PLSQL Index TaBLe Mangement, i.e PLSQL Collections (Associative Arrays, Nested Table, Varrays). All its implementations are through c interface.

library cache object NAMESPACE number, NAMESPACE name and TYPE name can be listed by following queries ("_kgl_debug" and dbms_shared_pool.markhot accept number as NAMESPACE):

SQL > select distinct namespace, object_type from dba_objects v order by 1;

SQL > select distinct namespace, type# from sys.obj$ order by 1;

SQL > select distinct kglhdnsp NAMESPACE_id, kglhdnsd NAMESPACE_name from x$kglob 
      --where kglhdnsd in ('APP CONTEXT')
      order by kglhdnsp;

SQL > select distinct kglobtyp TYPE_id, kglobtyd TYPE_name from x$kglob 
      --where kglobtyd in ('APP CONTEXT')
      order by kglobtyp;
Run the same test:

-- Stop all Jobs
SQL > exec clean_jobs;

--Restart DB to activate Hot library cache objects
SQL> startup force

SQL > select owner, name, property, hash_value, locks, pins, locked_total, pinned_total, executions, sharable_mem, namespace, type, full_hash_value
      from  v$db_object_cache v
      where (name in ('TEST_CTX') or hash_value in (1317011825) or property like '%HOT%');
      
OWNER  NAME       PROPERTY   HASH_VALUE LOCKS PINS LOCKED_TOTAL PINNED_TOTAL EXECUTIONS SHARABLE_MEM NAMESPACE    TYPE     FULL_HASH_VALUE
------ ---------- ---------- ---------- ----- ---- ------------ ------------ ---------- ------------ ------------ -------- -----------------------------
SYS    TEST_CTX   HOT        1317011825     0    0            1            0          0         0 APP CONTEXT  CURSOR   3581f5a97dfac7485a3330954e800171       
 
SQL > exec ctx_set_jobs(4); 
 
SQL > select sid, program, event, p1text, p1, p2text, p2, p3text, p3
      from v$session where program like '%(J%'; 

 SID PROGRAM                  EVENT                     P1TEXT          P1 P2TEXT             P2 P3TEXT                P3
---- ------------------------ ------------------------- ------ ----------- ------ -------------- ------ -----------------
   5 oracle@s5d00003 (J001)   null event                                 0                     0                        0
 186 oracle@s5d00003 (J004)   null event                                 0                     0                        0
 369 oracle@s5d00003 (J005)   null event                                 0                     0                        0
 902 oracle@s5d00003 (J000)   null event                                 0                     0                        0
 
SQL > select owner, name, property, hash_value, locks, pins, locked_total, pinned_total, executions, sharable_mem, namespace, type, full_hash_value
      from  v$db_object_cache v
      where (name in ('TEST_CTX') or hash_value in (1317011825) or property like '%HOT%'); 
 
OWNER  NAME       PROPERTY   HASH_VALUE LOCKS PINS LOCKED_TOTAL PINNED_TOTAL EXECUTIONS SHARABLE_MEM NAMESPACE    TYPE         FULL_HASH_VALUE
------ ---------- ---------- ---------- ----- ---- ------------ ------------ ---------- ------------ ------------ ------------ --------------------------------
SYS    TEST_CTX   HOT        1317011825     0    0            1            0          0         0    APP CONTEXT  CURSOR       3581f5a97dfac7485a3330954e800171
SYS    TEST_CTX   HOTCOPY6   1487681198     1    0            2    151394920  151394917      4096    APP CONTEXT  APP CONTEXT  1047cbfbca3fd100cf5758a258ac36ae
SYS    TEST_CTX   HOTCOPY138 3082567164     1    0            2    151821083  151821080      4096    APP CONTEXT  APP CONTEXT  31ac20c09044dfec01acd307b7bc3dfc
SYS    TEST_CTX   HOTCOPY187 3192676979     1    0            2    151252013  151252010      4096    APP CONTEXT  APP CONTEXT  6635075a7adcc68672a02262be4c6273
SYS    TEST_CTX   HOTCOPY115 4198626891     1    0            2    150529629  150529626      4096    APP CONTEXT  APP CONTEXT  d6724a1c9f480d55f73dc8fcfa41f64b 

SQL > select * from v$mutex_sleep order by sleeps desc, location;

MUTEX_TYPE                       LOCATION                                     SLEEPS  WAIT_TIME
-------------------------------- ---------------------------------------- ---------- ----------
Cursor Pin                       kkslce [KKSCHLPIN2]                               2      20118

SQL > select * from v$mutex_sleep_history order by sleep_timestamp desc, location;

MUTEX_IDENTIFIER SLEEP_TIMESTAMP      MUTEX_TYPE GETS SLEEPS REQUESTING_SESSION BLOCKING_SESSION LOCATION            MUTEX_VALUE      P1 P1RAW
---------------- -------------------- ---------- ---- ------ ------------------ ---------------- ------------------- ---------------- -- -----
      2816823972 24-OCT-2017 15:09:13 Cursor Pin    1      1                183              364 kkslce [KKSCHLPIN2] 0000016C00000000  2 00
      2214650983 24-OCT-2017 15:04:50 Cursor Pin    1      1                  5              902 kkslce [KKSCHLPIN2] 0000038600000000  2 00


Run the same dtrace script:

$ > sudo dtrace -n \
'BEGIN {self->start_wts = walltimestamp; self->start_ts = timestamp;}
pid$target::kglpndl:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::kgxExclusive:entry /execname == "oracle" && self->rc == 1/ { self->ts = timestamp; }
pid$target::kgxExclusive:return /self->ts > 0/ {  
 @lquant["ns"] = lquantize(timestamp - self->ts, 0, 10000, 1000); 
 @avgs["AVG_ns"] = avg(timestamp - self->ts);
 @mins["MIN_ns"] = min(timestamp - self->ts);
 @maxs["MAX_ns"] = max(timestamp - self->ts);
 @sums["SUM_ms"] = sum((timestamp - self->ts)/1000000);
 @counts[ustack(10, 0)] = count(); 
 self->rc = 0; self->ts = 0;}
END { printf("Start: %Y, End: %Y, Elapsed_ms: %d\n", self->start_wts, walltimestamp, (timestamp - self->start_ts)/1000000);}
' -p 11751

Start: 2017 Oct 24 15:21:02, End: 2017 Oct 24 15:22:40, Elapsed_ms: 97999
  ns
     value  ------------- Distribution ------------- count
       < 0 |                                         0
         0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   8050589
      1000 |@@                                       330902
      2000 |                                         1106
      3000 |                                         1606
      4000 |                                         1352
      5000 |                                         630
      6000 |                                         322
      7000 |                                         201
      8000 |                                         133
      9000 |                                         94
  >= 10000 |                                         481

  AVG_ns         897
  MIN_ns         813
  MAX_ns      315083
  SUM_ms           0

      oracle`kgxExclusive+0x105
      oracle`kglpndl+0x1fe
      oracle`kglUnPin+0x101
      a.out`kzctxChkTyp+0x14e
      a.out`kzctxesc+0x510
      a.out`pevm_icd_call_common+0x29d
      a.out`pfrinstr_ICAL+0x90
      oracle`pfrrun_no_tool+0x12a
      oracle`pfrrun+0x4c0
      oracle`plsql_run+0x288
  8387416
Solaris "prstat -mL" show almost 100% percentage of time the process has spent in user mode (USR).

Try with official API in dbms_shared_pool, it seems that NAMESPACE: 'APP CONTEXT' not yet supported.

-- Stop all Jobs
SQL > exec clean_jobs;

SQL > alter system reset "_kgl_debug" scope=spfile;

--Restart DB
SQL> startup force

SQL > exec sys.dbms_shared_pool.markhot('SYS', 'TEST_CTX', 21);
      --exec sys.dbms_shared_pool.unmarkhot('SYS', 'TEST_CTX', 21);

 ORA-26680: object type not supported
 ORA-06512: at "SYS.DBMS_SHARED_POOL", line 133
 
-- Using 32 Byte (16 hexadecimal) V$DB_OBJECT_CACHE.FULL_HASH_VALUE
SQL > exec sys.dbms_shared_pool.markhot(hash=>'3581f5a97dfac7485a3330954e800171', NAMESPACE=>21);
      --exec sys.dbms_shared_pool.unmarkhot(hash=>'3581f5a97dfac7485a3330954e800171', NAMESPACE=>21);

 ORA-26680: object type not supported
 ORA-06512: at "SYS.DBMS_SHARED_POOL", line 138
Comparing "_kgl_debug" and markhot, it seems that "_kgl_debug" is persistent after DB restart, but not always stable after DB restart. Several sessions can still contend for the same library cache objects without creating/using HOT objects.

Whereas markhot seems stable after DB restart, but not always persistent after DB restart. Moreover, markhot does not support all NAMESPACEs of library cache objects.

When a SYNONYM is marked HOT, it can encounter core dump with Error:
    ORA-00600: internal error code, arguments: [kgltti-no-dep1]
with CallStack:
 
  kgltti()+1358            -> dbgeEndDDEInvocation() // ERROR SIGNALED: yes COMPONENT: LIBCACHE
  kqlCompileSynonym()+3840 -> kgltti() 
  kqllod_new()+3768        -> kqlCompileSynonym() 
  kqlCallback()+79         -> kqllod_new() 
  kqllod()+710             -> kqlCallback() 
  kglobld()+1058           -> kqllod() 
  kglobpn()+1232           -> kglobld() 
  kglpim()+489             -> kglobpn() 
  kglpin()+1785            -> kglpim() 
  kglgob()+493             -> kglpin() 
  kgiind()+1529            -> kglgob() 
  pfri8_inst_spec()+126    -> kgiind() 
  pfri1_inst_spec()+69     -> pfri8_inst_spec() 
  pfrrun()+1506            -> pfri1_inst_spec() 
  plsql_run()+648          -> pfrrun() 
This error is addressed by Oracle MOS:
    ORA-00600 [kgltti-no-dep1] When Synonym Marked Hot (Doc ID 2153847.1)


4. V$MUTEX_SLEEP_HISTORY


V$MUTEX_SLEEP_HISTORY displays time-series data. Each row in this view is for a specific time, mutex type, location, requesting session and blocking session combination. The data in this view is contained within a circular buffer, with the most recent sleeps shown (Oracle V$MUTEX_SLEEP_HISTORY).

Two fields are documented as:
  GETS    The total number of gets since the mutex was created and up until the time of the wait 
          (and from all sessions past and present)
  SLEEPS  The number of times the requester had to sleep before obtaining the mutex
It seems that GETS is an instance-wide accumulated historized data, whereas SLEEPS is per REQUESTING_SESSION for a specific time, mutex type, location.

We can try to edit a query to reveal Mutex contention details. Column SLEEPS and GETs_Per_MS are the points to monitor.

with mutex_1 as (select /*+ materializse */ m.*
                ,(gets -lag(gets) over(partition by mutex_identifier, mutex_type, location order by sleep_timestamp, requesting_session)) Total_GETs_Delta
                ,(((sleep_timestamp -lag(sleep_timestamp) over(partition by mutex_identifier, mutex_type, location 
                    order by sleep_timestamp, requesting_session)))) Elapsed
               from v$mutex_sleep_history m) 
    ,mutex_2 as (select /*+ materializse */ m.*
                ,(extract(second from Elapsed) + extract(minute from Elapsed) * 60 + extract(hour from Elapsed))*1e3 Elapsed_MS
               from mutex_1 m)
    ,mutex   as (select /*+ materializse */ m.*
                ,round(Total_GETs_Delta/nullif(Elapsed_MS, 0)) GETs_Per_MS
               from mutex_2 m)
    ,oc as (select /*+ materializse */ * from v$db_object_cache where hash_value in (select mutex_identifier from mutex)) 
    ,ses as (select /*+ materializse */ s.*
              ,(select owner|| '.' ||object_name||case when procedure_name is not null then '.' ||procedure_name end
                from dba_procedures
                where object_id = s.plsql_entry_object_id and subprogram_id = s.plsql_entry_subprogram_id) plsql_entry
              ,(select owner||'.'||object_name||case when procedure_name is not null then  '.' || procedure_name end
                from dba_procedures
                where object_id = s.plsql_object_id and subprogram_id = s.plsql_subprogram_id) plsql
              ,(select dbms_lob.substr(sql_text, 50, 1) from dba_hist_sqltext where sql_id = s.sql_id and rownum = 1) sql_text
             from v$active_session_history s where sample_time <= sysdate-10/1440)   -- only last 10 minutes
select m.*, o.*, s.*
from  mutex m, oc o, ses s;
where m.mutex_identifier = o.hash_value(+)
  and m.requesting_session = s.session_id(+)
  and m.sleep_timestamp between (s.sample_time(+) - INTERVAL'1'SECOND) and s.sample_time(+)
order by m.mutex_identifier, m.mutex_type, m.location, m.gets desc, m.sleep_timestamp desc, m.requesting_session;


5. Code Path of "library cache: mutex X"


Run Application Context settings 1000 times and at the same time dtrace its SPID: 1217.

It prints out 29 "kgxExclusive" callstacks (Top 3 callstacks are listed at first). Editing them together, we can build up a small dictionary of "library cache: mutex X" to lookup different occurrences and frequencies of mutex X.

The test (on a fresh started instance) shows that 'TEST_CTX' was pinned 1000 times for 1000 executions.

1000 Application Context settings are implemented by 1000:
    PinAndPrepare:  kglpnp   -> kglpin
    PinAndAllocate: kglpin   -> kglpnal 
    PinAndDelete:   kglUnPin -> kglpndl

SQL > select name, locks, pins, locked_total, pinned_total, executions
      from v$db_object_cache where name in ('TEST_CTX') or hash_value in (1317011825);
      
      NAME             LOCKS        PINS LOCKED_TOTAL PINNED_TOTAL  EXECUTIONS
      ---------- ----------- ----------- ------------ ------------ -----------
      TEST_CTX             1           0            1         1000         999      

SQL > exec ctx_set(1000, 123);

SQL > select name, locks, pins, locked_total, pinned_total, executions
      from v$db_object_cache where name in ('TEST_CTX') or hash_value in (1317011825);

      NAME             LOCKS        PINS LOCKED_TOTAL PINNED_TOTAL  EXECUTIONS
      ---------- ----------- ----------- ------------ ------------ -----------
      TEST_CTX             1           0            1         2000        1999 

$ > sudo dtrace -n \
'pid$target::kgxExclusive:entry /execname == "oracle"/ { self->in = 1; }
pid$target::kgxExclusive:return /self->in > 0/ {
@counts[ustack(10, 0)] = count(); 
  self->in = 0;}
' -p 1217

dtrace: description 'pid$target::kgxExclusive:entry ' matched 4 probes

oracle`kgxExclusive+0x105               oracle`kgxExclusive+0x105          oracle`kgxExclusive+0x105              
a.out`kglpin+0x2fe                      a.out`kglpndl+0x1fe                a.out`kglpnal+0x1ae                    
a.out`kglpnp+0x269                      a.out`kglUnPin+0x101               a.out`kglpin+0x52f                     
a.out`kgiina+0x1db                      a.out`kzctxChkTyp+0x14e            a.out`kglpnp+0x269                     
oracle`kgintu_named_toplevel_unit+0x39  a.out`kzctxesc+0x510               a.out`kgiina+0x1db                     
a.out`kzctxBInfoGet+0x746               a.out`pevm_icd_call_common+0x29d   oracle`kgintu_named_toplevel_unit+0x39 
a.out`kzctxChkTyp+0xfd                  a.out`pfrinstr_ICAL+0x90           a.out`kzctxBInfoGet+0x746              
a.out`kzctxesc+0x510                    oracle`pfrrun_no_tool+0x12a        a.out`kzctxChkTyp+0xfd                 
a.out`pevm_icd_call_common+0x29d        oracle`pfrrun+0x4c0                a.out`kzctxesc+0x510                   
a.out`pfrinstr_ICAL+0x90                oracle`plsql_run+0x288             a.out`pevm_icd_call_common+0x29d       
  1000                                    1000                               1000                                 

oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105              
a.out`kglpin+0x2fe                     a.out`kgldtin+0x85d                 a.out`kglhdgn+0xc2                     
a.out`kglgob+0x1ed                     a.out`kgldti+0x63                   a.out`kglLock+0x679                    
a.out`kgldpo0+0x4d8                    a.out`kksauc+0x3b7                  a.out`kglget+0x11b                     
a.out`kglgbo+0x95                      a.out`kkscscid_auc_eval+0x176       a.out`kkspsc0+0x744                    
a.out`kksauc+0x888                     a.out`kkscsCheckCriteria+0x691      a.out`kksParseCursor+0x74              
a.out`kkscscid_auc_eval+0x176          a.out`kkscsCheckCursor+0x30a        a.out`opiosq0+0x70a                    
a.out`kkscsCheckCriteria+0x691         a.out`kkscsSearchChildList+0x356    a.out`kpooprx+0x102                    
a.out`kkscsCheckCursor+0x30a           a.out`kksfbc+0x916                  a.out`kpoal8+0x308                     
a.out`kkscsSearchChildList+0x356       a.out`kkspsc0+0x9d7                 oracle`opiodr+0x433                    
  1                                      1                                   1                                    
                                                                                                                  
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105           
a.out`kglLock+0x93d                    a.out`kglpin+0x2fe                  a.out`kglpndl+0x1fe                 
a.out`kglget+0x11b                     a.out`kglgob+0x1ed                  a.out`kglUnPin+0x101                
a.out`kglgob+0x13b                     a.out`kkdogty+0x3fd                 a.out`kksauc+0x3d0                  
a.out`kkdogty+0x3fd                    a.out`kkdot2t+0x36                  a.out`kkscscid_auc_eval+0x176       
a.out`kkdot2t+0x36                     a.out`opibnd0+0xbe0                 a.out`kkscsCheckCriteria+0x691      
a.out`psdgbaa+0x791                    a.out`opibnd+0x175                  a.out`kkscsCheckCursor+0x30a        
a.out`pevm_GBVAR+0xdc                  a.out`kpopbnd+0xc7                  a.out`kkscsSearchChildList+0x356    
a.out`pfrinstr_GBVAR+0x37              a.out`kpoal8+0x1431                 a.out`kksfbc+0x916                  
oracle`pfrrun_no_tool+0x12a            oracle`opiodr+0x433                 a.out`kkspsc0+0x9d7                 
  1                                      1                                   1                                 
                                                                                                                  
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105              
a.out`kglLock+0x93d                    a.out`kglati+0x6a                   a.out`kglLockCursor+0xf6               
a.out`kglget+0x11b                     a.out`kksaxs+0x76b                  a.out`kxsGetLookupLock+0x70            
a.out`kglgob+0x13b                     a.out`kksauc+0x1cc                  a.out`kkscsCheckCursor+0x198           
a.out`kgldpo0+0x4d8                    a.out`kkscscid_auc_eval+0x176       a.out`kkscsSearchChildList+0x356       
a.out`kglgbo+0x95                      a.out`kkscsCheckCriteria+0x691      a.out`kksfbc+0x916                     
a.out`kksauc+0x888                     a.out`kkscsCheckCursor+0x30a        a.out`kkspsc0+0x9d7                    
a.out`kkscscid_auc_eval+0x176          a.out`kkscsSearchChildList+0x356    a.out`kksParseCursor+0x74              
a.out`kkscsCheckCriteria+0x691         a.out`kksfbc+0x916                  a.out`opiosq0+0x70a                    
a.out`kkscsCheckCursor+0x30a           a.out`kkspsc0+0x9d7                 a.out`kpooprx+0x102                    
  1                                      1                                   1                                    
    
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105       
a.out`kglhdgn+0xc2                     a.out`kglLock+0x93d                 a.out`kgllkdl+0x196             
a.out`kglLock+0x679                    a.out`kglget+0x11b                  a.out`kglUnLock+0xf8            
a.out`kglget+0x11b                     a.out`kglgob+0x13b                  a.out`kxsUnlock+0x1b1           
a.out`kglgob+0x13b                     a.out`kkdogty+0x3fd                 a.out`kxsFreeXsc+0x16e          
a.out`kkdogty+0x3fd                    a.out`kkdot2t+0x36                  a.out`kksCloseCursor+0x60a      
a.out`kkdot2t+0x36                     oracle`psdsbaa+0x244                a.out`opicca+0x81               
a.out`opibnd0+0xbe0                    a.out`pevm_SBVAR+0x123              a.out`opiclo+0x96               
a.out`opibnd+0x175                     a.out`pfrinstr_SBVAR+0x3a           a.out`kpoclsa+0x44              
a.out`kpopbnd+0xc7                     oracle`pfrrun_no_tool+0x12a         oracle`opiodr+0x433             
  1                                      1                                   1                             
    
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105          
a.out`kgllkdl+0x196                    a.out`kglpnal+0x1ae                 a.out`kglLock+0x2aa               
a.out`kglUnLock+0xf8                   a.out`kglpin+0x52f                  a.out`kglget+0x11b                
a.out`kksauc+0x3e0                     a.out`kglgob+0x1ed                  a.out`kksaxs+0x52d                
a.out`kkscscid_auc_eval+0x176          a.out`kgldpo0+0x4d8                 a.out`kksauc+0x1cc                
a.out`kkscsCheckCriteria+0x691         a.out`kglgbo+0x95                   a.out`kkscscid_auc_eval+0x176     
a.out`kkscsCheckCursor+0x30a           a.out`kksauc+0x888                  a.out`kkscsCheckCriteria+0x691    
a.out`kkscsSearchChildList+0x356       a.out`kkscscid_auc_eval+0x176       a.out`kkscsCheckCursor+0x30a      
a.out`kksfbc+0x916                     a.out`kkscsCheckCriteria+0x691      a.out`kkscsSearchChildList+0x356  
a.out`kkspsc0+0x9d7                    a.out`kkscsCheckCursor+0x30a        a.out`kksfbc+0x916                
  1                                      1                                   1                               
                                                                                                                  
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105           
a.out`kglpin+0x2fe                     a.out`kgllkdl+0x196                 a.out`kglpnal+0x1ae                 
a.out`kglpnp+0x269                     a.out`kglUnLock+0xf8                a.out`kglpin+0x52f                  
a.out`kgiinb+0x5e0                     a.out`kxsReleaseParentLock+0xb6     a.out`kglgob+0x1ed                  
a.out`pfri7_inst_body_common+0x18d     a.out`kxsFreeXsc+0x181              a.out`kkdogty+0x3fd                 
a.out`pfri3_inst_body+0x45             a.out`kksCloseCursor+0x60a          a.out`kkdot2t+0x36                  
oracle`pfrrun+0x685                    a.out`opicca+0x81                   a.out`opibnd0+0xbe0                 
oracle`plsql_run+0x288                 a.out`opiclo+0x96                   a.out`opibnd+0x175                  
oracle`peicnt+0x946                    a.out`kpoclsa+0x44                  a.out`kpopbnd+0xc7                  
oracle`kkxexe+0x2f3                    oracle`opiodr+0x433                 a.out`kpoal8+0x1431                 
  3                                      1                                   1                                 
                                                                                                                  
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105              
a.out`kglhdgn+0x23e                    a.out`kglpnal+0x1ae                 a.out`kgllkdl+0x196                    
a.out`kglLock+0x679                    a.out`kglpin+0x52f                  a.out`kss_del_cb+0x19d                 
a.out`kglget+0x11b                     a.out`kglpnp+0x269                  a.out`kssdel+0xf2                      
a.out`kglgob+0x13b                     a.out`kgiinb+0x5e0                  a.out`kssdch_int+0x326                 
a.out`kkdogty+0x3fd                    a.out`pfri7_inst_body_common+0x18d  a.out`ksudlc+0xf6                      
a.out`kkdot2t+0x36                     a.out`pfri3_inst_body+0x45          a.out`kss_del_cb+0x106                 
a.out`opibnd0+0xbe0                    oracle`pfrrun+0x685                 a.out`kssdel+0xf2                      
a.out`opibnd+0x175                     oracle`plsql_run+0x288              a.out`ksupop+0x23a                     
a.out`kpopbnd+0xc7                     oracle`peicnt+0x946                 oracle`opiodr+0x4b1                    
  1                                      3                                   1                                    

oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105           oracle`kgxExclusive+0x105        
a.out`kglnti+0x83                      a.out`kglhdgn+0x23e                 a.out`kglpin+0x2fe               
a.out`kksauc+0x7c0                     a.out`kglLock+0x679                 a.out`kglpnp+0x269               
a.out`kkscscid_auc_eval+0x176          a.out`kglget+0x11b                  a.out`kgiind+0xbae               
a.out`kkscsCheckCriteria+0x691         a.out`kkspsc0+0x744                 a.out`pfri8_inst_spec+0x7e       
a.out`kkscsCheckCursor+0x30a           a.out`kksParseCursor+0x74           a.out`pfri1_inst_spec+0x45       
a.out`kkscsSearchChildList+0x356       a.out`opiosq0+0x70a                 oracle`pfrrun+0x5e2              
a.out`kksfbc+0x916                     a.out`kpooprx+0x102                 oracle`plsql_run+0x288           
a.out`kkspsc0+0x9d7                    a.out`kpoal8+0x308                  oracle`peicnt+0x946              
a.out`kksParseCursor+0x74              oracle`opiodr+0x433                 oracle`kkxexe+0x2f3              
  1                                      1                                   4                              
    
oracle`kgxExclusive+0x105              oracle`kgxExclusive+0x105   
a.out`kglpnal+0x1ae                    a.out`kglpndl+0x1fe         
a.out`kglpin+0x52f                     a.out`kss_del_cb+0x19d      
a.out`kglpnp+0x269                     a.out`kssdel+0xf2           
a.out`kgiind+0xbae                     a.out`kssdch_int+0x326      
a.out`pfri8_inst_spec+0x7e             a.out`ksudlc+0xf6           
a.out`pfri1_inst_spec+0x45             a.out`kss_del_cb+0x106      
oracle`pfrrun+0x5e2                    a.out`kssdel+0xf2           
oracle`plsql_run+0x288                 a.out`ksupop+0x23a          
oracle`peicnt+0x946                    oracle`opiodr+0x4b1         
  4                                      8  


6. Mutex vs. Latch


Latch is an instance-wide centralized locking mechanism, whereas Mutex is a distributed locking mechanism, directly attached on the specific shared memory data structures. That is why there exists v$latch (v$latch_children) for all Latches, whereas Mutex is exposed as V$DB_OBJECT_CACHE.hash_value. Latch is pre-defined and limited, whereas Mutex is dynamically created/released when requested.

Blog: Reducing "library cache: mutex X" concurrency with dbms_shared_pool.markhot lists top 3 differences between mutexes and latches:
  – A mutex can protect a single structure, latches often protect many structures
  – A mutex get is about 30-35 instructions in the algorithm, compared to 150-200 instructions for a latch get
  – A mutex is around 16 bytes in size, compared to 112-200 bytes for a latch
Mutex looks about 5 times slimmer and hence hopefully proportionally faster than Latch.

Further deep discussion can be found in Blog: LATCHES, LOCKS, PINS AND MUTEXES


7. Mutex Contention Test on Linux


Run the test on Linux, the same "library cache: mutex X" contentions can be observed.

Pick one Oracle session's spid, for example, 16060.
Run Command strace, We can see continues output of lines beginning with semtimedop:

$ > strace -Tp 16060

    semtimedop(20873218, {{41, -1, 0}}, 1, {0, 10000000}) = -1 EAGAIN (Resource temporarily unavailable) <0.011305>
Refer to Linux Documentations:

int semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timespec *timeout);

struct sembuf {
  unsigned short  sem_num; /* semaphore index in array, semaphore number */
  short          sem_op;  /* semaphore operation */
  short          sem_flg; /* operation flags */
};

struct timespec {
  time_t          tv_sec;
  long            tv_nsec;
};
                 
EAGAIN: An operation could not proceed immediately and 
        either IPC_NOWAIT was specified in sem_flg or the time limit specified in timeout expired.             
timespec.tv_nsec = 10000000 ns (0.011305 Second) looks like CFS Scheduler Time Slice as 10ms.

We can try to simulate Oracle ORA-07445 by:

kill -s SEGV 16060         
We get database alert log and incident file of killed session:
    
-- alert log    
Thu Oct 26 09:04:08 2017
Exception [type: SIGSEGV, unknown code] [ADDR:0x6400003F6A] [PC:0x7F98B7BA728A, semtimedop()+10] [exception issued by pid: 16234, uid: 100] [flags: 0x0, count: 1]
Errors in file /orabin/app/oracle/admin/c0d00445/diag/rdbms/c0d00445/c0d00445/trace/c0d00445_j001_16060.trc  (incident=22697):
ORA-07445: exception encountered: core dump [semtimedop()+10] [SIGSEGV] [ADDR:0x6400003F6A] [PC:0x7F98B7BA728A] [unknown code] []
Incident details in: /orabin/app/oracle/admin/c0d00445/diag/rdbms/c0d00445/c0d00445/incident/incdir_22697/c0d00445_j001_16060_i22697.trc

-- incident file 
Exception [type: SIGSEGV, unknown code] [ADDR:0x6400003F6A] [PC:0x7F98B7BA728A, semtimedop()+10] 
          [exception issued by pid: 16234, uid: 100] [flags: 0x0, count: 1]
Registers:
%rax: 0xfffffffffffffffc %rbx: 0x0000000000000000 %rcx: 0xffffffffffffffff
%rdx: 0x0000000000000001 %rdi: 0x00000000013e8002 %rsi: 0x00007ffc98536278
%rsp: 0x00007ffc985360a8 %rbp: 0x00007ffc985362a0  %r8: 0x0000000000002710
 %r9: 0x0000000000989680 %r10: 0x00007ffc98536228 %r11: 0x0000000000000202
%r12: 0x00007ffc985364d0 %r13: 0x00000000013e8002 %r14: 0x00000001708d3998
%r15: 0x0000000000000000 %rip: 0x00007f98b7ba728a %efl: 0x0000000000000202
  semtimedop()+0 (0x7f98b7ba7280) mov %rcx,%r10
  semtimedop()+3 (0x7f98b7ba7283) mov $0xdc,%eax
  semtimedop()+8 (0x7f98b7ba7288) syscall
> semtimedop()+10 (0x7f98b7ba728a) cmp $0xfffff001,%rax
  semtimedop()+16 (0x7f98b7ba7290) jae 0x7f98b7ba7293
  semtimedop()+18 (0x7f98b7ba7292) ret
  semtimedop()+19 (0x7f98b7ba7293) mov 0x2a2d0e(%rip),%rcx
  semtimedop()+26 (0x7f98b7ba729a) xor %edx,%edx
 
   sslsshandler()+456         -> ssexhd()        // CALL TYPE: call   ERROR SIGNALED: yes   COMPONENT: (null)
   __sighandler()             -> sslsshandler() 
   semtimedop()+10            -> __sighandler() 
   sskgpwwait()+237           -> semtimedop() 
   skgpwwait()+200            -> sskgpwwait() 
   ksliwat()+2091             -> skgpwwait() 
   kslwaitctx()+161           -> ksliwat() 
   ksfwaitctx()+32            -> kslwaitctx() 
   kgxWait()+1076             -> ksfwaitctx() 
   kgxExclusive()+741         -> kgxWait() 
   kglGetMutex()+170          -> kgxExclusive() 
   kglpndl()+503              -> kglGetMutex() 
   kglpnds()+81               -> kglpndl() 
   kglUnPin()+228             -> kglpnds() 
   kzctxChkTyp()+306          -> kglUnPin() 
   kzctxesc()+347             -> kzctxChkTyp() 
   pevm_icd_call_common()+411 -> kzctxesc() 
   pfrinstr_ICAL()+135        -> pevm_icd_call_common() 
   pfrrun_no_tool()+60        -> pfrinstr_ICAL() 
   pfrrun()+1155              -> pfrrun_no_tool() 
   plsql_run()+708            -> pfrrun() 


8. No Read Consistency in Application Context (Addendum 2017.06.06)


Modify above local context as global context, and start a job to update context value by:

exec clean_jobs;

create or replace context test_ctx using test_ctx_pkg accessed globally;

create or replace procedure ctx_set_global(p_cnt number) as
begin
 for i in 1..p_cnt loop
   test_ctx_pkg.set_val(i);    
 end loop;
end;
/

create or replace procedure ctx_set_jobs_global as
  l_job_id pls_integer;
begin
  dbms_job.submit(l_job_id, 'begin while true loop ctx_set_global(1000000); end loop; end;');
  commit;
end;    
/

exec ctx_set_jobs_global;

And then query systimestamp and global context by:

alter session set nls_timestamp_tz_format ='yyyy-mon-dd hh24:mi:ss.ff tzr tzd';
column name format a8
column min_time format a35
column max_time format a35
column min_ctx format a10
column max_ctx format a10

-- grace period for job start
exec dbms_lock.sleep(10);   

with sq_time_1 as (select systimestamp tm from dual connect by level <= 10000),
     sq_ctx_1  as (select sys_context('test_ctx', 'attr') ctx from dual connect by level <= 100),
     sq_time_2 as (select systimestamp tm from dual connect by level <= 10000),
     sq_ctx_2  as (select sys_context('test_ctx', 'attr') ctx from dual connect by level <= 100)
select 'Snap_1' name, min(tm) min_time, max(tm) max_time, min(ctx) min_ctx, max(ctx) max_ctx from sq_time_1, sq_ctx_1
union all
select 'Snap_2' name, min(tm) min_time, max(tm) max_time, min(ctx) min_ctx, max(ctx) max_ctx from sq_time_2, sq_ctx_2;


NAME     MIN_TIME                            MAX_TIME                            MIN_CTX    MAX_CTX
-------- ----------------------------------- ----------------------------------- ---------- ----------
Snap_1   2017-jun-06 08:05:09.625790 +02:00  2017-jun-06 08:05:09.625790 +02:00  263007     263007
Snap_2   2017-jun-06 08:05:09.625790 +02:00  2017-jun-06 08:05:09.625790 +02:00  362922     362922

exec dbms_lock.sleep(4);

with sq_ctx_1  as (select sys_context('test_ctx', 'attr') ctx from dual connect by level <= 100),
     sq_time_1 as (select systimestamp tm from dual connect by level <= 10000)
select 'Snap_1' name, min(tm) min_time, max(tm) max_time, min(ctx) min_ctx, max(ctx) max_ctx from sq_time_1, sq_ctx_1
union all
select 'Snap_2' name, systimestamp, systimestamp, min(sys_context('test_ctx', 'attr')), max(sys_context('test_ctx', 'attr')) 
  from dual connect by level <= 100;

NAME     MIN_TIME                            MAX_TIME                            MIN_CTX    MAX_CTX
-------- ----------------------------------- ----------------------------------- ---------- ----------
Snap_1   2017-jun-06 08:05:17.354265 +02:00  2017-jun-06 08:05:17.354265 +02:00  693574     693574
Snap_2   2017-jun-06 08:05:17.354265 +02:00  2017-jun-06 08:05:17.354265 +02:00  794397     794396

The output shows that systimestamp satisfies Read Consistency, but global context has two or more different values, hence global context does not guarantee Statement-Level Read Consistency.

Even for local context, Blog: Gotcha: Application Contexts demonstrated Non Read Consistency.

9. Test Code


create or replace context test_ctx using test_ctx_pkg;

create or replace package test_ctx_pkg is 
  procedure set_val (val number);
 end;
/

create or replace package body test_ctx_pkg is
  procedure set_val (val number) as
  begin
    dbms_session.set_context('test_ctx', 'attr', val);
  end;
end;
/

create or replace procedure ctx_set(p_cnt number, val number) as
begin
 for i in 1..p_cnt loop
  test_ctx_pkg.set_val(val);    -- 'library cache: mutex X' on TEST_CTX
 end loop;
end;
/

create or replace procedure ctx_set_jobs(p_job_cnt number) as
  l_job_id pls_integer;
begin
  for i in 1.. p_job_cnt loop
    dbms_job.submit(l_job_id, 'begin while true loop ctx_set(100000, '||i||'); end loop; end;');
  end loop;
  commit;
end;    
/

create or replace procedure clean_jobs as
begin
  for c in (select job from dba_jobs) loop
    begin
       dbms_job.remove (c.job);
    exception when others then null;
    end;
    commit;
  end loop;

  for c in (select d.job, d.sid, (select serial# from v$session where sid = d.sid) ser 
              from dba_jobs_running d) loop
    begin
      execute immediate
             'alter system kill session '''|| c.sid|| ',' || c.ser|| ''' immediate';
      dbms_job.remove (c.job);
    exception when others then null;
    end;
    commit;
  end loop;
  
  -- select * from dba_jobs;
  -- select * from dba_jobs_running;
end;
/


/**
exec ctx_set_jobs(4);

exec clean_jobs;

column program format a20
column event format a25
column p1text format a6
column p1 format 9999999999
column p2text format a6
column p2 format 9999999999999
column p3text format a6
column p3 format 9999999999999999

select sid, program, event, p1text, p1, p2text, p2, p3text, p3
  from v$session where program like '%(J%';

  SID PROGRAM              EVENT                     P1TEXT          P1 P2TEXT             P2 P3TEXT                P3
----- -------------------- ------------------------- ------ ----------- ------ -------------- ------ -----------------
   38 oracle@testdb (J003) library cache: mutex X    idn     1317011825 value   3968549781504 where   9041305591414788
  890 oracle@testdb (J000) library cache: mutex X    idn     1317011825 value    163208757248 where   9041305591414874
  924 oracle@testdb (J001) library cache: mutex X    idn     1317011825 value   4556960301056 where   9041305591414874
 1061 oracle@testdb (J002) library cache: mutex X    idn     1317011825 value   3968549781504 where   9041305591414879

  
column name format a10
column namespace format a15
column type format a15
set numformat 9999999999
  
select name, namespace, type, hash_value, locks, pins, locked_total, pinned_total
from v$db_object_cache where hash_value in (1317011825);

NAME       NAMESPACE       TYPE             HASH_VALUE       LOCKS        PINS LOCKED_TOTAL PINNED_TOTAL
---------- --------------- --------------- ----------- ----------- ----------- ------------ ------------
TEST_CTX   APP CONTEXT     APP CONTEXT      1317011825           4           0            4    257802287

**/