fastjson解析大文件时抛出java.lang.ArrayIndexOutOfBoundsException: -1

最近发现偶尔出现fastjson解析大文件时抛出java.lang.ArrayIndexOutOfBoundsException: -1异常。可以我是按照官方的中的实例代码来写的,如下的代码:

所以非常不解,我自己单独写代码解析json文件又没有问题,在应用跑了一段时间后,就突然出现这个问题。由于fastjson的代码没有深入阅读,而且异常还不是每次都能碰到,因此没有查到直接原因。但同事发现换个api来解析就不抛出异常了,因此在这里记录下,下次朋友类似的情况,也可以按照种方式来解决。代码这样改:

可能这种改法效率没有上面那个好,但是这个确实没有抛出异常了,至少在可用性方面得到保证。如果你也碰到这种情况,也可以尝试这样修改你代码。

【译】编写自定义的Velocity指令

本文将使用Apache Velocity API来编写自己的行内指令和块指令。以实现truncate()为例,即实现一个velocity的展示工具方法,用来对长字符串做截断。

在velocity中有两种指令:行内指令和块指令。行内指令在模板中仅有一行vm代码,而块指令有一段代码块同时以#end标签结尾。如下面所示的,第一个为行内指令,第二个为块指令:

编写行指令

为了声明你自定义的指令,你需要在velocity配置文件(比如:velocity.properties)中指定userdirective参数值,如果有多个自定义指令,则需要使用逗号来分割多个自定义指令类的全名。类似下面这样:

下面来创建行内指令#truncate,该指令有4个参数,如下:

第一个参数是需要被截断的字符串,而且是为一个必须的参数。其他几个参数都是用来对截断进行设置的可选参数。具体指令代码如下:

在Apache中所有的指令都继承自Directive 类,它有三个抽象方法需要我们来实现:getName()getType()render()

  • 第一个方法getName() 应该返回指令的名称,该名称将在模板中使用,即#后面的指令名称。
  • 第二个方法getType() 返回 BLOCK 或者 LINE,由你所实现的指令类型决定。
  • 第三个方法render(InternalContextAdapter context, Writer writer, Node node) 是真正起到作用的代码。Writer 是用来写入结果对象的;Node 对象包含了我们的指令信息(指令参数和属性);InternalContextAdapter 包含了Velocity渲染模板所需要的所有的信息。

指令参数(准确的说node就是指令的参数)可以通过调用node.jjtGetChild(i)获得,i是参数的序号(序号从0开始)。你可以调用node.jjtGetNumChildren().获得所有的参数。

jjtGetChild(i) 返回的是一个Node 类型的对象,通过这个对象你可以调用node.jjtGetChild(i).value() 方法来获得已经渲染后的变量的值;你也可以通过调用node.jjtGetChild(i).literal()方法来获得字面值。可以通过下面的代码来解释变量的值和字面值的区别:

(String)node.jjtGetChild(0).value() 的结果是 "This is Test Value", 但(String)node.jjtGetChild(0).literal() 的结果是 "This is $test". 当然大多数情况下,你都是需要变量渲染后的值。

要想测试我们新编写的指令,就得先在配置文件中配置如下的内容:

这样就可以运行模板了:

结果是:

编写块指令

现在我们把刚才编写的行内指令改成块指令,就像这样:

上面的代码和前面行内指令的效果一样,参数也是可选的,来看看指令的代码:

有一点不同是:我们额外的实现了Directive 的init() 方法,该方法可以让我们通过访问RuntimeServices 对象来获取logger实例和配置文件中的指令配置。所以我们的指令配置文件可能是这样的:

在配置文件中设置默认值是可选择的,你可以不设置,因为如果在模板中使用指令时没有传可选参数,我们仍然可以在代码中硬编码默认值。比如下面的代码,设置maxLength 的值为配置文件userdirective.truncateBlock.maxLength 的值,如果没有配置文件没有这个配置,则设置默认值为10。

尽管没有配置参数命名的官方推荐,但是使用userdirective.{directive}.{param} 方式可能会比较好。

render() 方法好像有点不太一样了,原因是块指令中的内容在指令节点的最后一个孩子节点上,但是因为所有参数都是可选的,我们需要某种方式来区分指令的参数和指令块的内容。一个办法是检查孩子节点的类型,如果是指令块的内容,则节点类型为ASTBlock,该类型正好与指令的参数不同。为了渲染ASTBlock 节点的内容,我们需要使用

总结

本文讲解了如何编写Apache Velocity自定义的指令,但是在编写自定义指令前,最好先看看考虑一下往模板中设置工具类的方式,因为编写自己的指令需要预留扩展核心模板语言和支持多参数的能力。例如:如果velocity的展示工具集中没有“truncate”方法,一个合适的方法是创建一个新的工具,并把它添加到velocity工具集中,而不是实现一个指令。(即实现指令的成本比较高)

自定义指令不太复杂,但是没有什么文档和教程支持。如果你需要更多的例子,可以参考velocity tool的源码。你也可以check out我的开源项目htmlcompressor,里面有一些块指令。

Reference

Creating Custom Directives for Apache Velocity

http://www.ibm.com/developerworks/cn/java/j-lo-velocity/

优秀的IT技术网站收集

技术开发相关的网站很多,信息多得看不过来,很多优秀的技术网站和博客很难出现在搜索引擎的第一二页,而且很多好的原创文章都是英文的,干货实在是太多了,下面列举了一下:

1. jquery插件库

http://www.jq22.com/

2. spring框架官方文档

http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/

3. 一个不错的java文章网站

http://www.jayway.com/category/java/page/2/

4. java相关的比较有深度的网站

http://www.importnew.com/

5. 开发者头条,开发者的重要资讯

http://toutiao.io/

6. 慕课网,免费学习各种IT技能

http://www.imooc.com/course/list

7. 在线css编辑网站

http://enjoycss.com/

8. java代码搜索网站

http://grepcode.com/

9. jar包搜索网站

http://www.findjar.com/

10. 各编程语言性能对比网站

http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=java&lang2=go

11. 开源中国的在线工具集和API文档集合,太全面了

http://www.ostools.net/

12. 一个很牛叉的java技术网站(英文的)

http://www.theserverside.com/?asrc=TAB_TheServerSideCOM

13. 一个很不错的java技术网站(英文的)

http://examples.javacodegeeks.com/

14. 一个技术干货很多的额博客

http://ifeve.com/

15. 最新最前沿的java技术咨询

http://www.javaworld.com/

16. 这里汇集了100个的牛x的java技术博客(英文的)

http://www.programcreek.com/2012/11/top-100-java-developers-blogs/

17. 查看html标签、js对象方法、css属性、SVG等浏览器兼容性的网站,可快速答疑解惑

http://caniuse.com/

这里只列举了我收藏的一些网站,欢迎添加其他不错的网站。

【译】JVM的默认参数

如果在META-INF/MANIFEST.MF文件里面设置了Main-Class属性,那么你就可以很方便的通过java -classpath myapp.jar Main命令甚至更简洁的java -jar myapp.jar命令,来启动java虚拟机。这些命令会使用默认的设置来启动JVM。

要想看到JVM执行时使用在使用哪些参数以及其各参数默认值,可以使用这个命令:

java -XX:+PrintFlagsFinal -version

如果这些参数还不够全,你可以打开几个开关:

java -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+PrintFlagsFinal -version

大部分的参数都可以安全的看到默认值,但是某些参数需要在生产环境运行时来调整。

(后面花括号里面有product的表示可以在启动参数中设置的,花括号里面是“product rw”的表示你还可以在jvm启动后使用类似下面的命令来设置这些参数)

jinfo -flag -PrintHeapAtGC=true <pid>

基本的内存参数设置

对java应用而言,最基本的设置莫过于java堆大小的初始值和最大值设置了(一般认为生产环境初始值和最大值设置成一样的比较合适)。

  • -Xms<size> 设置java堆大小的初始值
  • -Xmx<size> 设置java堆大小的最大值

如果你使用的java7或者更早版本的jdk,你可能发现“OutOfMemoryError: PermGen space”,这时你需要使用-XX:MaxPermSize=<size>参数来增大Perm区大小。server虚拟机默认的Perm区大小是64M,但很多应用所需的Perm区空间不止64M。

如果JVM虚拟机耗尽了堆空间,且GC垃圾回收不了时,会抛出“java.lang.OutOfMemoryError:Java heap space”,要分析内存占用情况,可以添加参数-XX:+HeapDumpOnOutOfMemoryError参数,这个参数会在抛出java.lang.OutOfMemoryError异常时,dump堆空间的数据到当前工作目录下的一个文件中,-XX:HeapDumpPath=<path>可以指定这个dump文件的具体位置

垃圾回收(GC)

垃圾回收策略有很多,这里不细说,可以参考Java Garbage Collection Distilled

不管你正在使用那一个垃圾收集器,你都可以用日志的方式记录一段事件的垃圾回收的过程,来观察垃圾回收的效果。你可以添加参数-verbose:gc来把垃圾回收日志输出到标准输出(STDOUT),但更好的方式是通过参数-Xloggc:<pathtofile>把垃圾回收日志输出到指定的文件。

同时添加-XX:+PrintGCDateStamps参数可以记录下垃圾回收的时间戳,添加-XX:+PrintGCDetails可以记录下垃圾回收的事件细节。

默认情况下,垃圾回收日志文件不断变大,但JVM还内置支持回转日志文件(rotating the file),可搭配使用这三个参数:

  • -XX:+UseGCLogFileRotation 开启回转日志文件
  • -XX:GCLogFileSize=8K 设置单个文件最大的文件大小
  • -XX:NumberOfGCLogFiles=1 设置回转日志文件的个数

最后,你可能需要使用-XX:+DisableExplicitGC参数来显性的关闭手动GC功能(即调用jdk的建议GC的api:System.gc()),让System.gc()方法变成一个空方法,让垃圾回收托管给JVM的垃圾收集器。

实例

java -Xms10g -Xmx10g -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/var/log/myapp/gc.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=10M -XX:NumberOfGCLogFiles=20 -jar myapp.jar

这个例子使虚拟机分配10GB内存和256MB给perm区(java7)。内存溢出时dump堆空间的共鞥启动了,使用G1(garbage first)垃圾收集器,垃圾收集日志记录到/var/log/myapp/gc.log中,同时开启了日志文件回转功能。

Referece