- 浏览: 92613 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
xtpgyaps:
楼主,问下,,怎么我重构了JakartaMultiPartRe ...
struts2 使用 jakarta 上传文件时commons fileupload的异常捕捉 -
netfork:
时间变化真快,楼主08年5月份发的贴,现在再看涉及到的源代码, ...
struts2 使用 jakarta 上传文件时commons fileupload的异常捕捉 -
harry:
不错很不错
Linda Rising:“你相信谁?”
Oracle JDBC内存管理--Oracle白皮书2009年8月
Oracle JDBC内存管理--Oracle白皮书2009年8月
原文:http://www.oracle.com/technetwork/database/enterprise-edition/memory.pdf
介绍
Oracle JDBC驱动程序可能会使用大量的内存。这是一种有意识的设计选择,在使用大量内存与提高性能之前做出权衡。在大多数情况下,对于大多数用户,这已被证明是一个不错的选择。一些用户已经经历了JDBC驱动程序使用的大量内存的问题。本白皮书正是写给这些用户。如果您的应用程序的性能表现是可接受的,那么你没有理由担心内存使用。如果您的应用程序并没有达到你所期望的性能,并且使用了比预期的更多的内存,那么请继续读下去。
7.3.4,8i中,和9i Oracle JDBC驱动程序使用差不多尽可能少的内存。不幸的是,这导致了不可接受的性能损失。在10g中,为了提高性能,驱动程序的体系结构被重新设计了。在该体系结构重建的最重要的变化之一是如何驱动程序使用的内存。开发团队做出了有针对性的决定,用内存换取性能。10g驱动程序因此比9i的驱动程序平均快约30%。当然,您所以没得的数据可能会有所不同。
由于内存价格相对比较便宜,伴随着10g驱动程序发布之后物理内存的大小也在显着地不断上涨,因此大多数用户都受益于(内存换性能的)性能改进。有一些用户,而且大部分是具有非常大规模应用的用户,已经看到了由于heap过大,垃圾收集器的trashing,甚至OutOfMemoryExceptions所引起的性能问题。在后续的版本中,开发团队一直在努力解决这些问题,通过改进驱动程序使用内存的方式,为用户提供额外的(内存使用)控制,以解决这些具体的问题。本白皮书介绍了驱动程序是如何使用内存的,应用程序特点如何具体地影响内存的使用,以及用户可以做些什么以更好地管理内存使用和提高应用程序性能。
注:在本文的其余部分的单词“驱动程序”本身是指10g及以后版本的Oracle JDBC驱动程序。其它情况会指明特定版本或被引用的版本。
它们都被用到哪里去了?
10g的Oracle JDBC驱动程序,具有比以前的版本更大、更复杂的类层次结构。这些类的对象存储了更多的信息,所以需要更多的内存。这确实增加了内存的使用,但并这并不是真正的问题所在。真正的问题是用来存储查询结果的buffer。每个语句(包括PreparedStatement和CallableStatement的)都持有两个缓冲区,一个byte[](字节数组)和一个char [](字符数组)。char[]用来保存所有字符类型的行数据,如:CHAR,VARCHAR2,NCHAR等,byte[]用来保存所有的其它类型的行数据。这些buffer在在SQL被解析的时候分配,一般也就是在第一次执行该Statement的时候。Statement会持有这两个buffer,直到它被关闭。
由于buffer是在SQL解析的时候被分配的,buffer的大小并不取决于查询返回的行数据的实际长度,而是行数据可能的最大的长度。在SQL解析时,每列的类型是已知的,从该信息中驱动程序可以计算存储每一列所需的内存的最大长度。驱动程序也有fetchSize属性,也就是每次fetch返回的行数。有了每列有大小和行数的大小,驱动程序可以由此计算出一次fetch所返回的数据最大绝对长度。这也就是所分配的buffer的大小。
某些大类型,如LONG和LONG RAW,由于太大而无法直接存于buffer中会采取另外一种不同的处理方式。如果查询结果包含一个LONG或LONG RAW,将fetchSize设置为1之后,所遇见的内存问题就会变得明朗很多了。这种类型的问题不在这里讨论了。
字符数据存储在char[] buffer中。Java中的每个字符占用两个字节。一个VARCHAR2(10)列将包含最多10个字符,也就是10个Java的字符,也就是每行20个字节。一个VARCHAR2(4000)列将占用每行8K字节。重要的其实是column的定义大小,而不是实际数据的大小。一个VARCHAR2(4000)但是只包含了NULL的列,仍然需要每行8K字节。buffer是在驱动程序看到的查询结果之前被分配的,因此驱动程序必须分配足够的内存,以应付最大可能的行大小。一个定义为VARCHAR2(4000)的列最多可包含4000个字符。Buffer必须大到足以容纳4000个字符分配,尽管实际的结果数据可能没有那么大。
BFILE,BLOB和CLOB会被存储为locator。Locator可高达4K字节,每个BFILE,BLOB和CLOB列的byte[]必须有至少每行4K字节。RAW列最多可以包含4K字节。其它类型的则需要很少的字节。一个合理的近似值是假设所有其它类型的列,每行占用22个字节。
范例
CREATE TABLE TAB (ID NUMBER(10), NAME VARCHAR2(40), DOB DATE)
ResultSet r = stmt.executeQuery(“SELECT * FROM TAB”);
当驱动器执行executeQuery方法,数据库将解析SQL。数据库会返回结果集将有三列:一个NUMBER(10),一个VARCHAR2(40),和一个DATE。第一列的需要(约)每行22个字节。第二列需要每行40个字符。第三列的需要(约)每行22个字节。因此,每行需要22 +(40 * 2)+ 22 = 124个字节。请记住,每个字符需要两个字节。fetchSize默认是10行,所以驱动程序将分配一个char[] 10 * 40 = 400个字符(800字节)和一个byte[] 10 *(22 + 22)= 440个字节,共1240字节。 1240字节不会导致什么内存问题。但有一些查询结果会比较更大一些。
在最坏的情况下,考虑一个返回255个VARCHAR(4000)列查询。每行每列需要8K字节。乘以255列,每行2040K字节也就是2MB。如果fetchSize设置为1000行,那么驱动程序将尝试分配一个2GB的char[]。这将是很糟糕的。
作为一个Java开发人员,读者无疑想到其它分配和使用内存的方式。请放心,Oracle的JDBC的开发团队也是能够想到。其它的方式都进行了测试,然后才选得这样一种方式。虽然还没有进入具体细节,但是确实有很多好的理由让我们选择这种方式。这个选择完全是为了驱动程序的性能。
管理buffer的大小
用户可以通过几种方法来管理这些buffer的大小。
1. 小心地定义table
2. 小心地编写查询代码
3. 小心地设置fetchSize的值
一个VARCHAR2(20)就够用的列被定义成VARCHAR2(4000)会导致非常大的区别。一个VARCHAR2(4000)列的要求每行8K字节。一个VARCHAR2(20)列每行只需要40个字节。如果列事实上从来没有超过20个字符,然后定义一个VARCHAR2(4000)列是在浪费驱动程序分配的buffer空间。
在只需要几列的情况下使用SELECT *,除了从buffer大小以外,也会非常大地影响性能,。它会需要更多时间来获取行的内容,将其转换,然后通过网络发送,再将其转换为Java表达。虽然返回了几十列,但只有少数是需要的,会导致驱动程序分配大量的buffer来存储那些不需要的结果。
控制内存使用的主要工具是fetchSize。虽然2MB也算比较大的,但是大多数Java环境分配这样大的buffer不会有任何问题。即使在最坏的情况下,如果fetchSize设置为1,255个VARCHAR(4000)列的结果也不会在大多数应用中产生问题。
解决内存使用问题的第一步是检查SQL。计算每个查询的一行数据的近似大小,然后看看fetchSize的大小。如果每行的大小是非常大,那考虑一下是否有可能获取较少的列或修改schema使得数据更加紧密以限制行大小。最后,设置fetchSize以保持buffer在一个合理的大小。什么是“合理的”取决于应用程序的具体情况。Oracle建议fetchSize不要超过100,虽然在某些情况下,更大一些的值也是合适的。就算会返回很多行,设置fetchSize为100了可能是不恰当的。
注: Oracle提供的方法OracleStatement.defineColumnType可用于减少过大的列定义的大小 。当调提供一个size参数时,size将覆盖该schema中列定义的大小。这允许你在无法自由修改schema的情况下,也可以一定程度上的解决问题。使用Thin驱动程序时,你可以只在有问题的列上调用defineColumnType。当使用OCI驱动程序时,你可以在具体的Statement上调用它,也可以为所有的Statement的所有列调用它。如果能调整schema,当然是更好的选择。
一条statement不算问题。除了像255个VARCHAR2(4000)列或的setFetchSize(100000)等病态的情况之外,一个单独的statement是不太可能导致内存使用的问题。在实践中,问题只出现在系统中有数百甚至数千Statement对象的情况下。一个非常大的系统,可能有几百个连接同时开启。每个连接可能有一个或两个statement同时打开。这样一个非常大的系统将在一台具有非常大的物理内存的计算机上运行。有了合理的配置,即使这样一个打开了数百个statement的非常大型的系统也是不太可能有严重的内存问题。是的,驱动程序虽然会使用大量的内存,但是内存就是要拿来拿来被用的。因此,在实践中,即使是非常大的系统,也依然可避免内存问题。
大型系统往往很多次地执行相同的SQL。出于性能方面的考虑,重用PreparedStatement而不是每次为每条SQL创建,将会非常有助于提高性能。如此之大的系统可以有很多PreparedStatement,每个特殊的SQL字符串都会有一个(或多个)。大多数大型系统使用一个模块化的框架,如WebLogic。框架内的独立组件创建他们所需要的PreparedStatement。这与PreparedStatements需要被重用想违背。为了解决这个问题需要,框架提供了statement cache。有了statement cache,一个连接很可能会在内存中保持一百个或更多的PreparedStatement。乘以数以百计的连接,你就可能真得会有潜在的内存问题了。
Oracle的JDBC驱动程序通过内置的驱动程序语句缓存,Implicit Statement Cache,来解决这个问题。(也有一个叫Explicit Statement Cache的缓存,这里不讨论)。Implicit Statement Cache对用户是透明的。用户代码调用prepareStatement就像创建一个新对象一样。如果驱动程序能从cache中取到可用的PrepareStatement,就会直接返回它。如果没有取到则创建一个新对象。用户代码无法从语义上区分一个新创建的和一个重复的PrepareStatement。
从性能角度来看,从缓存中取比新创建一个statement更快。缓存的statement的执行会快得多,因为驱动程序可以重用statement上一次执行的很多状态。
Implicit Statement Cache知道Oracle JDBC中PreparedStatement的内部结构。因此,它可以管理该结构,以获得最佳性能。特别是它可以管理的char[]和byte[] buffer。驱动程序的不同版本提供了不同的buffer管理方式,这是因为Oracle更好地理解了实际的应用对于buffer管理的需求。但是不管怎么,Oracle的JDBC驱动程序,只有当PreparedStatement返回到Implicit Statement Cache中的时候,才可以管理的这些char[]和byte[] buffer。如果没有关闭的PreparedStatement,那么驱动程序有没有办法知道该statement将不会被立即重用,所以不能做任何事情来管理buffer的使用。
Statement Batching和内存使用
行数据buffer不是Oracle的JDBC驱动程序创建的唯一的大buffer。它们还为发送到数据库的PreparedStatement参数创建很大的buffer。和写数据相比,应用程序通常读取更多的数据,并且每次写入的数据块都比较小。因此参数buffer,往往要远远高于该行数据buffer。然而,使用(不正确的使用)statement batching,也有可能迫使驱动程序创建大的buffer。
当应用程序调用PreparedStatement.setXXX设置参数值时,驱动程序需要存储这些值。这需要很少的内存;只是一个数组和对象类型(如String)的引用,long和double需要8个字节,其它类型需要4个字节。执行PreparedStatement时,驱动程序必须以SQL数据类型而不是Java类型来发送这些值到数据库。驱动程序创建一个byte[]和一个char[] buffer。参数的数据转换为SQL类型后会存储在这些buffer中。然后驱动程序将这些字节发送到网络上。由于驱动程序在分配buffer前已经知道数据的实际大小,所以它可以创建尽量小的buffer。而且如果一个语句多次执行,驱动程序会试图重用这些buffer。如果新的数据值需要一个更大的byte[]或char[]缓冲区,驱动程序会分配一个更大的buffer。只要有合适大小的内存,执行一条语句是不会产生内存不够用的问题的。但是使用statement batching,情况就不一样了。
JDBC标准和Oracle statement batching都在一个操作中执行一个语句多次。要做到这一点,驱动程序必须一次性发送PreparedStatement的所有execute的所有参数。这意味着驱动程序必须转换所有参数的数据到SQL类型并存入buffer。在批处理中一个batch中executes数量,batchSize,跟查询中的fetchSize差不多。虽然一个单独的语句的执行所需要的参数转换不可能会导致内存问题,可是非常大的batchSize可能就会引起问题。
在实践中,这是一个不常见的问题,并且只有在batchSize设置得非常不合理的大的时候(数万)才会产生。每隔几百行调用一次executeBatch应该就可以解决的问题。
特定版本的内存管理
本节介绍不同版本的Oracle JDBC驱动程序是如何管理buffer的以及用户如何调整驱动程序以获得最高的性能。
注:在讨论内存管理的细节的时候,忽略其实有两个buffer,byte[]和char[],会使得讨论方便一些。在本节下面的部分,“buffer”可能同时或者分别指代两个buffer,byte[]和char[]。
虽然Java的内存管理是相当不错的,但是大的buffer分配还是昂贵的。其实并不是实际malloc的成本。那其实是非常快的。相反,问题是Java语言的要求,所有这些buffer需求用零填充。所以不公要有一个很大的buffer被分配出来,它也必须要被零填充。零填充需要写buffer的每一个字节。现代的处理器处理由于有多级data caches,处理小buffer是没有什么问题的。零填充一个大的buffer超出了处理器的data cache的大小,是在内存中以内存的读写速度进行的,这大大低于处理器的最大运行速度。性能测试已多次表明,分配buffer,是驱动程序的一个巨大的性能上的瓶颈。这是一个比较纠结的地方,要平衡buffer分配与重用buffer所需的时间和内存成本。
Oracle数据库版本10g Oracle JDBC驱动程序
最初的10g的驱动程序使用了一个比较原始的内存管理方法。这个内存管理方法是为了获得最高的性能。当一个PreparedStatement是第一次执行,必要的byte[]和char[] buffer会被分配。就是这样。buffer只有在PreparedStatement本身被释放(freed)的时候才会被释放(freed)。Implicit Statement Cache不会为管理buffer做任何事情。所有Implicit Statement Cache中缓存的PreparedStatements的持有着其分配的byte[]和char[] buffer准备立即被重用。因此,针对调节此版本的驱动程序内存管理的唯一途径是通过设置setFetchSize,精心设计schema,并小心地编写SQL查询语句。最初的10g驱动器是相当快的,但可能有内存管理问题,包括OutOfMemoryException问题。
Oracle数据库版本10.2.0.4 Oracle JDBC驱动程序
10.2.0.4.0驱动程序添加了一个连接属性着手解决出现在初始10g驱动程序的内存管理问题。此连接属性采用一种一刀切的方式。如果设置,返回一个PreparedStatement到Implicit Statement Cache中的时候会释放其中的buffer。Statement是从cache中取出时,buffer会被重新分配。这个简单的方法大大地减少了内存使用,但是牺牲了巨大的性能成本。如上所述,分配buffer是昂贵的。
这个连接属性就是
oracle.jdbc.freeMemoryOnEnterImplicitCache
它的值是一个boolean字符串,“true”或“false”。如果设为“true”,当一个PreparedStatement返回cache时,buffer会被释放。如果设为“false”(默认值也是“false”),buffer被保留,和最初的10g驱动程序一样。该属性可以通过-D被设置为System property或作为调用getConnection方法时候的connection property。请注意,设置freeMemoryOnEnterImplicitCache不会导致参数值buffer被释放,它只会影响该行数据buffer。
Oracle数据库版本11.1.0.6.0 Oracle JDBC驱动程序
JDBC开发团队认识到,在10.2.0.4.0使用全有或全无的方式不太理想。11.1.0.6.0驱动程序提供了一个内存管理更为复杂的方法。此方法要实现两个目标,最大限度地减少未内存使用量和最小化分配buffer的成本。驱动程序在每个连接内部创建了一个buffer缓存(buffer cache)。当一个PreparedStatement返回到Implicit Statement Cache中的时候,它的buffer会被缓存到buffer缓存中。当一个PreparedStatement是从Implicit Statement Cache中取出时,buffer会也会同时从buffer缓存中取出。因此,Implicit Statement Cache中的PreparedStatement不再持有大的buffer,buffer会被多次重用而不是被多次创建。相比10g驱动程序,不管有没有freeMemoryOnEnterImplicitCache,都显著地提升了驱动程序的性能。
如开篇介绍中所指出的,buffer的大小可能会有很大的不同,从零到几十甚至上百MB。11.1.0.6.0的buffer缓存非常简单,事实证明有些太简单了。所有缓存的buffer大小都是相同的。由于buffer可以被任何Implicit Statement Cache中的PreparedStatement使用,所有buffer不得不足够大以满足buffer需求最大的PreparedStatement的需求。如果同时只会有一个statement被使用,那么只会有一个buffer,而且buffer是会被所有PreparedStatement使用的。对于一些或者是大部分statement来说,buffer可能都太大了,但buffer大小至少会正好与一个缓存中的statement需要的大小所匹配。如果PreparedStatement的重用的情况不是太偏颇,那么保持大的buffer并且重用它们将会获得相对比较好的性能,这是相对于只保持较小的buffer,并根据需要重新分配的较大的buffer来说的。如果同时打开多个statement,并且其中一个PreparedStatement的要求的buffer过大,这就会有一个潜在的内存问题。
考虑一个应用程序,其中一个PreparedStatement需要一个10MB的buffer,其余的需要较小的buffer。只要同时每个连接只有一个PreparedStatement在被使用,并且那个大的PrepareStatement经常被重用,那是没有什么问题的。每个statement在被使用的时候会分得这唯一的一个10MB的buffer,PreparedStatement返回Implicit Statement Cache的时候,buffer被返回buffer缓存。这唯一的10MB buffer只被创建一次,并且被多个PreparedStatements不断地重复使用。现在考虑这么一种情况,如果两个PreparedStatements的同时打开。两个都需要buffer。由于一个PreparedStatement可能被分配任何一个buffer,所以所有的buffer必须是大小相同的,也就是最大的长度大小。当两个PreparedStatement同时打开的时候,两个buffer必须都是10MB。打开第二个PreparedStatement的时候,即使需要的只是很小的一个buffer,依然需要创建一个10MB的buffer。如果三个statement同时打开,就要再创建第三个10MB的buffer。在一个大型系统中,当同时打开数百个连接和数据百个PreparedStatement的时候,给每一个PreparedStatement都分配一个最大长度的的buffer可能会导致内存使用过多。这是显而易见的,但开发团队并没有体会到这个问题有多大,并没在内部测试的时候也没发现这个问题。这个问题可以通过恰当地schema设计,小心地编写SQL,正确地设置fetchSize来适当缓解。
需要注意的是11.1.0.6.0驱动程序不支持freeMemoryOnEnterImplicitCache,当PreparedStatement返回cache的时候buffer总是会被释放的(released)。释放出来的buffer会被放入buffer缓存中。
Oracle数据库版本11.1.0.7.0 Oracle JDBC驱动程序
11.1.0.7.0驱动程序引入了一个连接属性以解决大buffer的问题。此属性限定了保存在buffer缓存中buffer的最大的长度大小。所有超大的buffer都会在PreparedStatement返回Implicit Statement Cache的时候被释放(freed),并且在PreparedStatement从cache中取出时再重新创建相应的buffer。如果大部分PreparedStatement都是需要适度大小的buffer,例如小于100KB,但有一些需要较大的buffer,那么设置该属性为110KB会使得小的buffer被高度重用而不用承担总是创建最大长度buffer的成本。设置此属性可以提高性能,甚至可以防止OutOfMemoryException问题。
这个连接属性就是
oracle.jdbc.maxCachedBufferSize
它的值是一个int字符串,例如“100000”。默认是Integer.MAX_VALUE。这是可以存储在buffer缓存中的buffer的最大长度大小。这个大小限制即使用于char[]又使用于byte[]的buffer。对于char[] buffer就是字符的数量,对于byte[] buffer就是字节的数量。它只是最大的buffer的大小,而不是一个预定义(predefined)的大小。如果maxCachedBufferSize设置为100KB,但最大的buffer大小不超过100KB只有50KB,那么buffer缓存中的buffer大小将是50KB。maxCachedBufferSize值的改变,只有将不同长度的char[]和byte[] buffer包括或者排除驱动程序内部的buffer缓存的时候,才会对性能有影响。大的值的改变,即使是几MB的变化,也可能没带来什么区别。类似的,变化1个大小也可能产生巨大的区别,如果是会导致一个PreparedStatement的buffer被包含或排除在buffer缓存中。此属性可以通过-D来设置System property或者通过的getConnection的时候的connection property。
如果您需要设置maxCachedBufferSize,那么开始估算需要的最大buffer的SQL查询的buffer大小。在这个过程中你也行会发现,通过调整这些查询的fetchSize大小才可以满足所需的性能需求。仔细考虑执行的频率和buffer的大小,选择一个合适的大小使得大多数语句可以使用缓存的buffer,并且buffer的大小足够小,足以使Java runtime可以支持大量的buffer,以尽量减少创建新的buffer的频率。
有些程序有大量的(相对于线程数来讲)空闲连接。应用可能需要连接到很多数据库中的任何一个,但是每次只会连接其中的一个。如果差不多每一个线程都使用一个连接去连接数据库,那么相对于线程数就会有很多的空闲连接。由于默认情况下buffer缓存是附属于每个连接的,空闲连接的结果就是buffer缓存中有很多不会被用到的buffer。这意味着没有必要地占用了更多的内存。虽然这是一种比较罕见的情况的,但并不是鲜为人知的。
这种情况的解决方法是设置连接属性
oracle.jdbc.useThreadLocalBufferCache
此属性的值是一个boolean字符串,“true”或“false”。默认值是“false”。当此属性设置为“true”,buffer缓存是存储在一个ThreadLocal中,而不是直接存储在连接中。如果有比连接较少的线程,那么这将减少内存的使用量。该属性可以通过-D设置System property或者通过的调用getConnection时的connection property。
所有连接useThreadLocalBufferCache =“true”共享同一个静态的ThreadLocal字段,从而使用同一组buffer缓存。useThreadLocalBufferCache =“true”的所有连接都必须为maxCachedBufferSize设置相同的值。如果一个线程首先使用一个连接,然后又使用另一个连接,两个连接将由于使用的buffer的数量和大小相互有一些间接的影响。通常情况下,所有使用ThreadLocal的buffer缓存的连接都属于同一应用,所以这不太会有什么问题。如果一个线程创建了一个statement,另一个线程关闭了该statement,buffer会从一个ThreadLocal的buffer缓存迁移到另一个。这是不推荐的做法。如果所有的statement都由一个线程创建被另一个线程关闭,那么就不会有buffer被重用。所以,这是不推荐的,但如果你的应用就是这么干的,那就不设置useThreadLocalBufferCache为“true”。让部分连接使用ThreadLocal buffer缓存,部分使用默认的连接内部buffer缓存,这也是可以实现的。
Oracle数据库版本11.2 Oracle JDBC驱动程序
11.2驱动程序比11.1.0.7.0具有更复杂的buffer缓存。这个buffer缓存于多个bucket中。一个bucket的所有buffer都是相同的大小而且这个大小是预先确定的.当第一次执行一个PreparedStatement的时候,驱动程序从一个buffer大小大于结果长度的但是又是buffer大小最小的buffer bucket中取出一个buffer(翻译的有点绕了原文如下When a PreparedStatement is executed for the first time the driver gets a buffer from the bucket holding the smallest size buffers that will hold the result.)。如果bucket中没有buffer,驱动程序根据bucket的预定义大小创建一个新的相应大小的buffer。当一个PreparedStatement被关闭,buffer被返回到相应的bucket中。由于buffer被用于不定大小的需求,buffer通常只比最小的(翻译注:应该是“最大的”吧?)需求大一些。这种差异是有限的,在实践中没有任何影响。
相比11.1或10.2.0.4.0驱动程序,11.2驱动程序始终会保持使用相同或更少的内存。有时候一种不正确的说法是11.2驱动程序可以在小得性能都不可接受的heap中运行。仅仅因为它可以运行在更少的内存中,并不意味着它应该就这样部署。为11.2驱动程序设置一个较大的-Xms值来大副地提高性能,这并不是一种罕见行为。请参阅下面的控制Java Heap。
11.2驱动程序支持maxCachedBufferSize,但它明显不再那么重要。在11.1中,正确地设置maxCachedBufferSize可能就会从OutOfMemoryException的境地变为性能优越。在11.2的驱动程序中,maxCachedBufferSize的设置有时可以改善那些非常大型、拥有大量statement缓存、和大量对于buffer大小需求明显不同的SQL的大型系统的性能。在11.2的maxCachedBufferSize值被解释为buffer的最大大小是该值以2为底的对数。例如,如果maxCachedBufferSize设置为20的缓冲区缓存的最大大小为2^20 = 1048576。为了向后兼容,大于30的值被解释为实际大小,而不是log2的大小,但建议2的对数方式设定此值。
在通常情况下,将maxCachedBufferSize设置一个合理的值是没有任何影响的。如果您需要设置maxCachedBufferSize,从18开始尝试。如果你设置的值不得不小于16,那你可能需要更多的内存。
在11.2驱动程序中,参数数据buffer使用的是相同的buffer缓存和缓存方案。当一个PreparedStatement被放入Implicit Statement Cache中时,参数数据buffer会被buffer缓存缓存起来。当一个PreparedStatement第一次被执行或第一次从Implicit Statement Cache中取出后执行,它就会从buffer缓存获取参数数据buffer。通常情况下,参数数据buffer的大小远远小于于返回的行数据buffer的大小,但是这些buffer也可能由于batch size过大而变得很大。11.2驱动程序也为其它大型字段操作如Bfile、Blob和Clob提供了大的byte[]和char[] buffer的buffer缓存。
11.2驱动程序也支持使用useThreadLocalBufferCache。它的功能和关于何时以及如何使用它的建议与11.1.0.7.0中相同。
11.2驱动程序还添加了一个新的属性,以控制Implicit Statement Cache。
oracle.jdbc.implicitStatementCacheSize
这个属性的值是一个整型字符串,如“100”它是statement cache的初始大小。将该属性设置为正值,即是开启Implicit Statement Cache。默认值为‘0’。该属性可以通过-D设置为Sytstem property或通过调用getConnection时的connection property设置。调用OracleConnection.setStatementCacheSize和/或OracleConnection.setImplicitCachingEnabled将覆盖implicitStatementCacheSize的值。此属性可以使无法在代码中启用Implicit Statement Cache的情况下更方便地启动此功能。
控制Java Heap
优化Java运行时内存使用是一个有一点黑盒的艺术。最重要的两个选项是-Xmx和-Xms。根据Java运行时版本和操作系统的不同,还有一些其它的参数。-Xmx设置Java运行时可以使用的最大的heap的大小。 -Xms刚设置初始heap的大小。默认值取决于操作系统和Java运行时版本,但一般的-Xmx的默认值是64MB,-Xms的默认值是1MB。 32位的JVM支持高达2GB的heap。 64位JVM支持更大的heap。这些参数接受“k”,“m”,和“g”作为后缀以表明是kilo-、mega-还是giga-个字节,例如,-Xmx1g。
当有足够的heap大小时,Oracle的JDBC驱动程序可以提供最佳的性能。对于大多数应用,增加heap的大小达到一个下限之后,应用的性能会大幅提升。大小超出该下限之后,将没有什么区别。如果heap的大小是如此之大以至于超出机器上运行的物理内存,heap内存将会被换出到二级存储,性能会受到严重的影响。设置heap大小时,只设置-Xmx是不够的。11.2驱动程序,特别地注重使用尽量少的内存,它一般不会使heap上涨超出其可以运行的最低水平。如果你只设置最大heap大小,-Xmx,驱动程序可能永远不会实际使用到那么大的内存。如果你还通过-Xms增加最小heap的大小,驱动程序将会可以使用更多的内存,并住住能提供更好的性能。做一个固定的heap大小是的性能测试是很重要的,即将-Xmx和-Xms设置成相同的值。一般生产环境中的应用也是运行在固定的heap大小中的。设置-server选项会使JVM减小最小化heap大小的努力,所以会提供一定的性能改进。适当地设置-Xms往往会比单独设置-server选项提供更多的性能改进。Oracle建议为服务器应用设置-server,-Xms,和-Xmx选项。通常-Xms和-Xmx应设置为相同的值。
结论
Oracle的JDBC驱动程序可能会使用大量的内存以达到最大的性能优化。如果内存制约了应用程序的性能,你可以调整它以获得最大的性能优化。理想的情况下,你应该有一个可以用来场景重现的测试案例,以模拟实现的应用的工作负载。通过系统地改变各种参数并运行测试,你可以找出最优化的设置组合。对于所有版本的Oracle JDBC驱动程序,启用implicit statement cache,小心地设计数据库schema,仔细地编写SQL查询,并适当设置fetchSize都首先要做的。下一步是增加Java堆大小设置-Xmx和-Xms以达到最大的实际可用大小。如果这样达到可接受的性能,你可以尝试减少-Xmx和-Xms,如果一个小一些的heap能提供更多的好处的话。如果最大的实际可用内存的性能还是比预期的要差,你应该尝试适当地设置freeMemoryOnEnterImplictCache或maxCachedBufferSize。
应该明确你的应用的特点是否适合启用useThreadLocalBufferCache。如果您需要将性能优化到极致,尝试调整Java的垃圾收集器。 (见http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html)在大多数情况下,设置-Xmx和-Xms就足够了。
原文:http://www.oracle.com/technetwork/database/enterprise-edition/memory.pdf
介绍
Oracle JDBC驱动程序可能会使用大量的内存。这是一种有意识的设计选择,在使用大量内存与提高性能之前做出权衡。在大多数情况下,对于大多数用户,这已被证明是一个不错的选择。一些用户已经经历了JDBC驱动程序使用的大量内存的问题。本白皮书正是写给这些用户。如果您的应用程序的性能表现是可接受的,那么你没有理由担心内存使用。如果您的应用程序并没有达到你所期望的性能,并且使用了比预期的更多的内存,那么请继续读下去。
7.3.4,8i中,和9i Oracle JDBC驱动程序使用差不多尽可能少的内存。不幸的是,这导致了不可接受的性能损失。在10g中,为了提高性能,驱动程序的体系结构被重新设计了。在该体系结构重建的最重要的变化之一是如何驱动程序使用的内存。开发团队做出了有针对性的决定,用内存换取性能。10g驱动程序因此比9i的驱动程序平均快约30%。当然,您所以没得的数据可能会有所不同。
由于内存价格相对比较便宜,伴随着10g驱动程序发布之后物理内存的大小也在显着地不断上涨,因此大多数用户都受益于(内存换性能的)性能改进。有一些用户,而且大部分是具有非常大规模应用的用户,已经看到了由于heap过大,垃圾收集器的trashing,甚至OutOfMemoryExceptions所引起的性能问题。在后续的版本中,开发团队一直在努力解决这些问题,通过改进驱动程序使用内存的方式,为用户提供额外的(内存使用)控制,以解决这些具体的问题。本白皮书介绍了驱动程序是如何使用内存的,应用程序特点如何具体地影响内存的使用,以及用户可以做些什么以更好地管理内存使用和提高应用程序性能。
注:在本文的其余部分的单词“驱动程序”本身是指10g及以后版本的Oracle JDBC驱动程序。其它情况会指明特定版本或被引用的版本。
它们都被用到哪里去了?
10g的Oracle JDBC驱动程序,具有比以前的版本更大、更复杂的类层次结构。这些类的对象存储了更多的信息,所以需要更多的内存。这确实增加了内存的使用,但并这并不是真正的问题所在。真正的问题是用来存储查询结果的buffer。每个语句(包括PreparedStatement和CallableStatement的)都持有两个缓冲区,一个byte[](字节数组)和一个char [](字符数组)。char[]用来保存所有字符类型的行数据,如:CHAR,VARCHAR2,NCHAR等,byte[]用来保存所有的其它类型的行数据。这些buffer在在SQL被解析的时候分配,一般也就是在第一次执行该Statement的时候。Statement会持有这两个buffer,直到它被关闭。
由于buffer是在SQL解析的时候被分配的,buffer的大小并不取决于查询返回的行数据的实际长度,而是行数据可能的最大的长度。在SQL解析时,每列的类型是已知的,从该信息中驱动程序可以计算存储每一列所需的内存的最大长度。驱动程序也有fetchSize属性,也就是每次fetch返回的行数。有了每列有大小和行数的大小,驱动程序可以由此计算出一次fetch所返回的数据最大绝对长度。这也就是所分配的buffer的大小。
某些大类型,如LONG和LONG RAW,由于太大而无法直接存于buffer中会采取另外一种不同的处理方式。如果查询结果包含一个LONG或LONG RAW,将fetchSize设置为1之后,所遇见的内存问题就会变得明朗很多了。这种类型的问题不在这里讨论了。
字符数据存储在char[] buffer中。Java中的每个字符占用两个字节。一个VARCHAR2(10)列将包含最多10个字符,也就是10个Java的字符,也就是每行20个字节。一个VARCHAR2(4000)列将占用每行8K字节。重要的其实是column的定义大小,而不是实际数据的大小。一个VARCHAR2(4000)但是只包含了NULL的列,仍然需要每行8K字节。buffer是在驱动程序看到的查询结果之前被分配的,因此驱动程序必须分配足够的内存,以应付最大可能的行大小。一个定义为VARCHAR2(4000)的列最多可包含4000个字符。Buffer必须大到足以容纳4000个字符分配,尽管实际的结果数据可能没有那么大。
BFILE,BLOB和CLOB会被存储为locator。Locator可高达4K字节,每个BFILE,BLOB和CLOB列的byte[]必须有至少每行4K字节。RAW列最多可以包含4K字节。其它类型的则需要很少的字节。一个合理的近似值是假设所有其它类型的列,每行占用22个字节。
范例
CREATE TABLE TAB (ID NUMBER(10), NAME VARCHAR2(40), DOB DATE)
ResultSet r = stmt.executeQuery(“SELECT * FROM TAB”);
当驱动器执行executeQuery方法,数据库将解析SQL。数据库会返回结果集将有三列:一个NUMBER(10),一个VARCHAR2(40),和一个DATE。第一列的需要(约)每行22个字节。第二列需要每行40个字符。第三列的需要(约)每行22个字节。因此,每行需要22 +(40 * 2)+ 22 = 124个字节。请记住,每个字符需要两个字节。fetchSize默认是10行,所以驱动程序将分配一个char[] 10 * 40 = 400个字符(800字节)和一个byte[] 10 *(22 + 22)= 440个字节,共1240字节。 1240字节不会导致什么内存问题。但有一些查询结果会比较更大一些。
在最坏的情况下,考虑一个返回255个VARCHAR(4000)列查询。每行每列需要8K字节。乘以255列,每行2040K字节也就是2MB。如果fetchSize设置为1000行,那么驱动程序将尝试分配一个2GB的char[]。这将是很糟糕的。
作为一个Java开发人员,读者无疑想到其它分配和使用内存的方式。请放心,Oracle的JDBC的开发团队也是能够想到。其它的方式都进行了测试,然后才选得这样一种方式。虽然还没有进入具体细节,但是确实有很多好的理由让我们选择这种方式。这个选择完全是为了驱动程序的性能。
管理buffer的大小
用户可以通过几种方法来管理这些buffer的大小。
1. 小心地定义table
2. 小心地编写查询代码
3. 小心地设置fetchSize的值
一个VARCHAR2(20)就够用的列被定义成VARCHAR2(4000)会导致非常大的区别。一个VARCHAR2(4000)列的要求每行8K字节。一个VARCHAR2(20)列每行只需要40个字节。如果列事实上从来没有超过20个字符,然后定义一个VARCHAR2(4000)列是在浪费驱动程序分配的buffer空间。
在只需要几列的情况下使用SELECT *,除了从buffer大小以外,也会非常大地影响性能,。它会需要更多时间来获取行的内容,将其转换,然后通过网络发送,再将其转换为Java表达。虽然返回了几十列,但只有少数是需要的,会导致驱动程序分配大量的buffer来存储那些不需要的结果。
控制内存使用的主要工具是fetchSize。虽然2MB也算比较大的,但是大多数Java环境分配这样大的buffer不会有任何问题。即使在最坏的情况下,如果fetchSize设置为1,255个VARCHAR(4000)列的结果也不会在大多数应用中产生问题。
解决内存使用问题的第一步是检查SQL。计算每个查询的一行数据的近似大小,然后看看fetchSize的大小。如果每行的大小是非常大,那考虑一下是否有可能获取较少的列或修改schema使得数据更加紧密以限制行大小。最后,设置fetchSize以保持buffer在一个合理的大小。什么是“合理的”取决于应用程序的具体情况。Oracle建议fetchSize不要超过100,虽然在某些情况下,更大一些的值也是合适的。就算会返回很多行,设置fetchSize为100了可能是不恰当的。
注: Oracle提供的方法OracleStatement.defineColumnType可用于减少过大的列定义的大小 。当调提供一个size参数时,size将覆盖该schema中列定义的大小。这允许你在无法自由修改schema的情况下,也可以一定程度上的解决问题。使用Thin驱动程序时,你可以只在有问题的列上调用defineColumnType。当使用OCI驱动程序时,你可以在具体的Statement上调用它,也可以为所有的Statement的所有列调用它。如果能调整schema,当然是更好的选择。
一条statement不算问题。除了像255个VARCHAR2(4000)列或的setFetchSize(100000)等病态的情况之外,一个单独的statement是不太可能导致内存使用的问题。在实践中,问题只出现在系统中有数百甚至数千Statement对象的情况下。一个非常大的系统,可能有几百个连接同时开启。每个连接可能有一个或两个statement同时打开。这样一个非常大的系统将在一台具有非常大的物理内存的计算机上运行。有了合理的配置,即使这样一个打开了数百个statement的非常大型的系统也是不太可能有严重的内存问题。是的,驱动程序虽然会使用大量的内存,但是内存就是要拿来拿来被用的。因此,在实践中,即使是非常大的系统,也依然可避免内存问题。
大型系统往往很多次地执行相同的SQL。出于性能方面的考虑,重用PreparedStatement而不是每次为每条SQL创建,将会非常有助于提高性能。如此之大的系统可以有很多PreparedStatement,每个特殊的SQL字符串都会有一个(或多个)。大多数大型系统使用一个模块化的框架,如WebLogic。框架内的独立组件创建他们所需要的PreparedStatement。这与PreparedStatements需要被重用想违背。为了解决这个问题需要,框架提供了statement cache。有了statement cache,一个连接很可能会在内存中保持一百个或更多的PreparedStatement。乘以数以百计的连接,你就可能真得会有潜在的内存问题了。
Oracle的JDBC驱动程序通过内置的驱动程序语句缓存,Implicit Statement Cache,来解决这个问题。(也有一个叫Explicit Statement Cache的缓存,这里不讨论)。Implicit Statement Cache对用户是透明的。用户代码调用prepareStatement就像创建一个新对象一样。如果驱动程序能从cache中取到可用的PrepareStatement,就会直接返回它。如果没有取到则创建一个新对象。用户代码无法从语义上区分一个新创建的和一个重复的PrepareStatement。
从性能角度来看,从缓存中取比新创建一个statement更快。缓存的statement的执行会快得多,因为驱动程序可以重用statement上一次执行的很多状态。
Implicit Statement Cache知道Oracle JDBC中PreparedStatement的内部结构。因此,它可以管理该结构,以获得最佳性能。特别是它可以管理的char[]和byte[] buffer。驱动程序的不同版本提供了不同的buffer管理方式,这是因为Oracle更好地理解了实际的应用对于buffer管理的需求。但是不管怎么,Oracle的JDBC驱动程序,只有当PreparedStatement返回到Implicit Statement Cache中的时候,才可以管理的这些char[]和byte[] buffer。如果没有关闭的PreparedStatement,那么驱动程序有没有办法知道该statement将不会被立即重用,所以不能做任何事情来管理buffer的使用。
Statement Batching和内存使用
行数据buffer不是Oracle的JDBC驱动程序创建的唯一的大buffer。它们还为发送到数据库的PreparedStatement参数创建很大的buffer。和写数据相比,应用程序通常读取更多的数据,并且每次写入的数据块都比较小。因此参数buffer,往往要远远高于该行数据buffer。然而,使用(不正确的使用)statement batching,也有可能迫使驱动程序创建大的buffer。
当应用程序调用PreparedStatement.setXXX设置参数值时,驱动程序需要存储这些值。这需要很少的内存;只是一个数组和对象类型(如String)的引用,long和double需要8个字节,其它类型需要4个字节。执行PreparedStatement时,驱动程序必须以SQL数据类型而不是Java类型来发送这些值到数据库。驱动程序创建一个byte[]和一个char[] buffer。参数的数据转换为SQL类型后会存储在这些buffer中。然后驱动程序将这些字节发送到网络上。由于驱动程序在分配buffer前已经知道数据的实际大小,所以它可以创建尽量小的buffer。而且如果一个语句多次执行,驱动程序会试图重用这些buffer。如果新的数据值需要一个更大的byte[]或char[]缓冲区,驱动程序会分配一个更大的buffer。只要有合适大小的内存,执行一条语句是不会产生内存不够用的问题的。但是使用statement batching,情况就不一样了。
JDBC标准和Oracle statement batching都在一个操作中执行一个语句多次。要做到这一点,驱动程序必须一次性发送PreparedStatement的所有execute的所有参数。这意味着驱动程序必须转换所有参数的数据到SQL类型并存入buffer。在批处理中一个batch中executes数量,batchSize,跟查询中的fetchSize差不多。虽然一个单独的语句的执行所需要的参数转换不可能会导致内存问题,可是非常大的batchSize可能就会引起问题。
在实践中,这是一个不常见的问题,并且只有在batchSize设置得非常不合理的大的时候(数万)才会产生。每隔几百行调用一次executeBatch应该就可以解决的问题。
特定版本的内存管理
本节介绍不同版本的Oracle JDBC驱动程序是如何管理buffer的以及用户如何调整驱动程序以获得最高的性能。
注:在讨论内存管理的细节的时候,忽略其实有两个buffer,byte[]和char[],会使得讨论方便一些。在本节下面的部分,“buffer”可能同时或者分别指代两个buffer,byte[]和char[]。
虽然Java的内存管理是相当不错的,但是大的buffer分配还是昂贵的。其实并不是实际malloc的成本。那其实是非常快的。相反,问题是Java语言的要求,所有这些buffer需求用零填充。所以不公要有一个很大的buffer被分配出来,它也必须要被零填充。零填充需要写buffer的每一个字节。现代的处理器处理由于有多级data caches,处理小buffer是没有什么问题的。零填充一个大的buffer超出了处理器的data cache的大小,是在内存中以内存的读写速度进行的,这大大低于处理器的最大运行速度。性能测试已多次表明,分配buffer,是驱动程序的一个巨大的性能上的瓶颈。这是一个比较纠结的地方,要平衡buffer分配与重用buffer所需的时间和内存成本。
Oracle数据库版本10g Oracle JDBC驱动程序
最初的10g的驱动程序使用了一个比较原始的内存管理方法。这个内存管理方法是为了获得最高的性能。当一个PreparedStatement是第一次执行,必要的byte[]和char[] buffer会被分配。就是这样。buffer只有在PreparedStatement本身被释放(freed)的时候才会被释放(freed)。Implicit Statement Cache不会为管理buffer做任何事情。所有Implicit Statement Cache中缓存的PreparedStatements的持有着其分配的byte[]和char[] buffer准备立即被重用。因此,针对调节此版本的驱动程序内存管理的唯一途径是通过设置setFetchSize,精心设计schema,并小心地编写SQL查询语句。最初的10g驱动器是相当快的,但可能有内存管理问题,包括OutOfMemoryException问题。
Oracle数据库版本10.2.0.4 Oracle JDBC驱动程序
10.2.0.4.0驱动程序添加了一个连接属性着手解决出现在初始10g驱动程序的内存管理问题。此连接属性采用一种一刀切的方式。如果设置,返回一个PreparedStatement到Implicit Statement Cache中的时候会释放其中的buffer。Statement是从cache中取出时,buffer会被重新分配。这个简单的方法大大地减少了内存使用,但是牺牲了巨大的性能成本。如上所述,分配buffer是昂贵的。
这个连接属性就是
oracle.jdbc.freeMemoryOnEnterImplicitCache
它的值是一个boolean字符串,“true”或“false”。如果设为“true”,当一个PreparedStatement返回cache时,buffer会被释放。如果设为“false”(默认值也是“false”),buffer被保留,和最初的10g驱动程序一样。该属性可以通过-D被设置为System property或作为调用getConnection方法时候的connection property。请注意,设置freeMemoryOnEnterImplicitCache不会导致参数值buffer被释放,它只会影响该行数据buffer。
Oracle数据库版本11.1.0.6.0 Oracle JDBC驱动程序
JDBC开发团队认识到,在10.2.0.4.0使用全有或全无的方式不太理想。11.1.0.6.0驱动程序提供了一个内存管理更为复杂的方法。此方法要实现两个目标,最大限度地减少未内存使用量和最小化分配buffer的成本。驱动程序在每个连接内部创建了一个buffer缓存(buffer cache)。当一个PreparedStatement返回到Implicit Statement Cache中的时候,它的buffer会被缓存到buffer缓存中。当一个PreparedStatement是从Implicit Statement Cache中取出时,buffer会也会同时从buffer缓存中取出。因此,Implicit Statement Cache中的PreparedStatement不再持有大的buffer,buffer会被多次重用而不是被多次创建。相比10g驱动程序,不管有没有freeMemoryOnEnterImplicitCache,都显著地提升了驱动程序的性能。
如开篇介绍中所指出的,buffer的大小可能会有很大的不同,从零到几十甚至上百MB。11.1.0.6.0的buffer缓存非常简单,事实证明有些太简单了。所有缓存的buffer大小都是相同的。由于buffer可以被任何Implicit Statement Cache中的PreparedStatement使用,所有buffer不得不足够大以满足buffer需求最大的PreparedStatement的需求。如果同时只会有一个statement被使用,那么只会有一个buffer,而且buffer是会被所有PreparedStatement使用的。对于一些或者是大部分statement来说,buffer可能都太大了,但buffer大小至少会正好与一个缓存中的statement需要的大小所匹配。如果PreparedStatement的重用的情况不是太偏颇,那么保持大的buffer并且重用它们将会获得相对比较好的性能,这是相对于只保持较小的buffer,并根据需要重新分配的较大的buffer来说的。如果同时打开多个statement,并且其中一个PreparedStatement的要求的buffer过大,这就会有一个潜在的内存问题。
考虑一个应用程序,其中一个PreparedStatement需要一个10MB的buffer,其余的需要较小的buffer。只要同时每个连接只有一个PreparedStatement在被使用,并且那个大的PrepareStatement经常被重用,那是没有什么问题的。每个statement在被使用的时候会分得这唯一的一个10MB的buffer,PreparedStatement返回Implicit Statement Cache的时候,buffer被返回buffer缓存。这唯一的10MB buffer只被创建一次,并且被多个PreparedStatements不断地重复使用。现在考虑这么一种情况,如果两个PreparedStatements的同时打开。两个都需要buffer。由于一个PreparedStatement可能被分配任何一个buffer,所以所有的buffer必须是大小相同的,也就是最大的长度大小。当两个PreparedStatement同时打开的时候,两个buffer必须都是10MB。打开第二个PreparedStatement的时候,即使需要的只是很小的一个buffer,依然需要创建一个10MB的buffer。如果三个statement同时打开,就要再创建第三个10MB的buffer。在一个大型系统中,当同时打开数百个连接和数据百个PreparedStatement的时候,给每一个PreparedStatement都分配一个最大长度的的buffer可能会导致内存使用过多。这是显而易见的,但开发团队并没有体会到这个问题有多大,并没在内部测试的时候也没发现这个问题。这个问题可以通过恰当地schema设计,小心地编写SQL,正确地设置fetchSize来适当缓解。
需要注意的是11.1.0.6.0驱动程序不支持freeMemoryOnEnterImplicitCache,当PreparedStatement返回cache的时候buffer总是会被释放的(released)。释放出来的buffer会被放入buffer缓存中。
Oracle数据库版本11.1.0.7.0 Oracle JDBC驱动程序
11.1.0.7.0驱动程序引入了一个连接属性以解决大buffer的问题。此属性限定了保存在buffer缓存中buffer的最大的长度大小。所有超大的buffer都会在PreparedStatement返回Implicit Statement Cache的时候被释放(freed),并且在PreparedStatement从cache中取出时再重新创建相应的buffer。如果大部分PreparedStatement都是需要适度大小的buffer,例如小于100KB,但有一些需要较大的buffer,那么设置该属性为110KB会使得小的buffer被高度重用而不用承担总是创建最大长度buffer的成本。设置此属性可以提高性能,甚至可以防止OutOfMemoryException问题。
这个连接属性就是
oracle.jdbc.maxCachedBufferSize
它的值是一个int字符串,例如“100000”。默认是Integer.MAX_VALUE。这是可以存储在buffer缓存中的buffer的最大长度大小。这个大小限制即使用于char[]又使用于byte[]的buffer。对于char[] buffer就是字符的数量,对于byte[] buffer就是字节的数量。它只是最大的buffer的大小,而不是一个预定义(predefined)的大小。如果maxCachedBufferSize设置为100KB,但最大的buffer大小不超过100KB只有50KB,那么buffer缓存中的buffer大小将是50KB。maxCachedBufferSize值的改变,只有将不同长度的char[]和byte[] buffer包括或者排除驱动程序内部的buffer缓存的时候,才会对性能有影响。大的值的改变,即使是几MB的变化,也可能没带来什么区别。类似的,变化1个大小也可能产生巨大的区别,如果是会导致一个PreparedStatement的buffer被包含或排除在buffer缓存中。此属性可以通过-D来设置System property或者通过的getConnection的时候的connection property。
如果您需要设置maxCachedBufferSize,那么开始估算需要的最大buffer的SQL查询的buffer大小。在这个过程中你也行会发现,通过调整这些查询的fetchSize大小才可以满足所需的性能需求。仔细考虑执行的频率和buffer的大小,选择一个合适的大小使得大多数语句可以使用缓存的buffer,并且buffer的大小足够小,足以使Java runtime可以支持大量的buffer,以尽量减少创建新的buffer的频率。
有些程序有大量的(相对于线程数来讲)空闲连接。应用可能需要连接到很多数据库中的任何一个,但是每次只会连接其中的一个。如果差不多每一个线程都使用一个连接去连接数据库,那么相对于线程数就会有很多的空闲连接。由于默认情况下buffer缓存是附属于每个连接的,空闲连接的结果就是buffer缓存中有很多不会被用到的buffer。这意味着没有必要地占用了更多的内存。虽然这是一种比较罕见的情况的,但并不是鲜为人知的。
这种情况的解决方法是设置连接属性
oracle.jdbc.useThreadLocalBufferCache
此属性的值是一个boolean字符串,“true”或“false”。默认值是“false”。当此属性设置为“true”,buffer缓存是存储在一个ThreadLocal中,而不是直接存储在连接中。如果有比连接较少的线程,那么这将减少内存的使用量。该属性可以通过-D设置System property或者通过的调用getConnection时的connection property。
所有连接useThreadLocalBufferCache =“true”共享同一个静态的ThreadLocal字段,从而使用同一组buffer缓存。useThreadLocalBufferCache =“true”的所有连接都必须为maxCachedBufferSize设置相同的值。如果一个线程首先使用一个连接,然后又使用另一个连接,两个连接将由于使用的buffer的数量和大小相互有一些间接的影响。通常情况下,所有使用ThreadLocal的buffer缓存的连接都属于同一应用,所以这不太会有什么问题。如果一个线程创建了一个statement,另一个线程关闭了该statement,buffer会从一个ThreadLocal的buffer缓存迁移到另一个。这是不推荐的做法。如果所有的statement都由一个线程创建被另一个线程关闭,那么就不会有buffer被重用。所以,这是不推荐的,但如果你的应用就是这么干的,那就不设置useThreadLocalBufferCache为“true”。让部分连接使用ThreadLocal buffer缓存,部分使用默认的连接内部buffer缓存,这也是可以实现的。
Oracle数据库版本11.2 Oracle JDBC驱动程序
11.2驱动程序比11.1.0.7.0具有更复杂的buffer缓存。这个buffer缓存于多个bucket中。一个bucket的所有buffer都是相同的大小而且这个大小是预先确定的.当第一次执行一个PreparedStatement的时候,驱动程序从一个buffer大小大于结果长度的但是又是buffer大小最小的buffer bucket中取出一个buffer(翻译的有点绕了原文如下When a PreparedStatement is executed for the first time the driver gets a buffer from the bucket holding the smallest size buffers that will hold the result.)。如果bucket中没有buffer,驱动程序根据bucket的预定义大小创建一个新的相应大小的buffer。当一个PreparedStatement被关闭,buffer被返回到相应的bucket中。由于buffer被用于不定大小的需求,buffer通常只比最小的(翻译注:应该是“最大的”吧?)需求大一些。这种差异是有限的,在实践中没有任何影响。
相比11.1或10.2.0.4.0驱动程序,11.2驱动程序始终会保持使用相同或更少的内存。有时候一种不正确的说法是11.2驱动程序可以在小得性能都不可接受的heap中运行。仅仅因为它可以运行在更少的内存中,并不意味着它应该就这样部署。为11.2驱动程序设置一个较大的-Xms值来大副地提高性能,这并不是一种罕见行为。请参阅下面的控制Java Heap。
11.2驱动程序支持maxCachedBufferSize,但它明显不再那么重要。在11.1中,正确地设置maxCachedBufferSize可能就会从OutOfMemoryException的境地变为性能优越。在11.2的驱动程序中,maxCachedBufferSize的设置有时可以改善那些非常大型、拥有大量statement缓存、和大量对于buffer大小需求明显不同的SQL的大型系统的性能。在11.2的maxCachedBufferSize值被解释为buffer的最大大小是该值以2为底的对数。例如,如果maxCachedBufferSize设置为20的缓冲区缓存的最大大小为2^20 = 1048576。为了向后兼容,大于30的值被解释为实际大小,而不是log2的大小,但建议2的对数方式设定此值。
在通常情况下,将maxCachedBufferSize设置一个合理的值是没有任何影响的。如果您需要设置maxCachedBufferSize,从18开始尝试。如果你设置的值不得不小于16,那你可能需要更多的内存。
在11.2驱动程序中,参数数据buffer使用的是相同的buffer缓存和缓存方案。当一个PreparedStatement被放入Implicit Statement Cache中时,参数数据buffer会被buffer缓存缓存起来。当一个PreparedStatement第一次被执行或第一次从Implicit Statement Cache中取出后执行,它就会从buffer缓存获取参数数据buffer。通常情况下,参数数据buffer的大小远远小于于返回的行数据buffer的大小,但是这些buffer也可能由于batch size过大而变得很大。11.2驱动程序也为其它大型字段操作如Bfile、Blob和Clob提供了大的byte[]和char[] buffer的buffer缓存。
11.2驱动程序也支持使用useThreadLocalBufferCache。它的功能和关于何时以及如何使用它的建议与11.1.0.7.0中相同。
11.2驱动程序还添加了一个新的属性,以控制Implicit Statement Cache。
oracle.jdbc.implicitStatementCacheSize
这个属性的值是一个整型字符串,如“100”它是statement cache的初始大小。将该属性设置为正值,即是开启Implicit Statement Cache。默认值为‘0’。该属性可以通过-D设置为Sytstem property或通过调用getConnection时的connection property设置。调用OracleConnection.setStatementCacheSize和/或OracleConnection.setImplicitCachingEnabled将覆盖implicitStatementCacheSize的值。此属性可以使无法在代码中启用Implicit Statement Cache的情况下更方便地启动此功能。
控制Java Heap
优化Java运行时内存使用是一个有一点黑盒的艺术。最重要的两个选项是-Xmx和-Xms。根据Java运行时版本和操作系统的不同,还有一些其它的参数。-Xmx设置Java运行时可以使用的最大的heap的大小。 -Xms刚设置初始heap的大小。默认值取决于操作系统和Java运行时版本,但一般的-Xmx的默认值是64MB,-Xms的默认值是1MB。 32位的JVM支持高达2GB的heap。 64位JVM支持更大的heap。这些参数接受“k”,“m”,和“g”作为后缀以表明是kilo-、mega-还是giga-个字节,例如,-Xmx1g。
当有足够的heap大小时,Oracle的JDBC驱动程序可以提供最佳的性能。对于大多数应用,增加heap的大小达到一个下限之后,应用的性能会大幅提升。大小超出该下限之后,将没有什么区别。如果heap的大小是如此之大以至于超出机器上运行的物理内存,heap内存将会被换出到二级存储,性能会受到严重的影响。设置heap大小时,只设置-Xmx是不够的。11.2驱动程序,特别地注重使用尽量少的内存,它一般不会使heap上涨超出其可以运行的最低水平。如果你只设置最大heap大小,-Xmx,驱动程序可能永远不会实际使用到那么大的内存。如果你还通过-Xms增加最小heap的大小,驱动程序将会可以使用更多的内存,并住住能提供更好的性能。做一个固定的heap大小是的性能测试是很重要的,即将-Xmx和-Xms设置成相同的值。一般生产环境中的应用也是运行在固定的heap大小中的。设置-server选项会使JVM减小最小化heap大小的努力,所以会提供一定的性能改进。适当地设置-Xms往往会比单独设置-server选项提供更多的性能改进。Oracle建议为服务器应用设置-server,-Xms,和-Xmx选项。通常-Xms和-Xmx应设置为相同的值。
结论
Oracle的JDBC驱动程序可能会使用大量的内存以达到最大的性能优化。如果内存制约了应用程序的性能,你可以调整它以获得最大的性能优化。理想的情况下,你应该有一个可以用来场景重现的测试案例,以模拟实现的应用的工作负载。通过系统地改变各种参数并运行测试,你可以找出最优化的设置组合。对于所有版本的Oracle JDBC驱动程序,启用implicit statement cache,小心地设计数据库schema,仔细地编写SQL查询,并适当设置fetchSize都首先要做的。下一步是增加Java堆大小设置-Xmx和-Xms以达到最大的实际可用大小。如果这样达到可接受的性能,你可以尝试减少-Xmx和-Xms,如果一个小一些的heap能提供更多的好处的话。如果最大的实际可用内存的性能还是比预期的要差,你应该尝试适当地设置freeMemoryOnEnterImplictCache或maxCachedBufferSize。
应该明确你的应用的特点是否适合启用useThreadLocalBufferCache。如果您需要将性能优化到极致,尝试调整Java的垃圾收集器。 (见http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html)在大多数情况下,设置-Xmx和-Xms就足够了。
相关推荐
在白皮书中,作者Christopher Jones,作为Oracle数据库的产品经理,介绍了多种语言API,包括OCI、ODBC、ODPI-C、C++、OCCI、Java、JDBC、.NET、ODP.NET、Node.js、Python的cx_Oracle、PHP、Perl的DBD::Oracle、Ruby...
Oracle是世界上最广泛使用的数据库管理系统之一,尤其在企业级应用中占据着重要地位。这份"我们公司的内部oracle开发课件(我的珍藏)"包含了丰富的学习资源,旨在帮助开发者深入理解和熟练掌握Oracle数据库的开发和...
### JMeter性能测试白皮书知识点详解 #### 线程组配置 在JMeter中,**线程组**是构建任何性能测试的基础组件之一,它定义了虚拟用户的集合及其行为模式。 - **线程数(模拟用户数)**:表示在测试中模拟的用户数量...
本白皮书旨在帮助用户完成从Oracle到Sybase Adaptive Server Enterprise (ASE)的应用程序迁移过程。本文档将详细介绍两个数据库管理系统之间的主要差异,并提供一个迁移流程以协助客户顺利完成迁移工作。此外,文档...
### Java、JDBC 以及数据库 Web 服务在 Oracle Database 10g 中的新特性 #### 引言 Oracle Database 10g 第一版(Release 1)为数据库中的 Java 运行时环境(Oracle JVM)、Oracle JDBC 驱动程序及 Oracle 数据库 ...
这意味着KingbaseES V8R2能够支持主流的数据库开发标准和语法,如SQL、ODBC、JDBC等,并且能够兼容如Oracle、SQLServer、MySQL等主流数据库产品的语法,从而使得从其他异构数据库向KingbaseES的迁移过程变得简单高效...
此外,该平台还提供了丰富的内置适配器,包括但不限于亿信BI采集适配器、JDBC驱动采集驱动适配器、Oracle采集适配器等,以支持多种不同类型的数据源。 #### 五、丰富的元数据分析应用 睿治平台提供了多种元数据...
- **Oracle特定的JDBC扩展**:支持Oracle JDBC API的扩展,使应用程序能够在不修改代码的情况下迁移。 - **SQL*Plus脚本**:支持Oracle SQL*Plus工具的脚本,方便进行批量任务的迁移。 - **Oracle Call Interface ...
【DDB5.0开发者白皮书】是针对网易分布式数据库DDB的最新开发者文档,主要涵盖DDB的架构、SQL语法规则、Hint语法、连接池范例以及使用规范和边界等内容。以下是该白皮书中涉及的重要知识点的详细解释: 1. **DDB...
- 数据库:MySQL或Oracle,使用JDBC进行数据库连接。 - 版本控制:Git用于代码版本控制和团队协作。 - 构建工具:Maven或Gradle进行项目构建和依赖管理。 5. **文档规范** - 需求分析:明确系统的目标和功能,...
1.4.3. 数据库支持:SuperMap GIS 10i支持多种关系型数据库(如Oracle、SQL Server、MySQL)和空间数据库(如PostgreSQL、ArcSDE),同时兼容ODBC/JDBC接口,可以连接各种异构数据源,确保了数据的广泛兼容性。...
它由Oracle公司出品,旨在通过提供一套完整的工具和接口来支持关键业务应用程序的需求。 首先,MySQL集群是一个分布式多主机构架,能够实现水平扩展,即通过增加更多的服务器来提升数据库的处理能力。它的无单点...
这得益于其对现代处理器优化的支持以及更高效的内存管理,使得开发者能够创建更快的应用程序。 2. **Unicode支持**:Delphi XE全面支持Unicode,使得开发者可以处理各种语言和字符集,扩展了软件的国际化和全球化...
JSP实践之旅 电子书版 <br> <br>序言--关于JSP实践之旅 简明介绍 JSP内幕 JSP官方白皮书 国内不谈java 基本语法介绍 2001年度Java最佳技术和产品 JSP入门介绍 三种Web开发主流技术的...
8. **information**:可能是一个包含额外信息的目录或文件夹,比如技术白皮书、常见问题解答等。 9. **2d**:可能与Java 2D图形编程有关,介绍如何在Java中创建和操作2D图形。 10. **jdbc**:Java数据库连接(JDBC)...