Monday, March 20, 2017

PL/SQL Function Result Cache Invalidation (I)

Oracle PL/SQL Function Result Cache is a global cross-session cached associative array, mapping from base data sources to function result, and stored in shared pool:

SQL(64) > select * from v$sgastat where name like '%Result Cache%';

POOL         NAME                            BYTES
------------ -------------------------- ----------
shared pool  Result Cache: Cache Mgr           256
shared pool  Result Cache: Memory Mgr          208
shared pool  Result Cache: State Objs        69920
shared pool  Result Cache                   159880

If changes to any of data sources are committed, the cached result becomes invalid by the DML session, and get rebuilt by the user session which is accessing the function. All build, invalidate and access are synchronized by a single latch:

SQL(854) > select * from v$latch where name = 'Result Cache: RC Latch';

Note: all tests are done in Oracle 12.1.0.2.0. See appended Test Code.

1. Build


Run:

SQL(64) > exec dbms_result_cache.flush;
SQL(64) > exec run_test;

List Result Cache:

select scn_to_timestamp(scn) scn_time, ro.* 
from v$result_cache_objects ro 
where 1=1
--  and type = 'Dependency'
--  and status = 'Invalid'
order by scn_time, scn;

Display dependencies:

select count(*) from v$result_cache_dependency;

 COUNT(*)
---------
       51

select distinct
  from_obj.name from_name, from_obj.type, from_obj.status, from_obj.namespace,
  from_obj.scn, scn_to_timestamp(from_obj.scn) scn_time,
  to_obj.name to_name, to_obj.type to_type, to_obj.status to_status, to_obj.namespace to_namespace, 
  to_obj.scn to_scn, scn_to_timestamp(to_obj.scn) to_scn_time
from v$result_cache_objects    from_obj, 
     v$result_cache_dependency dep,
     v$result_cache_objects    to_obj
where from_obj.id = dep.result_id 
  and to_obj.id   = dep.depend_id
--  and to_obj.name in ('K.RC_TAB_DEP', 'K.RC_TAB')
--  and from_obj.name like '%"K"."%GET_VAL"%'
order by from_obj.scn;


2. Invalidate



SQL(64) > update rc_tab_dep set dval=33 where did = 3;

SQL(64) > select s.sid, t.used_urec from v$transaction t, v$session s 
           where t.ses_addr=s.saddr and s.sid=sys.dbms_support.mysid;      
 
 SID  USED_UREC
---- ----------
  64          1 

SQL(64) > select * from v$result_cache_statistics
            where lower(name) like '%invalid%'; 

 ID NAME                   VALUE
--- ---------------------- -----
  8 Invalidation Count     0
  9 Delete Count Invalid   0

SQL(64) > select count(*) from v$result_cache_dependency;

 COUNT(*)
---------
       51

SQL(64) > commit;

SQL(64) > select * from v$result_cache_statistics
            where lower(name) like '%invalid%'; 

 ID NAME                   VALUE
--- ---------------------- -----
  8 Invalidation Count     17
  9 Delete Count Invalid   0
  
SQL(64) > select count(*) from v$result_cache_dependency;

 COUNT(*)
---------
       0  

The granularity of invalidation seems in table unit, even only one single row is updated, all result cache entries depending on that table are invalidate. Above test shows that "Invalidation Count" is increased from 0 to 17 (17 "GET_VAL" result cache entries).

3. Invalidate: select for update


Rebuild Cache, and then run a "select for update" for one row:

SQL(64) > exec dbms_result_cache.flush;
SQL(64) > exec run_test;

SQL(64) > select * from rc_tab_dep where did = 3 for update;

SQL(64) > select s.sid, t.used_urec from v$transaction t, v$session s 
           where t.ses_addr=s.saddr and s.sid=sys.dbms_support.mysid;      
 
 SID  USED_UREC
---- ----------
  64          1 

Get spid 13699 of test session (SID 64), open a Solaris window, run command:

$ dtrace -w -n 'pid$target::qesrcRO_Invalidate:entry {@[pid, ustack(10, 0)] = count(); stop(); exit(0);}' -p 13699 

Back to SQL Window, run:

SQL(64) > commit;

Session is hanging.

Solaris window prints out Stack Trace:

    oracle`qesrcRO_Invalidate
    oracle`qesrcDO_Invalidate+0x9c8
    oracle`qesrcCM_PreCmt_+0xd81
    oracle`ktcCommitTxn_new+0x2a7
    oracle`ktcCommitTxn+0x59
    oracle`ktdcmt+0x8b
    oracle`k2lcom+0x94
    oracle`k2send+0x4f3
    oracle`xctctl+0x62
    oracle`xctCommitTxn+0x2b8

Open a second SQL Window, build Result Cache for function get_val_2, which is irrelevant to function get_val:

SQL(512) > exec run_test_2;

Make some check on Unix side:

-- spid 10502 of test session (SID 512), check callstack:

$  pstack 10502

 fffffd7ffc9d3e3b semsys   (2, c00000f, fffffd7fffdf4968, 1, d0)
 0000000001ab90f5 sskgpwwait () + 1e5
 0000000001ab8c95 skgpwwait () + c5
 0000000001ab7ab0 kslgess () + 980
 0000000001ab6cef ksl_get_shared_latch () + 39f
 0000000005ab76ab qesrcPin_Get () + 8cb
 0000000005aa8ff4 qesrcCM_Acquire () + 354
 000000000bd2287d psdfrcLookup () + 12d
 000000000e001883 pevm_ENTERX () + 143
 000000000dfc2907 pfrinstr_ENTERX () + 47
 0000000001a435ca pfrrun_no_tool () + 12a
 
$  ipcs -as
 T         ID      KEY        MODE        OWNER    GROUP  CREATOR   CGROUP NSEMS   OTIME    CTIME
 Semaphores:
 s  201326607   0x375d691c --ra-r-----   oracle      dba   oracle      dba   704 13:14:45 15:52:33

We can see:
 semid = 201326607 = 0xc00000f
 
and "0xc00000f" is 
 semsys   (2, c00000f, fffffd7fffdf4968, 1, d0)
 
in pstack output, and 2 is semop defined in syscall.h:
 semop (...) :: semsys(2, ...)
 
 int semop(int semid, struct sembuf *sops, size_t nsops);
 

If we truss the blocking session, we can also see the same semid with semnum begin 34 and 16.
One thing we noticed is that truss de-blocks (unlock) the blocking session (like prun).

$  truss -dp 13699 
/1:     833.5393        semctl(201326607, 34, SETVAL, 1)                = 0
/1:     833.5395        semctl(201326607, 16, SETVAL, 1)                = 0
/1:     833.5395        lwp_info(0xFFFFFD7FFFDF7440)                    = 0

It hangs on Result Cache: RC Latch.

On a third SQL Window, watch the blocking:

SQL(1024) > select sid, blocker_sid, blocker_is_valid, wait_event_text, p2 from v$wait_chains;

  SID BLOCKER_SID BLOCK WAIT_EVENT_TEXT    P2
----- ----------- ----- --------------- -----
  512          64 TRUE  latch free        559
   64             FALSE

SQL(1024) > select * from v$latchholder;

 PID  SID LADDR            NAME                       GETS
---- ---- ---------------- ---------------------- --------
  25   64 00000000600572B0 Result Cache: RC Latch  15564353

On a fourth SQL Window, make select for update on another row:

SQL(768) > select * from rc_tab_dep where did = 5 for update;

       DID       DVAL
---------- ----------
         5         50
1 row selected.

SQL(768) > commit;

The commit is also hanging on "latch free" of 'Result Cache: RC Latch'.

And all other sessions which are accessing or updating Result Cache are waiting for the same latch free (ksl_get_shared_latch).

In this test, we started one transaction by "select for update", but did not update any row, however, "commit" triggered a call to "qesrcRO_Invalidate", hence held "RC Latch". Any other later session which requires this latch is waiting for "latch free".

Furthermore, query on any one of
  v$result_cache_objects
  v$result_cache_dependency
  v$result_cache_statistics

is also pending on "RC Latch" "latch free".

The above Stack Trace shows that when a transaction user session calls commit command, commit takes a detour to visit Result Cache along its code path in order to perform the invalidation before publishing the confirmed and consistent news to the world. Therefore, Function Result Cache is not free of charge. It demands the resources to maintain the database wide consistence, and thus PL/SQL Function Result Cache (in Oracle Database PL/SQL Language Reference) said:

Oracle Database automatically detects all data sources (tables and views) that are queried while a result-cached function is running. If changes to any of these data sources are committed, the cached result becomes invalid and must be recomputed. The best candidates for result-caching are functions that are invoked frequently but depend on information that changes infrequently or never.

This behavior of "SELECT FOR UPDATE" is described in Oracle MOS
    Bug 17855824 : PLSQL RESULT CACHE WRONGLY INVALIDATED BY A SELECT FOR UPDATE STATEMENT


4. RC Latch "ksl_get_shared_latch" tracing


For each RC Latch Get, the call stack looks like:

 0000000001ab696b ksl_get_shared_latch () + 1b
 0000000005ab76ab qesrcPin_Get () + 8cb
 0000000005aa8ff4 qesrcCM_Acquire () + 354
 000000000bd2287d psdfrcLookup () + 12d
 000000000e001883 pevm_ENTERX () + 143
In case of Latch Miss, the process yields the CPU and goes to sleep:

 fffffd7ffc9d396a yield () + a
 0000000005ab78d6 qesrcPin_Get () + af6
 0000000005aa8ff4 qesrcCM_Acquire () + 354
 000000000bd2287d psdfrcLookup () + 12d
 000000000e001883 pevm_ENTERX () + 143
Run the same test, and measure Latch Get time in qesrcCM_Acquire (CM: Cache Manager) by dtrace:

SQL > exec run_test;

$ dtrace -n \
'BEGIN {self->start_wts = walltimestamp; self->start_ts = timestamp;}
pid$target::qesrcCM_Acquire:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::ksl_get_shared_latch:entry /execname == "oracle" && self->rc == 1/ { self->ts = timestamp; }
pid$target::ksl_get_shared_latch: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 13699

  Start: 2017 Sep  4 14:54:02, End: 2017 Sep  4 14:54:06, Elapsed_ms: 3939

  ns
           value  ------------- Distribution ------------- count
               0 |                                         0
            1000 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      15
            2000 |@@                                       1
            3000 |                                         0
            4000 |                                         0
            5000 |                                         0
            6000 |@@                                       1
            7000 |                                         0

  AVG_ns    1605
  MIN_ns    1140
  MAX_ns    6891
  SUM_ms       0

  oracle`ksl_get_shared_latch+0x17a
  oracle`qesrcCM_Acquire+0x34b
  oracle`psdfrcLookup+0x119
  oracle`pevm_ENTERX+0x142
  oracle`pfrinstr_ENTERX+0x3e
  oracle`pfrrun_no_tool+0xc2
  oracle`pfrrun+0x4a2
  oracle`plsql_run+0x278
  oracle`peicnt+0x14d
  oracle`kkxexe+0x205
    17


5. Causal Analysis of Result Cache


V$RESULT_CACHE_OBJECTS provides 4 time related columns:
(a). CREATION_TIMESTAMP: 
      Time when the object was first time created in Result Cache, and never changed for both TYPE: Result and Dependency.
   
(b). SCN:    
      For TYPE = Result, it is the SCN of first time created in Result Cache, same as CREATION_TIMESTAMP;
      Its STATUS is 'Published' if valid; 'Invalid' if invalidated (for example, due to Dependency).
      Its OBJECT_NO is always 0.

      For TYPE = Dependency, when first time inserted, it is the SCN of time created in Result Cache, same as CREATION_TIMESTAMP.
      Its STATUS is always 'Published', never 'Invalid'. Therefore for Dependency, there is no 'Invalid'.
      Once changed, it is the last Commit SCN (Getting Oracle Transaction Commit SCN).
      SCN timestamp minus CREATION_TIMESTAMP gives us the time span from time Created to time Changed.
      Any existence of this time difference indicates the Change of Dependency, 
      hence it can be used to track Result Invalidation caused by its Dependencies.
      Its OBJECT_NO is Dictionary object number (DBA_OBJECTS.object_id).
     
(c). LRU_NUMBER and SCAN_COUNT: 
      LRU list position, the bigger the value, the more recent the usage.
      (Oracle Docu wrote "the smaller the value, the more recent the usage". It seems not the case.)
      LRU_NUMBER is monotonic and increases one for each access of Result Cache.
      To maintain this ordering, LRU_NUMBERs are shifted by increasing 1 to reflect RC access. 
      It is not clear if there exist multiple LRU Chains because each LRU_NUMBER can have multiple rows.
      (select lru_number, count(*) from v$result_cache_objects where lru_number > 0 group by lru_number order by 2 desc;)

      Following test shows that SCAN_COUNT is the number of calls for one Result Cache item;
      LRU_NUMBER is the total number of calls over entire Result Cache.

 
create or replace function test_scan_lru (p_id number) return number result_cache as
begin
  return p_id;
end;
/

exec dbms_result_cache.flush;

select name, scan_count, lru_number 
from v$result_cache_objects 
where type = 'Result' and name like '%TEST_SCAN_LRU%';

 -- no rows selected

declare
  l_ret number;
begin
  for i in 1..1e6 loop
    l_ret := test_scan_lru(123);
  end loop;
end;
/

select name, scan_count, lru_number 
from v$result_cache_objects 
where type = 'Result' and name like '%TEST_SCAN_LRU%';

 -- NAME                                                   SCAN_COUNT   LRU_NUMBER
 -- ----  ---------------------------------------------- ------------ ------------
 -- "S".  "GET_VAL_3"::8."GET_VAL_3"#3048d2af80817a01 #1      999,999   48,007,056

declare
  l_ret number;
begin
  for i in 1..1e6 loop
    l_ret := test_scan_lru(123);
  end loop;
end;
/

select name, scan_count, lru_number 
from v$result_cache_objects 
where type = 'Result' and name like '%TEST_SCAN_LRU%';

 -- NAME                                                 SCAN_COUNT   LRU_NUMBER
 -- -------------------------------------------------- ------------ ------------
 -- "S"."GET_VAL_3"::8."GET_VAL_3"#3048d2af80817a01 #1    1,999,999   49,007,056
Synthesizing above information, we can compose 3 queries to track Result Cache invalidations, and 2 queries to monitor its usage (see appended "Result Cache Monitoring Views").

-- Last invalidated Dependencies
select * from rc_scn_view;

-- last invalidated Dependencies and Result
select * from rc_scn_dep_view;

-- RC usage
select * from rc_lru_number_view;

-- RC memory
select * from rc_memory_view;
 
-- RC stats
select * from rc_stats_view;


6. Result Cache: RC Latch S mode and X mode Blocking Chains


We will make S mode and X mode blocking test based on the calling API revealed in Blog Oracle 12 and latches:
 The kslgetl and ksl_get_shared_latch functions take the following arguments:
    1-latch address
    2-immediate get (0 means yes, 1 means no)
    3-where (X$KSLLW.INDX)
    4-why (X$KSLWSC.INDX)
    5-mode (8=shared,16=exclusive; only for ksl_get_shared_latch function)
The above text needs two corrections. We will have a look later.
   (1). arguments why (arg2) comes before where(arg3)
   (2). X$KSLLW.INDX and X$KSLWSC.INDX are the same (equal join columns), both point to the where (location).
        X$KSLLW is parent table, X$KSLWSC is child table. 
If Mode=16, arg5 is 20000000 00000000 | <PID>.

It seems that Oracle calls different subroutines for different Latches. ksl_get_shared_latch is C subroutine to get shared Latch (in S Mode or X Mode), for example, result_cache Latch; whereas kslgetl is used to get exclusive Latch, for example, row cache objects Latch.

Following documents have a deep investigation of Latches and Mutexes:
     New features of Latches and Mutexes in Oracle 12c
     [Oracle] Researching internal latch implementation (ksl_get_shared_latch, kslfre, kslgetsl_w) and crashing PMON
     The missing argument of ksl_get_shared_latch : the power of disassembly in action


6.1. S mode Blocking


--------------------- Setup ---------------------
create or replace function rc_s_x_mode_blocking_func (p_x number) return number result_cache as
begin
  return p_x;
end;
/

create or replace procedure rc_s_x_mode_blocking_proc (p_x number) as
 l_x number;
begin
  l_x := rc_s_x_mode_blocking_func(p_x);
end;
/

--------------------- Prepare Test ---------------------
-- Open 3 Test Sessions (SID): S1(4335), S2(6122), S3(7497) with Process (SPID): P1(6114), P2(26548), P3(26550); 
-- and one monitoring Sessions (SID): S4.
-- S1 and S2 request S Mode to get already built Items, where S3 requests X Mode to add a new Item.

-- Build 2 Items in Result Cache.

SQL (S4) > exec dbms_result_cache.flush;

SQL (S4) > exec rc_s_x_mode_blocking_proc(1);
SQL (S4) > exec rc_s_x_mode_blocking_proc(2);

--------------------- Test Steps ---------------------

--==== @T1: S1 requets S Mode, stop at Return (stop at Entry has no blocking). Launch Dtrace at first, then run plsql.

SQL (S1)> exec rc_s_x_mode_blocking_proc(1);

P1$ sudo dtrace -w -n \
'pid$target::qesrcCM*:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::ksl_get_shared_latch:entry /execname == "oracle" && self->rc == 1  && arg4 == 8 / { 
  printf("\n--------Calling %s:%s(Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X), Mode=>%d, PID=>0x%X)", 
         probefunc, probename, arg0, arg1, arg2, arg3, arg3, arg4, arg5);
  self->rc = 2;}
pid$target::ksl_get_shared_latch:return /self->rc == 2/ {
   printf("\n--------Stopped at %s:%s", probefunc, probename);
   printf("\n--------ustack(5, 0)--------"); ustack(5, 0);
   stop(); exit(0);}
' -p 6114

 CPU     ID                    FUNCTION:NAME
   1  80660       ksl_get_shared_latch:entry
--------Calling ksl_get_shared_latch:entry
    (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5358(0x14EE), Mode=>8, PID=>0x1)
   1  80661      ksl_get_shared_latch:return
--------Stopped at ksl_get_shared_latch:return
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch+0x1b8
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143
              oracle`pfrinstr_ENTERX+0x47

-- oradebug peek RC Latch address
SQL> oradebug setmypid
Statement processed.
SQL> oradebug peek 0x600572B0 24
[0600572B0, 0600572C8) = 00000001 00000000 0C56FEFC 0000022F 00000002 000014EE
                            Nproc  ˆX flag     gets   latch#   level#    where


SQL (S4)> select addr, gets, misses, sleeps, immediate_gets, spin_gets, wait_time from v$latch where latch# in (559) or name in ('Result Cache: RC Latch');
  ADDR              GETS       MISSES   SLEEPS  IMMEDIATE_GETS  SPIN_GETS  WAIT_TIME
  ----------------  ---------  -------  ------  --------------  ---------  ----------
  00000000600572B0  207027964  1011584  508174  0               797961     4975709595

SQL (S4)> select * from v$latchholder;
  PID  SID   LADDR             NAME                    GETS     
  ---- ----  ----------------  ----------------------  ---------  
  33   4335  00000000600572B0  Result Cache: RC Latch  207027964 


--==== @T2: S2 requets S Mode, not blocked

SQL (S2)> exec rc_s_x_mode_blocking_proc(2);

SQL (S4)> select addr, gets, misses, sleeps, immediate_gets, spin_gets, wait_time from v$latch where latch# in (559) or name in ('Result Cache: RC Latch');
  ADDR              GETS       MISSES   SLEEPS  IMMEDIATE_GETS  SPIN_GETS  WAIT_TIME
  ----------------  ---------  -------  ------  --------------  ---------  ----------
  00000000600572B0  207027965  1011584  508174  0               797961     4975709595

SQL (S4)> select * from v$latchholder;
  PID  SID   LADDR             NAME                    GETS     
  ---- ----  ----------------  ----------------------  ---------  
  33   4335  00000000600572B0  Result Cache: RC Latch  207027965 

--==== @T3: S3 requets X Mode Request, blocked by S1, Wait: latch free. Launch Dtrace at first, then run plsql.

SQL (S3)> exec rc_s_x_mode_blocking_proc(3);

P3$ sudo dtrace -n \
'pid$target::qesrcCM*:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::ksl_get_shared_latch:entry /execname == "oracle" && self->rc == 1  && arg4 == 16 / { 
  printf("\n--------Calling %s:%s(Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X), Mode=>%d, PID=>0x%X))", 
          probefunc, probename, arg0, arg1, arg2, arg3, arg3, arg4, arg5);
  printf("\n--------ustack(5, 0)--------"); ustack(5, 0); self->rc = 0;}
' -p 26550

 CPU     ID                    FUNCTION:NAME
   1  80816       ksl_get_shared_latch:entry
--------Calling ksl_get_shared_latch:entry
    (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5347(0x14E3), Mode=>16, PID=>0x2000000000000023))
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch
              oracle`qesrcCM_AddDO_+0x43b5
              oracle`qesrcCM_AddDO+0xf3
              oracle`qesrcCM_Acquire+0x702
              oracle`psdfrcLookup+0x12d

-- oradebug peek RC Latch address              
SQL>  oradebug peek 0x600572B0 24
[0600572B0, 0600572C8) = 00000001 40000000 0C56FEFE 0000022F 00000002 000014EE
                            Nproc  ˆX flag     gets   latch#   level#    where  

--******** S3 stopped here, resumed when S1 is set run ********--

   1  80816       ksl_get_shared_latch:entry
--------Calling ksl_get_shared_latch:entry
    (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5358(0x14EE), Mode=>16, PID=>0x2000000000000023))
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch
              oracle`qesrcPin_Get+0x959
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143

   1  80816       ksl_get_shared_latch:entry
--------Calling ksl_get_shared_latch:entry
    (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5374(0x14FE), Mode=>16, PID=>0x2000000000000023))
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch
              oracle`qesrcCM_Release+0x293
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26

P4$ pstack 26550

  ------------  lwp# 1 / thread# 1  ---------------
   ffff80ffbdba36eb semsys   (2, 33, ffff80ffbfff2f68, 1, 29d)
   000000000578adc5 sskgpwwait () + 1e5
   000000000578a965 skgpwwait () + c5
   000000000578972d kslgess () + 92d
   00000000057889bf ksl_get_shared_latch () + 39f
   00000000097a4145 qesrcCM_AddDO_ () + 43b5
   00000000097a4d03 qesrcCM_AddDO () + f3
   00000000097aa772 qesrcCM_Acquire () + 702
   000000000fa4e10d psdfrcLookup () + 12d
   0000000011d2e163 pevm_ENTERX () + 143

SQL (S4)> select addr, gets, misses, sleeps, immediate_gets, spin_gets, wait_time from v$latch where latch# in (559) or name in ('Result Cache: RC Latch');
  ADDR              GETS       MISSES   SLEEPS  IMMEDIATE_GETS  SPIN_GETS  WAIT_TIME
  ----------------  ---------  -------  ------  --------------  ---------  ----------
  00000000600572B0  207027966  1011584  508174  0               797961     4975709595

SQL (S4)> select * from v$latchholder;
  PID  SID   LADDR             NAME                    GETS     
  ---- ----  ----------------  ----------------------  ---------  
  33   4335  00000000600572B0  Result Cache: RC Latch  207027966 
  
SQL (S4)> select sid, blocker_sid, blocker_is_valid, chain_signature, in_wait wait_event_text, p1, p2, p3, in_wait_secs, num_waiters from v$wait_chains;  
  SID   OSID   BLOCKER_SID  BLOCKER_OSID  BLOCKER_IS_VALID  CHAIN_SIGNATURE                IN_WAIT  WAIT_EVENT_TEXT  P1          P2   P3  IN_WAIT_SECS  NUM_WAITERS
  ----  -----  -----------  ------------  ----------------  -----------------------------  -------  ---------------  ----------  ---  --  ------------  -----------  
  4335  6114                              FALSE             <='latch free'  FALSE                                                        1
  7497  26550  4335         6114          TRUE              <='latch free'  TRUE     latch free       1610969776  559  0   406           0
 

--==== @T4: S2 requets S Mode again, blocked by S1. Wait Event: latch free. But the real Blocking is caused by S3.

SQL (S2)> exec rc_s_x_mode_blocking_proc(2);

P2$ pstack 26548

   ffff80ffbdba36eb semsys   (2, 33, ffff80ffbfff3948, 1, 11d)
   000000000578adc5 sskgpwwait () + 1e5
   000000000578a965 skgpwwait () + c5
   000000000578972d kslgess () + 92d
   00000000057889bf ksl_get_shared_latch () + 39f
   00000000097b8a7b qesrcPin_Get () + 8cb
   00000000097aa3c4 qesrcCM_Acquire () + 354
   000000000fa4e10d psdfrcLookup () + 12d
   0000000011d2e163 pevm_ENTERX () + 143

SQL (S4)> select addr, gets, misses, sleeps, immediate_gets, spin_gets, wait_time from v$latch where latch# in (559) or name in ('Result Cache: RC Latch');
  ADDR              GETS       MISSES   SLEEPS  IMMEDIATE_GETS  SPIN_GETS  WAIT_TIME
  ----------------  ---------  -------  ------  --------------  ---------  ----------
  00000000600572B0  207027966  1011584  508174  0               797961     4975709595

SQL (S4)> select * from v$latchholder;
  PID  SID   LADDR             NAME                    GETS     
  ---- ----  ----------------  ----------------------  ---------  
  33   4335  00000000600572B0  Result Cache: RC Latch  207027966
  
SQL (S4)> select sid, osid, blocker_sid, blocker_osid, blocker_is_valid, chain_signature, in_wait, wait_event_text, p1, p2, p3, in_wait_secs, num_waiters from v$wait_chains;
  SID   OSID   BLOCKER_SID  BLOCKER_OSID  BLOCKER_IS_VALID  CHAIN_SIGNATURE                IN_WAIT  WAIT_EVENT_TEXT  P1          P2   P3  IN_WAIT_SECS  NUM_WAITERS
  ----  -----  -----------  ------------  ----------------  -----------------------------  -------  ---------------  ----------  ---  --  ------------  -----------
  4335  6114                              FALSE             <='latch free'  FALSE                                                        2
  7497  26550  4335         6114          TRUE              <='latch free'  TRUE     latch free       1610969776  559  0   1403          0
  6122  26548  4335         6114          TRUE              <='latch free'  TRUE     latch free       1610969776  559  0   803           0

 
--==== @T5: resumne S1 running

P1$ prun 6114

SQL (S4)> select addr, gets, misses, sleeps, immediate_gets, spin_gets, wait_time from v$latch where latch# in (559) or name in ('Result Cache: RC Latch');
  ADDR              GETS       MISSES   SLEEPS  IMMEDIATE_GETS  SPIN_GETS  WAIT_TIME
  ----------------  ---------  -------  ------  --------------  ---------  ----------
  00000000600572B0  207027970  1011586  508177  0               797961     6975739595
Above test shows that compatible S Mode not blocking each other; but it blocks X Mode request at ksl_get_shared_latch:entry. Once there exists an X Mode request at ksl_get_shared_latch:entry, all coming S Mode requests are blocked.

Probably this is the behaviour pointed out in Presentation: New features of Latches and Mutexes in Oracle 12c (Andrey Nikolaev) Page 28:
    If the latch is being held in S mode, the X mode waiter will block all further requests.

At T2, V$LATCHHOLDER shows S1 is a RC Latch Holder, but there are not yet any blocked sessions. So the existence of Latch Holder does not necessarily mean blockings.

From T3 (or T4) to T5, V$LATCH shows there are 4 (207027970-207027966) Gets, 2 (1011586-1011584) Misses, 3 (508177-508174) Sleeps. Among them,
     S3 made 3 X Mode (16) Gets, 1 Miss and 2 Sleeps.
     S2 made 1 S Mode (8) Get, 1 Miss and 1 Sleep.

S3 made 3 X Mode Gets for:
     qesrcCM_AddDO
     qesrcCM_Acquire
     psdfrcWriteResult

It also demonstrated that 1 Miss can provoke more Sleeps.

If we expand GV$LATCH_MISSES definition by adding column: INDX:

CREATE OR REPLACE FORCE VIEW X_GV$LATCH_MISSES as
SELECT t1.indx              INDX,
       t1.inst_id           INST_ID,       
       t1.ksllasnam         PARENT_NAME,   
       t2.ksllwnam          "WHERE",       
       t1.kslnowtf          NWFAIL_COUNT,  
       t1.kslsleep          SLEEP_COUNT,   
       t1.kslwscwsl         WTR_SLP_COUNT, 
       t1.kslwsclthg        LONGHOLD_COUNT,
       t2.ksllwnam          LOCATION,      
       t1.con_id            CON_ID         
  FROM x$ksllw t2, x$kslwsc t1
 WHERE t2.indx = t1.indx;
and run query below to map INDX to "WHERE" or "LOCATION".

select * from sys.X_GV$LATCH_MISSES 
where parent_name in ('Result Cache: RC Latch') 
  and indx in (5347, 5358, 5374, 5359);
  
INDX  INST_ID  PARENT_NAME             WHERE                          NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION
----- -------  ----------------------  -----------------------------  ------------  -----------  -------------  --------------  -----------------------------
5347  1        Result Cache: RC Latch  Result Cache: Serialization01  0             9633         119281         712             Result Cache: Serialization01
5358  1        Result Cache: RC Latch  Result Cache: Serialization12  0             1303643      1036967        79711           Result Cache: Serialization12
5359  1        Result Cache: RC Latch  Result Cache: Serialization13  0             1776         116077         176             Result Cache: Serialization13
5374  1        Result Cache: RC Latch  Result Cache: Serialization28  0             2300         53438          474             Result Cache: Serialization28  
In GV$LATCH_MISSES definition, parent x$ksllw stores "WHERE" names, child x$kslwsc stores real values. Both WHERE and LOCATION are same.

In AWR Section: Latch Miss Sources, only gv$latch_misses (nwfail_count, sleep_count, wtr_slp_count) are visible. Unfortunately longhold_count is not reported, but longhold_count often signifies the X Mode Requests, which caused RC Latch longhold.

oradebug peek shows S Mode Blocking state transition. Flag 40000000 denotes S Mode Blocking.

--==== @T1: S1 requets S Mode. Get RC item at 1st Session, then Stop
[0600572B0, 0600572C8) = 00000001 00000000 0C56FEFC 0000022F 00000002 000014EE   
                            Nproc ˆX flag      gets   latch#   level#    where
                            
--==== @T3: S3 requets X Mode Request. Add RC item at 3rd Session, blocked
[0600572B0, 0600572C8) = 00000001 40000000 0C56FEFE 0000022F 00000002 000014EE   
                            Nproc ˆX flag      gets   latch#   level#    where
Note: In AIX oradebug peek output, "ˆX flag" comes before "Nproc":

SQL> oradebug peek 0x0700000000055978 24;
SQL> [700000000055978, 700000000055990) = 00000000 00000001 0D298B4B 022F0000 00000002 000014EE
SQL> [700000000055978, 700000000055990) = 40000000 00000001 0D298B4F 022F0000 00000002 000014EE
SQL> [700000000055978, 700000000055990) = 40000000 00000000 0D298B50 022F0000 00000002 000014EE
                                           ˆX flag    Nproc     gets   latch#   level#    where

SQL> [700000000055978, 700000000055990) = 20000000 00000058 0D2984B2 022F0000 00000002 000014FE
                                           ˆX flag     pidˆ     gets   latch#   level#    where


6.2. X mode Blocking


The approach we picked out to study Oracle RC Latch activity is trying to depict its State-Transition Diagrams.
  dtrace to show Transition Event (Call Stack), paused to view State.
  oradebug Peek to display State.
  prun to resume Transition.

--------------------- Test X Mode Blocking ---------------------
-- Open 2 Solaris Windows, and one Sqlplus session. 
-- In first Solaris Windows, run dtrace; in second Solaris Windows, run oradebug and host prun.
-- Launch Dtrace at first, then run plsql.

SQL > exec rc_s_x_mode_blocking_proc(6);   

sudo dtrace -w -n \
'BEGIN {self->seq = 1; }
pid$target::qesrcCM*:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::ksl_get_shared_latch:entry / self->rc > 0 && arg0 == 0x600572B0 && (arg4 == 8 || arg4 == 16) / { 
   printf("\nStep %d at %Y--------Calling %s:%s(Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X), Mode=>%d, PID=>0x%X)", 
          self->seq, walltimestamp, probefunc, probename, arg0, arg1, arg2, arg3, arg3, arg4, arg5);
   self->rc = 2; self->seq = self->seq + 1; }
pid$target::ksl_get_shared_latch:return /self->rc > 1/ {
   printf("\nStep %d at %Y--------Stopped at %s:%s", self->seq, walltimestamp, probefunc, probename);
   printf("\n--------ustack(5, 0)--------"); ustack(5, 0); self->seq = self->seq + 1; 
   stop();}
pid$target::kslfre:entry /self->rc > 1 && arg0 == 0x600572B0/ {
   printf("\nStep %d at %Y--------Stopped at %s:%s(Addr=>0x%-X)", self->seq, walltimestamp, probefunc, probename, arg0);
   printf("\n--------ustack(5, 0)--------"); ustack(5, 0); self->seq = self->seq + 1; self->rc = 3; 
   stop();} 
pid$target::kslfre:return /self->rc == 3/ {
   printf("\nStep %d at %Y--------Stopped at %s:%s(Addr=>0x%-X)", self->seq, walltimestamp, probefunc, probename, arg0);
   printf("\n--------ustack(5, 0)--------"); ustack(5, 0); self->seq = self->seq + 1; self->rc = 2;
   stop();}       
' -p 24559

SQL> oradebug peek 0x600572B0 24
     [0600572B0, 0600572C8) = 00000000 00000000 84BD178B 0000022F 00000002 000014FE
SQL> host prun 24559

dtrace: allowing destructive actions
 CPU     ID                    FUNCTION:NAME
   1  81838       ksl_get_shared_latch:entry
Step 1 at 2017 Sep 21 12:04:56--------Calling ksl_get_shared_latch:entry
       (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5358(0x14EE), Mode=>8, PID=>0x1)

   1  81839      ksl_get_shared_latch:return
Step 3 at 2017 Sep 21 12:04:56--------Stopped at ksl_get_shared_latch:return
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch+0x1b8
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143
              oracle`pfrinstr_ENTERX+0x47

SQL> oradebug peek 0x600572B0 24             
     [0600572B0, 0600572C8) = 00000001 00000000 84BD178C 0000022F 00000002 000014EE
SQL> host prun 24559                   

   1  81838       ksl_get_shared_latch:entry
Step 5 at 2017 Sep 21 12:06:05--------Calling ksl_get_shared_latch:entry
     (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5347(0x14E3), Mode=>16, PID=>0x2000000000000023)

   1  81839      ksl_get_shared_latch:return
Step 7 at 2017 Sep 21 12:06:05--------Stopped at ksl_get_shared_latch:return
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch+0x1b8
              oracle`qesrcCM_AddDO+0xf3
              oracle`qesrcCM_Acquire+0x702
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143
              
SQL> oradebug peek 0x600572B0 24
     [0600572B0, 0600572C8) = 00000023 20000000 84BD178D 0000022F 00000002 000014E3         
SQL> host prun 24559     

   1  81838       ksl_get_shared_latch:entry
Step 9 at 2017 Sep 21 12:08:07--------Calling ksl_get_shared_latch:entry
     (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5358(0x14EE), Mode=>16, PID=>0x2000000000000023)

   1  81839      ksl_get_shared_latch:return
Step 11 at 2017 Sep 21 12:08:07--------Stopped at ksl_get_shared_latch:return
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch+0x1b8
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143
              oracle`pfrinstr_ENTERX+0x47
              
SQL> oradebug peek 0x600572B0 24  
     [0600572B0, 0600572C8) = 00000023 20000000 84BD178E 0000022F 00000002 000014EE
SQL> host prun 24559               

   1  81838       ksl_get_shared_latch:entry
Step 13 at 2017 Sep 21 12:08:51--------Calling ksl_get_shared_latch:entry
     (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5374(0x14FE), Mode=>16, PID=>0x2000000000000023)

   1  81839      ksl_get_shared_latch:return
Step 15 at 2017 Sep 21 12:08:51--------Stopped at ksl_get_shared_latch:return
--------ustack(5, 0)--------
              oracle`ksl_get_shared_latch+0x1b8
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26
              oracle`pfrrun_no_tool+0x12a
              
SQL> oradebug peek 0x600572B0 24  
     [0600572B0, 0600572C8) = 00000023 20000000 84BD178F 0000022F 00000002 000014FE
SQL> host prun 24559               

   1  81840                     kslfre:entry
Step 16 at 2017 Sep 21 12:09:27--------Stopped at kslfre:entry(Addr=>0x600572B0)
--------ustack(5, 0)--------
              oracle`kslfre
              oracle`qesrcCM_Release+0x22d
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26

   1  81841                     kslfre:entry
Step 17 at 2017 Sep 21 12:09:27--------Stopped at kslfre:entry(Addr=>0x600572B0)
--------ustack(5, 0)--------
              oracle`kslfre
              oracle`qesrcCM_Release+0x22d
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26
              
SQL> oradebug peek 0x600572B0 24  
     [0600572B0, 0600572C8) = 00000023 20000000 84BD178F 0000022F 00000002 000014FE
SQL> host prun 24559               

   0  81842                    kslfre:return
Step 18 at 2017 Sep 21 12:10:25--------Stopped at kslfre:return(Addr=>0x172)
--------ustack(5, 0)--------
              oracle`kslfre+0x172
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26
              oracle`pfrrun_no_tool+0x12a
              
SQL> oradebug peek 0x600572B0 24  
     [0600572B0, 0600572C8) = 00000000 00000000 84BD178F 0000022F 00000002 000014FE 
Only looking the oradebug peek output, we can see the state transition of RC Latch values:

SQL> oradebug peek 0x600572B0 24  --S0: initial  
    [0600572B0, 0600572C8) = 00000000 00000000 84BD178B 0000022F 00000002 000014FE
                                 pidˆ  ˆX flag     gets   latch#   level#    where    

SQL> oradebug peek 0x600572B0 24  --S1: Mode=>8  qesrcCM_Acquire+0x354
    [0600572B0, 0600572C8) = 00000001 00000000 84BD178C 0000022F 00000002 000014EE
SQL> host prun 24559  
    
SQL> oradebug peek 0x600572B0 24  --S2: Mode=>16 qesrcCM_AddDO+0xf3
    [0600572B0, 0600572C8) = 00000023 20000000 84BD178D 0000022F 00000002 000014E3
SQL> host prun 24559  

SQL> oradebug peek 0x600572B0 24  --S3: Mode=>16 qesrcCM_Acquire+0x354
    [0600572B0, 0600572C8) = 00000023 20000000 84BD178E 0000022F 00000002 000014EE
SQL> host prun 24559  

SQL> oradebug peek 0x600572B0 24  --S4: Mode=>16 psdfrcWriteResult+0x213
    [0600572B0, 0600572C8) = 00000023 20000000 84BD178F 0000022F 00000002 000014FE
SQL> host prun 24559  

SQL> oradebug peek 0x600572B0 24  --S5: psdfrcWriteResult -> qesrcCM_Release -> kslfre:entry
    [0600572B0, 0600572C8) = 00000023 20000000 84BD178F 0000022F 00000002 000014FE
SQL> host prun 24559  

SQL> oradebug peek 0x600572B0 24  --S6: psdfrcWriteResult -> qesrcCM_Release -> kslfre:return
    [0600572B0, 0600572C8) = 00000000 00000000 84BD178F 0000022F 00000002 000014FE
From S1 to S4, each Step gets once Latch, in total, it requires 4 RC Latch Gets (from 84BD178C to 84BD178F), which are one S Mode(8), and 3 X Mode(16). Therefore, inserting new RC items is much expensive than simply fetching them. In consequence, more latch free wait event on RC Latch pervades over system.

If after the first dtrace stop (Step S1), we also open 3 Sqlplus Sesssions to fetch already existed Result Cache Item (S Mode Get):

SQL > exec rc_s_x_mode_blocking_proc(1);
We observed the following behaviours:
  From S0 -> S1: S Mode get.
  From S2 -> S3 and S3 -> S4: one single blocked S Mode Session(the 1st wating Session) can get RC Latch (deblocked);
                              other Sessions are still blocked (kslfre called ???).
  From S4 -> S5: no S Mode Sessions can get RC Latch, all S Mode Sessions are still blocked.
  From S5 -> S6: all S Mode Sessions get RC Latch.
It looks like that during each X Mode state transition in that X Mode session, maximum one S Mode Session can occasionally get RC Latch, all other S Mode Sessions have to wait till "kslfre" called by that X Mode Session (of course, other X Mode Sessions are also blocked. And first RC Latch Get by each X Mode Session is a S Mode Get).

Above oradebug peek also shows that S Mode and X Mode Blockings are marked by different flags:
  S Mode Blocking:  Blocking is achieved by flag 0x40000000 bit in the latch value.
  X Mode Blocking:  Blocking is achieved by flag 0x20000000 bit in the latch value.                            
In S Mode Blocking, first position (4 bytes) stores Nproc; whereas in X Mode Blocking, first position stores blocking session PID.

[0600572B0, 0600572C8) = 00000001 40000000 0C56FEFE 0000022F 00000002 000014EE
                            Nproc  ˆX flag     gets   latch#   level#    where
                            
[0600572B0, 0600572C8) = 00000023 20000000 84BD178D 0000022F 00000002 000014E3
                             pidˆ  ˆX flag     gets   latch#   level#    where  
Refering to X_GV$LATCH_MISSES (GV$LATCH_MISSES with INDX), we can map INDX to "WHERE":
  5347 (0x14E3) -> Result Cache: Serialization01
  5358 (0x14EE) -> Result Cache: Serialization12
  5360 (0x14F0) -> Result Cache: Serialization14
  5374 (0x14FE) -> Result Cache: Serialization28
Cross-checking with V$RESULT_CACHE_OBJECTS:
  V$RESULT_CACHE_OBJECTS.creation_timestamp = timestamp of psdfrcWriteResult+0x213 Call
  V$RESULT_CACHE_OBJECTS.build_time = 
    from  timestamp of psdfrcWriteResult+0x213 Call 
    to    timestamp of (psdfrcWriteResult -> qesrcCM_Release ->) kslfre:entry Call
Look further two Addresses, they contain Misses: 0x70C257=7389783, and Sleeps: 0xAC913=706835

SQL> oradebug peek 0x0600572C8 24
[0600572C8, 0600572E0) = 00000001 00000000 00000000 0070C257 00000000 00000000

SQL> oradebug peek 0x0600572E0 24
[0600572E0, 0600572F8) = 5CA280DF 00000005 000AC913 00000000 00000000 00000000


6.3. X mode Blocking: Dtrace


With following Dtrace script, we will draw a State Transition Diagram to demonstrate 4 RC Latch Gets (1 S Mode and successive 3 X Mode) when adding a new RC item (only one S Mode when fetching an existed item). For each Transition, we show State Entry and State Return. We can see that there is a kslfre call between two Transitions. That is probably why occasionally other S Mode Sessions (fetching an existed item) can still get RC Latch during this X Mode Session (adding a new item) as discussed in the last section.

-- Launch Dtrace at first, then run plsql to add a new RC item.

SQL > exec rc_s_x_mode_blocking_proc(7); 

sudo dtrace -n \
'
typedef unsigned int ub4;

typedef struct latchstate /* latch state */ {
    ub4 nproc_pid;  /* Nproc or pid   */
    ub4 flag;       /* blocking flag  */ 
    ub4 gets;       /* number of gets */
    ub4 latch;      /* latch number   */
    ub4 level;      /* latch level    */
    ub4 where; } latchstate;
    
BEGIN {laddr = 0x600572B0; step = 1; begin_gets = 0, end_gets = 0; prev_ts = timestamp;
   self->evt = "begin"; self->loc = 0x0;}
pid$target::qesrcCM*:entry /execname == "oracle"/ { self->rc = 1; }
pid$target::ksl_get_shared_latch:entry /self->rc > 0 && arg0 == laddr && (self->evt != probefunc || self->loc != arg3)/ { 
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   begin_gets = (begin_gets == 0 ? ls->gets: begin_gets); end_gets = ls->gets;
   printf("\n***State Entry>>>(nproc_pid=>0x%X, flag=>0x%X, gets=>0x%X, latch=>0x%X, level=>0x%X, where=>0x%X)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   printf("\n>>>Transition>>>%s:%s\n      (Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X), Mode=>%d, PID=>0x%X)", 
          probefunc, probename, arg0, arg1, arg2, arg3, arg3, arg4, arg5);
   self->evt = probefunc; self->loc = arg3;    
   ustack(5, 0);   
   step = step + 1; self->entry = 1; prev_ts = timestamp;} 
pid$target::ksl_get_shared_latch:return /self->rc > 0 && self->entry == 1/ {
   ls = ((latchstate *) copyin(laddr, sizeof(latchstate))); 
   printf("\n>>>State Return***(nproc_pid=>0x%X, flag=>0x%X, gets=>0x%X, latch=>0x%X, level=>0x%X, where=>0x%X)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
    self->entry = 0;}   
pid$target::kslfre:entry /self->rc > 0 && arg0 == laddr && self->evt != probefunc/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   end_gets = ls->gets;
   printf("\n>>>Transition>>>%s:%s(Addr=>0x%-X)", probefunc, probename, arg0);
   self->evt = probefunc; self->loc = 0x0;
   printf("\n===Latch freed===");
   ustack(5, 0); step = step + 1; prev_ts = timestamp;}      
END {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   printf("\nLatch Gets = %d (%d - %d)", (end_gets - begin_gets), (unsigned long)begin_gets, (unsigned long)end_gets); }
' -p 18118

 CPU     ID                    FUNCTION:NAME
   4  81846       ksl_get_shared_latch:entry
---------Step 1 at 2017 Sep 25 08:14:15 (Elapsed-ns=2668662103)---------
***State Entry>>>(nproc_pid=>0x0, flag=>0x0, gets=>0x84BD1DBE, latch=>0x22F, level=>0x2, where=>0x14EE)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5358(0x14EE), Mode=>8, PID=>0x1)
              oracle`ksl_get_shared_latch
              oracle`qesrcPin_Get+0x8cb
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143

   4 128142      ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>0x1, flag=>0x0, gets=>0x84BD1DBF, latch=>0x22F, level=>0x2, where=>0x14EE)
   4  89490                     kslfre:entry
---------Step 2 at 2017 Sep 25 08:14:15 (Elapsed-ns=48752)---------
>>>Transition>>>kslfre:entry(Addr=>0x600572B0)
===Latch freed===
              oracle`kslfre
              oracle`qesrcPin_Get+0xaf6
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143

   4  81846       ksl_get_shared_latch:entry
---------Step 3 at 2017 Sep 25 08:14:15 (Elapsed-ns=19989)---------
***State Entry>>>(nproc_pid=>0x0, flag=>0x0, gets=>0x84BD1DBF, latch=>0x22F, level=>0x2, where=>0x14EE)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5347(0x14E3), Mode=>16, PID=>0x2000000000000024)
              oracle`ksl_get_shared_latch
              oracle`qesrcCM_AddDO_+0x43b5
              oracle`qesrcCM_AddDO+0xf3
              oracle`qesrcCM_Acquire+0x702
              oracle`psdfrcLookup+0x12d

   4 128142      ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>0x24, flag=>0x20000000, gets=>0x84BD1DC0, latch=>0x22F, level=>0x2, where=>0x14E3)
   4  89490                     kslfre:entry
---------Step 4 at 2017 Sep 25 08:14:15 (Elapsed-ns=11792)---------
>>>Transition>>>kslfre:entry(Addr=>0x600572B0)
===Latch freed===
              oracle`kslfre
              oracle`qesrcCM_AddDO_+0x2e0
              oracle`qesrcCM_AddDO+0xf3
              oracle`qesrcCM_Acquire+0x702
              oracle`psdfrcLookup+0x12d

   4  81846       ksl_get_shared_latch:entry
---------Step 5 at 2017 Sep 25 08:14:15 (Elapsed-ns=9352)---------
***State Entry>>>(nproc_pid=>0x0, flag=>0x0, gets=>0x84BD1DC0, latch=>0x22F, level=>0x2, where=>0x14E3)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5358(0x14EE), Mode=>16, PID=>0x2000000000000024)
              oracle`ksl_get_shared_latch
              oracle`qesrcPin_Get+0x959
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143

   4 128142      ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>0x24, flag=>0x20000000, gets=>0x84BD1DC1, latch=>0x22F, level=>0x2, where=>0x14EE)
   4  89490                     kslfre:entry
---------Step 6 at 2017 Sep 25 08:14:15 (Elapsed-ns=25046)---------
>>>Transition>>>kslfre:entry(Addr=>0x600572B0)
===Latch freed===
              oracle`kslfre
              oracle`qesrcPin_Get+0xaf6
              oracle`qesrcCM_Acquire+0x354
              oracle`psdfrcLookup+0x12d
              oracle`pevm_ENTERX+0x143

   4  81846       ksl_get_shared_latch:entry
---------Step 7 at 2017 Sep 25 08:14:15 (Elapsed-ns=25170)---------
***State Entry>>>(nproc_pid=>0x0, flag=>0x0, gets=>0x84BD1DC1, latch=>0x22F, level=>0x2, where=>0x14EE)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x600572B0, Immed_Get=>1, Why=>1, Where=>5374(0x14FE), Mode=>16, PID=>0x2000000000000024)
              oracle`ksl_get_shared_latch
              oracle`qesrcCM_Release+0x293
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26

   4 128142      ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>0x24, flag=>0x20000000, gets=>0x84BD1DC2, latch=>0x22F, level=>0x2, where=>0x14FE)
   4  89490                     kslfre:entry
---------Step 8 at 2017 Sep 25 08:14:15 (Elapsed-ns=9845)---------
>>>Transition>>>kslfre:entry(Addr=>0x600572B0)
===Latch freed===
              oracle`kslfre
              oracle`qesrcCM_Release+0x22d
              oracle`psdfrcWriteResult+0x213
              oracle`pevm_RET+0x3d9
              oracle`pfrinstr_RET+0x26

^C
   1      2                             :END
---------Step 9 at 2017 Sep 25 08:14:17 (Elapsed-ns=2254749376)---------
Latch Gets = 4 (2226986430 - 2226986434)


7. RESULT_CACHE Object Status Transition


SQL RESULT_CACHE hint has an option of SNAPSHOT. which specifies the Expired time interval. In the following test, we set "snapshot=5" (5 seconds) to track status transition of RESULT_CACHE Object:
    New -> Published -> Expired -> Invalid

As the test shows, SNAPSHOT option turns off usual RESULT_CACHE invalidation of DML statement, and allows stale Read (dirty Read, non Consistency Read) of RESULT_CACHE.

We observed this usage in Oracle dynamic sampling (SNAPSHOT=3600). In real application, this undocumented feature can be used to make RESULT_CACHE as a database-wide provider of periodically sampling data.

----------------- Test Code -----------------
exec dbms_result_cache.flush;

select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3;
select status, name from v$result_cache_objects; 

exec dbms_lock.sleep(2);
update rc_tab set val = -val where id = 3;
commit;
select id, val from rc_tab where id = 3;     

--stale Read
select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3;
select status, name from v$result_cache_objects; 

exec dbms_lock.sleep(3);
select status, name from v$result_cache_objects; 

exec dbms_lock.sleep(1);
select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3;
select status, name from v$result_cache_objects;

----------------- Test Output -----------------

07:11:00 > exec dbms_result_cache.flush;
07:11:00 > select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3;
  
          ID        VAL
  ---------- ----------
           3         30
07:11:00 > select status, name from v$result_cache_objects;

  STATUS    NAME
  --------- -----------------------------------------------------------------------
  Published select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3

07:11:00 > exec dbms_lock.sleep(2);
07:11:02 > update rc_tab set val = -val where id = 3;
  1 row updated.
07:11:02 > commit;
07:11:02 > select id, val from rc_tab where id = 3;
  
          ID        VAL
  ---------- ----------
           3        -30
           
07:11:02 > --stale Read           
07:11:02 > select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3;
  
          ID        VAL
  ---------- ----------
           3         30
07:11:02 > select status, name from v$result_cache_objects;

  STATUS    NAME
  --------- -----------------------------------------------------------------------
  Published select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3

07:11:02 > exec dbms_lock.sleep(3);
07:11:05 > select status, name from v$result_cache_objects;

  STATUS    NAME
  --------- -----------------------------------------------------------------------
  Expired   select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3

07:11:05 > exec dbms_lock.sleep(1);
07:11:06 > select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3;
  
          ID        VAL
  ---------- ----------
           3        -30
07:11:06 > select status, name from v$result_cache_objects;

  STATUS    NAME
  --------- -----------------------------------------------------------------------
  Published select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3
  Invalid   select /*+ RESULT_CACHE(snapshot=5) */ id, val from rc_tab where id = 3
  2 rows selected.


8. Result Cache History Recording


There exist no DBA_HIST_* views to display the RC history of active sessions. We can create a small utility to record RC history usages (see appended code).

For example, we record 10 snaps in an interval of 1 second for top 5 rows, and then check the created history.

exec rc_scn_record_hist(p_snaps => 10, p_interval=> 1, p_rows => 5);

select * from hist_rc_scn 
order by ts, sign(extract(minute from(scn_diff))*60 + extract(second from(scn_diff))) desc
        ,scn desc, scn_diff desc, status, name, id;

select * from hist_rc_scn_dep 
order by ts, sign(extract(minute from(f_scn_diff))*60 + extract(second from(f_scn_diff))) desc
        ,f_scn desc, f_scn_diff desc, f_status, f_name;
        
select * from hist_rc_lru_number t
order by ts, name;   

select * from hist_rc_memory t
order by ts, free; 

select * from hist_rc_stats t
order by name, ts;   


9. Result Cache Hidden Parameters


Here a list of RC hidden parameters in Oracle 12.1.0.2.0:
Name                                       Description                                             Default 
------------------------------------------ ------------------------------------------------------- -------
_result_cache_auto_size_threshold          result cache auto max size allowed                      100       
_result_cache_auto_time_threshold          result cache auto time threshold                        1000 
_result_cache_auto_execution_threshold     result cache auto execution threshold                   1    
_result_cache_deterministic_plsql          result cache deterministic PLSQL functions              FALSE
_result_cache_block_size                   result cache block size                                 1024 
_result_cache_copy_block_count             blocks to copy instead of pinning the result            1    
_result_cache_global                       Are results available globally across RAC?              TRUE 
_result_cache_timeout                      maximum time (sec) a session waits for a result         10   
_result_cache_auto_time_distance           result cache auto time distance                         300  
_result_cache_auto_dml_monitoring_slots    result cache auto dml monitoring slot                   4    
_result_cache_auto_dml_monitoring_duration result cache auto dml monitoring duration               15   
_result_cache_auto_dml_threshold           result cache auto dml threshold                         16   
_result_cache_auto_dml_trend_threshold     result cache auto dml trend threshold                   20   
_optimizer_ads_result_cache_life           result cache shelf life for ADS queries                 3600 
_client_result_cache_bypass                bypass the client result cache                          FALSE
_re_result_cache_keysiz                    defines max number key for result cache hash table      20   
_re_result_cache_size                      defines max number of cached elements for result cache  20   
In verbatim text, they sound like something to regulate the refresh of Result Cache or its Dynamic Performance Views. But it is not clear how to make the test cases to detect their usage.

10. Test Code



drop table rc_tab;

drop table rc_tab_dep;

drop table rc_tab_2;

create table rc_tab (id number, val number);

create table rc_tab_dep (did number, dval number);

create table rc_tab_2 (id number, val number);

insert into rc_tab select level, level*10 from dual connect by level <= 17;

insert into rc_tab_dep select level, level*10 from dual connect by level <= 17;

insert into rc_tab_2 select level, level*10 from dual connect by level <= 13;

commit;

create or replace function get_val_dep (p_did number) return number as
  l_dval_dep number;
begin
  select dval into l_dval_dep from rc_tab_dep where did = p_did;
  return l_dval_dep;
end;
/

create or replace function get_val (p_id number) return number result_cache as
  l_val     number;
  l_val_dep number;
begin
  select val into l_val from rc_tab where id = p_id;
  l_val_dep := get_val_dep(p_id);
  return l_val + l_val_dep;
end;
/

create or replace procedure run_test as
  l_val number;
begin
  -- SQL RESULT_CACHE
  --for c in (select /*+ RESULT_CACHE */ * from rc_tab) loop
  --  null;
  --end loop;
  
  for i in 1 .. 17 loop
    l_val := get_val(i);
  end loop;
end;
/

create or replace function get_val_2 (p_id number) return number result_cache as
  l_val     number;
  l_val_dep number;
begin
  select val into l_val from rc_tab_2 where id = p_id;
  return l_val;
end;
/

create or replace procedure run_test_2 as
  l_val number;
begin
  for i in 1 .. 13 loop
    l_val := get_val_2(i);
  end loop;
end;
/


11. Result Cache Monitoring Views



drop view rc_scn_view;

create view rc_scn_view as
select systimestamp ts, scn_to_timestamp(t.scn) scn_time, (scn_to_timestamp(scn) - creation_timestamp) scn_diff, t.*  
from   v$result_cache_objects t
order by sign(extract(minute from(scn_diff))*60 + extract(second from(scn_diff))) desc
        ,scn desc, scn_diff desc, status, name, id;    

drop view rc_scn_dep_view;

create view rc_scn_dep_view as
with rs_scn as 
  (select scn_to_timestamp(t.scn) scn_time, (scn_to_timestamp(scn) - creation_timestamp) scn_diff, 
          id, type, status, name, namespace, creation_timestamp, scn, object_no, invalidations, row_count
   from   v$result_cache_objects t)
select systimestamp ts,
       f.scn_time f_scn_time, f.scn_diff f_scn_diff, f.id f_id, f.type f_type, f.status f_status, f.name f_name, f.namespace f_namespace, 
       f.creation_timestamp f_creation_timestamp, f.scn f_scn, f.object_no f_object_no, f.invalidations f_invalidations, f.row_count f_row_count,
       t.scn_time t_scn_time, t.scn_diff t_scn_diff, t.id t_id, t.type t_type, t.status t_status, t.name t_name, t.namespace t_namespace, 
       t.creation_timestamp t_creation_timestamp, t.scn t_scn, t.object_no t_object_no, t.invalidations  t_invalidations, t.row_count t_row_count
from rs_scn                    f, 
     v$result_cache_dependency d,
     rs_scn                    t
where f.id    = d.result_id(+) 
  and t.id(+) = d.depend_id
--  and t.name in ('K.RC_TAB_DEP', 'K.RC_TAB')
--  and f.name like '%"K"."%GET_VAL"%'
order by sign(extract(minute from(f.scn_diff))*60 + extract(second from(f.scn_diff))) desc
        ,f.scn desc, f.scn_diff desc, f.status, f.name;
        
drop view rc_lru_number_view;

create view rc_lru_number_view as
select systimestamp ts, name, cache_id, type, status, namespace
      ,min(lru_number) min_lru, max(lru_number) max_lru, max(lru_number) - min(lru_number) lru_diff
      ,count(*) rc_count, sum(block_count) blocks, sum(ceil(row_count*row_size_avg/1024)) est_blocks
      ,sum(row_count) row_cnt, min(row_size_min) row_size_min, max(row_size_max) row_size_max, round(avg(row_size_avg)) row_size_avg
      ,sum(pin_count) pin_count, round(avg(scan_count)) avg_scan_cnt
      ,sum(build_time*10) build_time_ms, round(avg(build_time*10), 3) avg_build_time, min(build_time*10) min_build_time, max(build_time*10) max_build_time
      ,sum(invalidations) invalidations
from   v$result_cache_objects t
where  lru_number > 0
group by name, cache_id, type, status, namespace
order by rc_count desc, avg_scan_cnt desc;  

drop view rc_memory_view;

create view rc_memory_view as 
select systimestamp ts, v.* from
(select free, count(*) cnt, min(offset) min_offset, max(offset) max_offset from v$result_cache_memory group by free) v;

drop view rc_stats_view;

create view rc_stats_view as 
select systimestamp ts, v.* from v$result_cache_statistics v;


12. Result Cache History Recording



drop table hist_rc_scn;

create table hist_rc_scn as select 123456 snap_id, v.* from rc_scn_view v where 1=2;

drop table hist_rc_scn_dep;

create table hist_rc_scn_dep as select 123456 snap_id, v.* from rc_scn_dep_view v where 1=2;

drop table hist_rc_lru_number;

create table hist_rc_lru_number as select 123456 snap_id, v.* from rc_lru_number_view v where 1=2;

drop table hist_rc_memory;

create table hist_rc_memory as select 123456 snap_id, v.* from rc_memory_view v where 1=2;

drop table hist_rc_stats;

create table hist_rc_stats as select 123456 snap_id, v.* from rc_stats_view v where 1=2;


create or replace procedure rc_scn_record_hist(p_snaps number := 10, p_interval number := 1, p_rows number := 10) as
begin
  for i in 1..p_snaps loop
    insert into hist_rc_scn   
    select i snap_id, c.* from  
    (select v.* from rc_scn_view v 
     order by sign(extract(minute from(scn_diff))*60 + extract(second from(scn_diff))) desc
             ,scn desc, scn_diff desc, status, name, id) c
    where rownum <= p_rows;
    
    insert into hist_rc_scn_dep 
    select i snap_id, c.* from 
    (select v.* from rc_scn_dep_view v
     order by sign(extract(minute from(f_scn_diff))*60 + extract(second from(f_scn_diff))) desc
             ,f_scn desc, f_scn_diff desc, f_status, f_name) c
    where rownum <= p_rows;
    
    insert into hist_rc_lru_number 
    select i snap_id, c.* from rc_lru_number_view c;    
    
    insert into hist_rc_memory 
    select i snap_id, c.* from rc_memory_view c;
        
    insert into hist_rc_stats 
    select i snap_id, c.* from rc_stats_view c;     
     
    commit;
    dbms_lock.sleep(p_interval);
  end loop;
end;
/

truncate table hist_rc_scn;
truncate table hist_rc_scn_dep;
truncate table hist_rc_lru_number;
truncate table hist_rc_memory;
truncate table hist_rc_stats;

exec rc_scn_record_hist(p_snaps => 10, p_interval=> 1, p_rows => 5);

select * from hist_rc_scn 
order by ts, sign(extract(minute from(scn_diff))*60 + extract(second from(scn_diff))) desc
        ,scn desc, scn_diff desc, status, name, id;

select * from hist_rc_scn_dep 
order by ts, sign(extract(minute from(f_scn_diff))*60 + extract(second from(f_scn_diff))) desc
        ,f_scn desc, f_scn_diff desc, f_status, f_name;
        
select * from hist_rc_lru_number t
order by ts, name;  

select * from hist_rc_memory t
order by ts, free; 

select * from hist_rc_stats t
order by name, ts; 

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