系统正常运行一段时间后,QA报给我一个异常:

java.lang.OutOfMemoryError: GC overhead limit exceeded

at java.text.DecimalFormatSymbols.clone(Unknown Source)

at java.text.DecimalFormat.clone(Unknown Source)

at java.text.SimpleDateFormat.initialize(Unknown Source)

at java.text.SimpleDateFormat.<init>(Unknown Source)

at java.text.SimpleDateFormat.<init>(Unknown Source)

at com.thomsonreuters.ce.exportjobcontroller.task.file.iiroutage.entity.AnalyticTask.<init>(AnalyticTask.java:18)

参考文档,此类异常是由于:

"if too much time is being spent in garbage collection: if more than 98% of the total time is spent in garbage collection and less than 2% of the heap is recovered, an OutOfMemoryError will be thrown."

异常是在测试人员做压力测试的时候抛出,直接原因是大量业务实体对象AnalyticTask的创建(>1w)导致了GC overhead limit exceeded。经分析,初步判定是业务实体类中定义的SimpleDateFormat成员导致。

首先,了解一下这个类的两个特性:

1. SimpleDateFormat 不是线程安全的;

2. 创建它的实例将会耗费很大的代价。

以上两点都是由它的一个成员属性造成的。它继承了DateFormat,在DateFormat中定义了一个protected属性的Calendar类的对象:calendar。而Calendar 是一个非常复杂的类,因为涉及到时区、本地化以及闰年等等因素。在本例中,由于在业务实体类中定义了SimpleDateFormat类型的属性,每一个业务对象的创建,都会创建一个SimpleDateFormat实例对象,大量的实例对象占用了大量的jvm空间。由于它的复杂性,GC的工作量比较大,效率很低,从而导致问题的发生。

进一步分析线程安全的问题:

JDK原始文档如下:
  Synchronization:
  Date formats are not synchronized. 
  It is recommended to create separate format instances for each thread. 
  If multiple threads access a format concurrently, it must be synchronized externally

format方法实现:

private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
} switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break; case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break; default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}

  

calendar.setTime(date)这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
  线程1调用format方法,改变了calendar这个字段。
  中断来了。
  线程2开始执行,它也改变了calendar。
  又中断了。
  线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。
  分析一下format的实现,我们不难发现,用到成员变量calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
  这个问题背后隐藏着一个更为重要的问题--无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。

  这也同时提醒我们在开发和设计系统的时候注意下一下三点:

  1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明

  2.对线程环境下,对每一个共享的可变变量都要注意其线程安全性

  3.我们的类和方法在做设计的时候,要尽量设计成无状态的

解决方案:参考 http://www.cnblogs.com/peida/archive/2013/05/31/3070790.html

本列采用ThreadLocal变量来解决问题。

SimpleDateFormat 的性能和线程安全性的更多相关文章

  1. SimpleDateFormat时间格式化存在线程安全问题

    想必大家对SimpleDateFormat并不陌生.SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调 ...

  2. 2017.12.11SimpleDateFormat的线程安全性讨论

    转载来自:http://blog.csdn.net/zxh87/article/details/19414885 1.结论 DateFormat和SimpleDateFormat都不是线程安全的.在多 ...

  3. Java多线程编程(3)--线程安全性

    一.线程安全性   一般而言,如果一个类在单线程环境下能够运作正常,并且在多线程环境下,在其使用方不必为其做任何改变的情况下也能运作正常,那么我们就称其是线程安全的.反之,如果一个类在单线程环境下运作 ...

  4. Java并发编程学习笔记(一)——线程安全性

    主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...

  5. Spring并发访问的线程安全性问题

    Spring并发访问的线程安全性问题 http://windows9834.blog.163.com/blog/static/27345004201391045539953/ 由于Spring MVC ...

  6. 《Java并发编程实战》第二章 线程安全性 读书笔记

    一.什么是线程安全性 编写线程安全的代码 核心在于要对状态訪问操作进行管理. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与 ...

  7. Java并发编程实战 之 线程安全性

    1.什么是线程安全性 当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全 ...

  8. 【Java并发.2】线程安全性

    要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享(Shared)和可变的(Mutable)状态的访问. “共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生 ...

  9. Java并发(理论知识)—— 线程安全性

    1.什么是线程安全性                                                                                      当多个线 ...

随机推荐

  1. ng-cookie 的基本使用

    2.angular-cookie - 配置$cookiesProvider ```angular.module("Demo",[]).config(["$cookiesP ...

  2. Cocos2d-x 3.x学习笔记(一):开始Cocos2d之旅

    首先,进入官网下载cocos2d-x:http://www.cocos.com/download/,当然你需要注册一个账号才可以下载. 接下来需要跟着官网的配置文档配置一下开发环境,不得不说,Coco ...

  3. R语言-Knitr包的详细使用说明

    R语言-Knitr包的详细使用说明 by 扬眉剑 来自数盟[总舵] 群:321311420 1.相关资料 1:自动化报告-谢益辉 https://github.com/yihui/r-ninja/bl ...

  4. sql存储过程exec执行字符串select 的区别

    USE [GuangHong]GO/****** Object: StoredProcedure [dbo].[st_MES_SelInspctDetail] Script Date: 11/23/2 ...

  5. Azure 云服务中的实例端点

    Azure云服务(cloud Servive)中有三种端点类型(endpoint type):输入端点(input);内部端点(internal);实例端点(InstanceInput) 1.输入端点 ...

  6. C#double转化成字符串 保留小数位数, 不以科学计数法的形式出现

      在C#中大家都会遇到这种情况 double类型的数据,需要格式化(保留N未有效数字)或者是保留N为小数等情况,我们往往采取double.tostring("参数");的方法.下 ...

  7. Codeforces Codeforces Round #319 (Div. 2) B. Modulo Sum 背包dp

    B. Modulo Sum Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/577/problem/ ...

  8. spring关于“transactionAttributes”的相关配置

    spring关于"transactionAttributes"的相关配置 <bean id="baseTransactionProxy" class=&q ...

  9. IOS debug网络PonyDebugger 实践篇

    引言:  PonyDebugger是一个很给力的iOS调试工具,它的监视器安装在Chrome浏览器下做为插件使用,通过监视器和PonyDebugger的iOS SDK相辅相成,可以很好的监视App的运 ...

  10. iOS开发——UI篇OC篇&UITableView多项选择

    UITableView多项选择 自定义cell和取到相应的cell就行了 TableViewCell.h #import <UIKit/UIKit.h> @interface TableV ...