测试oom使用的类
package com.concurrency.thread;
import com.concurrency.MyFixSizeThreadPool;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用jvm参数指定最大堆内存: 10M
* -Xmx10m
*/
public class TestThreadLocalOOM {
ThreadLocal<List<byte[]>> threadLocal = ThreadLocal.withInitial(() -> new ArrayList<>());
AtomicInteger maxThreadCount = new AtomicInteger();
volatile boolean isStop = false;
Runnable runnable = () -> {
// 每次增加2M
try{
threadLocal.get().add(new byte[1 * 1024 * 1024]);
}catch (OutOfMemoryError err) {
err.printStackTrace();
isStop = true;
}
int index = maxThreadCount.addAndGet(1);
System.out.println("thread index: " + index + "..." + Thread.currentThread() + "...size=" + threadLocal.get().size());
};
Runnable runWithRemove = () -> {
// 每次增加2M
threadLocal.get().add(new byte[1 * 1024 * 1024]);
int index = maxThreadCount.addAndGet(1);
System.out.println("thread index: " + index + "..." + Thread.currentThread() + "...size=" + threadLocal.get().size());
threadLocal.remove();
};
/**
* 测试threadlocal不使用线程池的多线程的场景
*/
@Test
public void testWithoutPool() throws IOException, InterruptedException {
while (true) {
// 长时间运行不会oom
new Thread(runnable).start();
Thread.sleep(100);
}
// System.in.read();
}
@Test
public void testWithFixThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
while (!isStop) {
// 7次之后oom
executorService.execute(runnable);
Thread.sleep(100);
}
}
@Test
public void testWithFixThreadPoolAndRemove() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
while (true) {
// 长时间运行不会oom
executorService.execute(runWithRemove);
Thread.sleep(100);
}
}
@Test
public void testWithMyFixedThreadPoolWithoutRemove() throws InterruptedException {
MyFixSizeThreadPool myFixSizeThreadPool = new MyFixSizeThreadPool(1);
while (true) {
// 长时间运行不会oom
myFixSizeThreadPool.execute(runnable);
Thread.sleep(100);
}
}
public static void main(String[] args) throws IOException, InterruptedException {
Thread.sleep(3*1000);
TestThreadLocalOOM testThreadLocalOOM = new TestThreadLocalOOM();
testThreadLocalOOM.testWithMyFixedThreadPoolWithoutRemove();
}
}
一共分析三个场景
运行testWithoutPool方法, 运行期heap占用图如下, 在运行过程中一直未触发oom
运行testWithFixThreadPool方法, 运行期heap占用图如下, 在循环第8次的时候就触发oom
thread index: 1...Thread[pool-1-thread-1,5,main]...size=1
thread index: 2...Thread[pool-1-thread-1,5,main]...size=2
thread index: 3...Thread[pool-1-thread-1,5,main]...size=3
thread index: 4...Thread[pool-1-thread-1,5,main]...size=4
thread index: 5...Thread[pool-1-thread-1,5,main]...size=5
thread index: 6...Thread[pool-1-thread-1,5,main]...size=6
thread index: 7...Thread[pool-1-thread-1,5,main]...size=7
thread index: 8...Thread[pool-1-thread-1,5,main]...size=7
java.lang.OutOfMemoryError: Java heap space
at com.concurrency.thread.TestThreadLocalOOM.lambda$new$1(TestThreadLocalOOM.java:24)
at com.concurrency.thread.TestThreadLocalOOM$$Lambda$2/2129789493.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
运行testWithFixThreadPoolAndRemove方法, 手动调用remove, 运行期heap占用图如下, 在运行过程中一直未触发oom
要分析为什么oom, 就要知道threadLocal对象是存储在什么地方?查看Thread源码, 可以看出每一个Thread维护了一个threadLocals对象:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
每一个threaLocal对象都存储在这个map中, 已Entry(ThreadLocal> k, Object v)方式存储, 线程退出时, jvm会调用Thread的exit方法,
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null; // 释放threadLocals这个map中存放的所有对象
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
在这个方法中, 通过threadLocals = null; 将map这个对象释放, gc可以回收. 所以testWithoutPool不会oom
为什么使用线程池就有可能oom呢?这个和线程池的实现方式有关, 简单看一下ThreadPoolExecutor的实现方式(不考虑线程的退出情况), 线程池中的所有线程一直在循环监听一个BlockingQueue
// java.util.concurrent.ThreadPoolExecutor这个类的方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 一直死循环, 监听队列, 获取任务
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
可见线程池中的线程一直并没有退出, 而是一直在自旋运行. 所以不会运行到Thread.exit这个方法. 在没有手动调用ThreadLocal.remove方法的时候, Thread中的threadLocals 这个Map会一直堆积, 而内存得不到释放, 导致了oom
第2部分分析了为什么线程池中threadlocal会oom, 要解决这个问题, 在threadLoca对象不再需要使用的时候, 手动调用remove方法, 清除Thread中threadLocals 中当前的Entry, 以便gc回收.
// java.lang.ThreadLocal#remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
既然在使用线程池的时候, 如果没有手动调用ThreadLocal的remove方法, 有潜在的oom风险. 那如果用户忘记了手动调用, 对程序员不友好.
那有没有方法可以改进一下这个线程池, 让线程池中的线程运行完一个任务后, 主动释放Thread.threadLocals?
答案肯定是可以的, 可以看到, testWithMyFixedThreadPoolWithoutRemove运行后, 一直不会oom, MyFixSizeThreadPool实现代码如下:
package com.concurrency;
import java.lang.reflect.Field;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class MyFixSizeThreadPool {
private final int core;
private AtomicInteger currentThreadCount = new AtomicInteger(0);
private LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
public MyFixSizeThreadPool(int core) {
this.core = core;
}
public void execute(Runnable command) {
if(currentThreadCount.get() < core) {
currentThreadCount.addAndGet(1);
Thread thread = new Thread(new Worker());
thread.start();
}
taskQueue.offer(command);
}
class Worker implements Runnable {
Thread thread;
@Override
public void run() {
// 一只查询是否有值
Runnable task = null;
while ((task = getTask()) != null) {
task.run();
// 运行完成后, 清空Thread.threadLocals, 确保下一个任务调用这个线程时, 不会使用者上一个任务留下的threadLocals
Thread thread = Thread.currentThread();
try {
Field threadLocalsFiled = Thread.class.getDeclaredField("threadLocals");
threadLocalsFiled.setAccessible(true);
threadLocalsFiled.set(thread, null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private Runnable getTask() {
Runnable take = null;
while (true) {
try {
take = taskQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
continue;
}
return take;
}
}
}