Skip to content

Commit b5ec73b

Browse files
garyrussellartembilan
authored andcommitted
INT-3915: Possible Memory Leak in FileChannelCache
JIRA: https://jira.spring.io/browse/INT-3915 Close the redundant `FileChannel` when Map collision occurs.
1 parent aca5646 commit b5ec73b

File tree

1 file changed

+58
-45
lines changed

1 file changed

+58
-45
lines changed
Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,69 +19,82 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.io.RandomAccessFile;
22+
import java.nio.channels.FileChannel;
2223
import java.nio.channels.FileLock;
2324
import java.nio.channels.OverlappingFileLockException;
24-
import java.nio.channels.FileChannel;
2525
import java.util.concurrent.ConcurrentHashMap;
2626
import java.util.concurrent.ConcurrentMap;
2727

2828
/**
2929
* Static cache of FileLocks that can be used to ensure that only a single lock is used inside this ClassLoader.
3030
*
3131
* @author Iwein Fuld
32+
* @author Gary Russell
3233
* @since 2.0
3334
*/
3435
final class FileChannelCache {
3536

36-
private static ConcurrentMap<File, FileChannel> channelCache = new ConcurrentHashMap<File, FileChannel>();
37+
private static ConcurrentMap<File, FileChannel> channelCache = new ConcurrentHashMap<File, FileChannel>();
3738

3839

39-
/**
40-
* Try to get a lock for this file while guaranteeing that the same channel will be used for all file locks in this
41-
* VM. If the lock could not be acquired this method will return <code>null</code>.
42-
* <p>
43-
* Locks acquired through this method should be passed back to #closeChannelFor to prevent memory leaks.
44-
* <p>
45-
* Thread safe.
46-
*/
47-
public static FileLock tryLockFor(File fileToLock) throws IOException {
48-
FileChannel channel = channelCache.get(fileToLock);
49-
if (channel == null) {
50-
FileChannel newChannel = new RandomAccessFile(fileToLock, "rw").getChannel();
51-
FileChannel original = channelCache.putIfAbsent(fileToLock, newChannel);
52-
channel = (original != null) ? original : newChannel;
53-
}
54-
FileLock lock = null;
55-
if (channel != null) {
56-
try {
57-
lock = channel.tryLock();
58-
}
59-
catch (OverlappingFileLockException e) {
60-
// File is already locked in this thread or virtual machine
61-
}
62-
}
63-
return lock;
64-
}
40+
/**
41+
* Try to get a lock for this file while guaranteeing that the same channel will be used for all file locks in this
42+
* VM. If the lock could not be acquired this method will return <code>null</code>.
43+
* <p>
44+
* Locks acquired through this method should be passed back to #closeChannelFor to prevent memory leaks.
45+
* <p>
46+
* Thread safe.
47+
*/
48+
public static FileLock tryLockFor(File fileToLock) throws IOException {
49+
FileChannel channel = channelCache.get(fileToLock);
50+
if (channel == null) {
51+
@SuppressWarnings("resource")
52+
FileChannel newChannel = new RandomAccessFile(fileToLock, "rw").getChannel();
53+
FileChannel original = channelCache.putIfAbsent(fileToLock, newChannel);
54+
if (original != null) {
55+
channel = original;
56+
try {
57+
newChannel.close();
58+
}
59+
catch (IOException e) {
60+
// ignore
61+
}
62+
}
63+
else {
64+
channel = newChannel;
65+
}
66+
}
67+
FileLock lock = null;
68+
if (channel != null) {
69+
try {
70+
lock = channel.tryLock();
71+
}
72+
catch (OverlappingFileLockException e) {
73+
// File is already locked in this thread or virtual machine
74+
}
75+
}
76+
return lock;
77+
}
6578

66-
/**
67-
* Close the channel for the file passed in.
68-
* <p>
69-
* Thread safe.
70-
*/
71-
public static void closeChannelFor(File fileToUnlock) {
72-
FileChannel fileChannel = channelCache.remove(fileToUnlock);
73-
if (fileChannel != null) {
74-
try {
79+
/**
80+
* Close the channel for the file passed in.
81+
* <p>
82+
* Thread safe.
83+
*/
84+
public static void closeChannelFor(File fileToUnlock) {
85+
FileChannel fileChannel = channelCache.remove(fileToUnlock);
86+
if (fileChannel != null) {
87+
try {
7588
fileChannel.close();
7689
}
77-
catch (IOException e) {
90+
catch (IOException e) {
7891
// ignore
7992
}
80-
}
81-
}
93+
}
94+
}
8295

83-
public static boolean isLocked(File file) {
84-
return channelCache.containsKey(file);
85-
}
96+
public static boolean isLocked(File file) {
97+
return channelCache.containsKey(file);
98+
}
8699

87100
}

0 commit comments

Comments
 (0)