One Trick Per Day

简介: 初始化Map应避免默认容量导致扩容,推荐Guava的`newHashMapWithExpectedSize`;禁用Executors创建线程池,防止OOM,应手动通过`ThreadPoolExecutor`设置合理参数;`Arrays.asList`返回不可变列表,禁止修改操作;遍历Map使用`entrySet`提升性能;`SimpleDateFormat`非线程安全,建议用`ThreadLocal`或Java8时间类;并发修改记录需加锁,优先乐观锁,冲突高则用悲观锁。

1.初始化Map大小并非用多少指定多少
● 初始化Map并非用多少初始化Size是多少,建议使用Guava,避免扩容引起的动荡()
说明
● 如:Map map = new HashMap<>(1); 在具体使用时,并非size=1,而是最近的2的幂等,如1实际是2,3实际是4,9实际是16
使用方法
● 依赖gvaua:Map map = Maps.newHashMapWithExpectedSize(7);


com.google.guava
guava
17.0

● 手动声明:Map map = new HashMap<>(实际存储个数 / 0.75 + 1);
2.线程池初始化严禁使用Executors
使用线程池时候,我们可能会使用下面四个场景,这在alibaba代码规范中都是明令禁止的
// 创建一个单线程化的Executor[因为数量固定,可能会堆积大量请求,导致OOM]
private static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 创建一个固定数目线程的线程池[因为数量固定,可能会堆积大量请求,导致OOM]
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

// 创建一个可执行命令的单线程Executor[可能会创建大量的线程,导致OOM]
private static ExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

// 创建一个可缓存的线程池(60S存活时间)[可能会创建大量的线程,导致OOM]
private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
我们先来一个简单的例子,模拟一下使用 Executors 导致 OOM 的情况。
public class ExecutorsDemo {
private static ExecutorService executor = Executors.newFixedThreadPool(15);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(new SubThread());
}
}
}
class SubThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
}
}
通过指定 JVM 参数:-Xmx8m -Xms8m 运行以上代码,会抛出 OOM:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
以上代码指出,ExecutorsDemo.java 的第 16 行,就是代码中的 executor.execute(new SubThread());。
通过上面的例子,我们知道了 Executors 创建的线程池存在 OOM 的风险,那么到底是什么原因导致的呢?我们需要深入 Executors 的源码来分析一下。其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致 OOM 的其实是 LinkedBlockingQueue.offer 方法。
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
如果读者翻看代码的话,也可以发现,其实底层确实是通过 LinkedBlockingQueue 实现的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
如果读者对 Java 中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。Java 中 的 BlockingQueue 主 要 有 两 种 实 现, 分 别 是 ArrayBlockingQueue 和 LinkedBlockingQueue。ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,必须设置容量。LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为 Integer.MAX_VALUE。这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置 LinkedBlockingQueue 的容量的话,其默认容量将会是 Integer.MAX_VALUE。 而 newFixedThreadPool 中创建 LinkedBlockingQueue 时,并未指定容量。此时,LinkedBlockingQueue 就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。上面提到的问题主要体现在 newFixedThreadPool 和 newSingleThreadExecutor 两个工厂方法上,并不是说newCachedThreadPool 和 newScheduledThreadPool 这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致 OOM

正确使用:
private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(10));
这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
但是部分alibaba作者更推荐使用guava创建对应的线程池,示例如下:
public class ExecutorsDemo {
private static ThreadFactory namedThreadFactory = new
ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), namedThreadFactory, new
ThreadPoolExecutor.
AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
pool.execute(new SubThread());
}
}
}
通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
3.Arrays.asList之后不要调用修改操作
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
因为asList返回的实际是一个Arrays内部类,并没有实现集合的修改方法(add/remove/clear)// 当操作修改方法时,会报UnsupportedOperationException。
第一种情况:list.add("yangguanbao"); 运行时异常。
第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。[涉及栈堆指针操作,修改数组的数据,导致同样引用该数据的list值被改变]

4.使用 entrySet 遍历 Map 类集合 KV
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。
如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。
5.SimpleDateFormat不要定义为static
SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
6.并发修改同一记录时需要加锁
要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次

相关文章
|
25天前
|
Java 应用服务中间件 网络安全
Eclipse运行SSM/SSH项目教程
本教程介绍如何在Eclipse中配置并运行Java Web项目。涵盖JDK、Tomcat环境搭建,项目导入(支持Maven与非Maven),Eclipse中绑定Tomcat服务器及项目部署步骤。提供常见问题解决方案,如数据库连接配置错误等,助你快速启动项目并访问。
|
25天前
|
Java 测试技术 API
从Google线上故障,谈灰度发布的重要性
2025年6月12日,Google Cloud因未灰度发布的新配置引发空指针异常,导致Gmail、YouTube等服务中断超7小时。故障暴露了缺乏配置灰度与错误处理机制的风险。本文结合Nacos等配置中心的IP/标签灰度方案,探讨如何通过渐进式发布保障系统稳定性,避免全局故障。
|
25天前
|
运维 安全 Devops
生产环境缺陷管理
git-poison基于go-git实现分布式bug追溯,解决多分支开发中bug漏修、漏发等问题。通过“投毒-解毒”机制,自动化卡点发布流程,降低协同成本,避免人为失误,已在大型团队落地应用,显著提升发布安全与效率。
|
25天前
|
Java 数据安全/隐私保护
脱敏工具
提供身份证、姓名、手机号脱敏的Java实现,通过正则替换隐藏敏感信息:身份证保留前后部分,中间用*代替;姓名仅显示首尾字符;手机号显示前3后4位,中间4位脱敏,保障数据安全。
|
25天前
|
消息中间件 监控 Java
RocketMQ:底层Netty频繁OS OOM
本文记录了一例Java应用因多ClassLoader加载多个Netty的PooledByteBufAllocator导致堆外内存超限,引发OS OOM的排查过程。尽管MaxDirectMemorySize设为1G,但多个中间件独立占用堆外内存,总量远超限制。通过NMT、Arthas等工具定位到RocketMQ客户端为主要占用者。最终建议短期调小堆内存以腾出空间,并推动中间件优化。
|
25天前
|
自然语言处理 fastjson Java
FastJson:大面积故障规避案例
本文分享了一次因Kotlin语法误用导致FastJson反序列化全局异常的排查经历。在多语言混编工程中,`{}`被误赋值为Java对象字段,触发FastJson解析Kotlin类时将静态标记`kotlin_error`置为true且无法恢复,最终引发全量反序列化失败。问题隐蔽性强,影响范围大,历时两天定位。反思指出:多语言开发需谨慎、框架有局限、灰度发布至关重要,并强调Bug是成长的阶梯。
|
25天前
|
Java 测试技术 Linux
生产环境发布管理
本文介绍大型团队如何通过自动化部署平台实现多环境(dev/test/pre/prod)高效发布与运维。涵盖各环境职责、基于Jenkins+K8S的CI/CD流程、分支管理、一键发布及日志排查方案,结合Skywalking实现链路追踪,提升发布效率与问题定位能力。(238字)
|
25天前
|
Java 关系型数据库 MySQL
Activiti工程搭建
本文介绍如何在Idea中创建Maven工程并安装Activiti插件,包含插件下载、版本兼容性修复及验证步骤,同时引入SpringBoot、MySQL、MyBatis与Activiti相关依赖,配置数据源与流程引擎,完成基础环境搭建,适用于工作流项目快速开发。
|
25天前
|
Java 数据安全/隐私保护
随机ID
提供两个Java方法:一个生成指定长度的随机数字与大小写字母组合字符串,另一个仅生成指定长度的随机数字。适用于需要随机验证码、密码等场景,使用简单,可直接调用。
|
25天前
|
存储 Java
超长String接收处理
Java中字符串变量最大长度可达Integer.MAX_VALUE,但字符串字面量受class文件格式限制,理论最大65535,实际仅支持65534。超长字面量编译报错,需通过StringBuilder分段处理,避免“常量字符串过长”问题。

热门文章

最新文章