? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 感謝點(diǎn)贊和關(guān)注 ,每天進(jìn)步一點(diǎn)點(diǎn)!加油!
目錄
一、問題描述
二、問題定位和源碼分析
一、問題描述
ftp程序讀取windows本地文件寫入HDFS,5天左右程序 重啟一次,懷疑是為OOM掛掉,馬上想著就分析 GC日志了。
### 打印gc日志
/usr/java/jdk1.8.0_162/bin/java \
-Xmx1024m -Xms512m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 \
-XX:-ResizePLAB -verbose:gc -XX:-PrintGCCause -XX:+PrintAdaptiveSizePolicy \
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/hadoop/datadir/windeploy/log/ETL/DS/gc.log-`date +'%Y%m%d%H%M'` \
-classpath $jarPath com.winnerinf.dataplat.ftp.FtpUtilDS
程序分配內(nèi)存 1024M ,從gc日志可以看出,old區(qū)域的占用大小一直從100M上升到了1G,后面頻繁的fullGc,但是釋放的空間并不多,最后程序由于內(nèi)存空間不足導(dǎo)致OOM。
我們使用 jmap 命令將該進(jìn)程的 JVM 快照導(dǎo)出來進(jìn)行分析:
jmap -dump:live,format=b,file=dump.hprof PID
這個命令會在當(dāng)前目錄下生成一個dump.hrpof文件,這里是二進(jìn)制的格式,你不能直接打開看的,其把這一時刻JVM堆內(nèi)存里所有對象的快照放到文件里去了,供你后續(xù)去分析。
Memory Analyzer Mat下載地址:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation
https://kangll.blog.csdn.net/article/details/130222759?spm=1001.2014.3001.5502https://kangll.blog.csdn.net/article/details/130222759?spm=1001.2014.3001.5502Mat 工具給出了兩個問題:
問題一
"org.apache.hadoop.fs.FileSystem$Cache"”的一個實(shí)例文件系統(tǒng)被"sun.misc.Launcher$AppClassLoader @ 0xc04e9290"加載。占用208,987,664字節(jié)(20.36%)。內(nèi)存在“"org.apache.hadoop.fs.FileSystem$Cache"”的一個實(shí)例中累積。文件系統(tǒng)被程序"sun.misc.Launcher$AppClassLoader @ 0xc04e9290"加載。
問題二
7,670個“org.apache.hadoop.conf.Configuration”實(shí)例。配置”,由“sun.misc”加載。"sun.misc.Launcher$AppClassLoader @ 0xc04e9290" 占用813,527,552字節(jié)(79.25%)。這些實(shí)例是從"java.util.HashMap$Node[]",的一個實(shí)例中引用的。由"<系統(tǒng)類加載器>"加載。
二、問題定位和源碼分析
問題的源頭在于 org.apache.hadoop.fs.FileSystem 這個類,程序運(yùn)行了5天, conf 類就產(chǎn)生了幾千個實(shí)例。這些實(shí)例雖然占用的大小不大,但是每產(chǎn)生一個FileSystem實(shí)例時,它都會維護(hù)一個Properties對象(Hashtable的子類),用來存儲hadoop的那些配置信息。hadoop的配置有幾百個很正常,因此一個FileSystem實(shí)例就會對應(yīng)上百個的Hashtable$Entity實(shí)例,就會占用大量內(nèi)存。
為什么會有如此多的FileSystem實(shí)例呢?
以下是我們獲取FIleSystem的方式:
Configuration conf = new Configuration();
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
conf.set("fs.file.impl", "org.apache.hadoop.fs.LocalFileSystem");
FileSystem fileSystem = FileSystem.get(conf);
FileSystem.get底層是有使用緩存的,因此我們在每次使用完并沒有關(guān)閉fileSystem,只是httpfs服務(wù)關(guān)閉時才去關(guān)閉FileSystem。
/****************************************************************
*
* </ol>
*****************************************************************/
@SuppressWarnings("DeprecatedIsStillUsed")
@InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class FileSystem extends Configured
implements Closeable, DelegationTokenIssuer {
public static final String FS_DEFAULT_NAME_KEY =
CommonConfigurationKeys.FS_DEFAULT_NAME_KEY;
public static final String DEFAULT_FS =
CommonConfigurationKeys.FS_DEFAULT_NAME_DEFAULT;
/**
* 獲取這個URI的方案和權(quán)限的文件系統(tǒng)
* 如果配置有屬性{@code "fs.$SCHEME.impl.disable。緩存"}設(shè)置為true,
* 將創(chuàng)建一個新實(shí)例,并使用提供的URI和進(jìn)行初始化配置,然后返回而不被緩存
*
* 如果有一個緩存的FS實(shí)例匹配相同的URI,它將被退回
* 否則:將創(chuàng)建一個新的FS實(shí)例,初始化配置和URI,緩存并返回給調(diào)用者。
*/
public static FileSystem get(URI uri, Configuration conf) throws IOException {
String scheme = uri.getScheme();
String authority = uri.getAuthority();
if (scheme == null && authority == null) { // use default FS
return get(conf);
}
if (scheme != null && authority == null) { // no authority
URI defaultUri = getDefaultUri(conf);
if (scheme.equals(defaultUri.getScheme()) // if scheme matches default
&& defaultUri.getAuthority() != null) { // & default has authority
return get(defaultUri, conf); // return default
}
}
// //如果cache被關(guān)閉了,每次都會創(chuàng)建一個新的FileSystem
String disableCacheName = String.format("fs.%s.impl.disable.cache", scheme);
if (conf.getBoolean(disableCacheName, false)) {
LOGGER.debug("Bypassing cache to create filesystem {}", uri);
return createFileSystem(uri, conf);
}
return CACHE.get(uri, conf);
}
/** Caching FileSystem objects. */
static class Cache {
// ....
FileSystem get(URI uri, Configuration conf) throws IOException{
// key 的問題 ,詳細(xì)我們看下 Key
Key key = new Key(uri, conf);
return getInternal(uri, conf, key);
}
}
/**
*如果鍵映射到實(shí)例,則獲取FS實(shí)例,如果沒有找到FS,則創(chuàng)建并初始化FS。
* 如果這是映射中的第一個條目,并且JVM沒有關(guān)閉,那么它會注冊一個關(guān)閉鉤子來關(guān)閉文件系統(tǒng),并將這個FS添加到{@code toAutoClose}集合中,
* 如果{@code " FS .automatic。Close "}在配置中設(shè)置(默認(rèn)為true)。
*/
private FileSystem getInternal(URI uri, Configuration conf, Key key)
throws IOException{
FileSystem fs;
synchronized (this) {
fs = map.get(key);
}
if (fs != null) {
return fs;
}
fs = createFileSystem(uri, conf);
final long timeout = conf.getTimeDuration(SERVICE_SHUTDOWN_TIMEOUT,
SERVICE_SHUTDOWN_TIMEOUT_DEFAULT,
ShutdownHookManager.TIME_UNIT_DEFAULT);
synchronized (this) { // refetch the lock again
FileSystem oldfs = map.get(key);
if (oldfs != null) { // a file system is created while lock is releasing
fs.close(); // close the new file system
return oldfs; // return the old file system
}
// now insert the new file system into the map
if (map.isEmpty()
&& !ShutdownHookManager.get().isShutdownInProgress()) {
ShutdownHookManager.get().addShutdownHook(clientFinalizer,
SHUTDOWN_HOOK_PRIORITY, timeout,
ShutdownHookManager.TIME_UNIT_DEFAULT);
}
fs.key = key;
map.put(key, fs);
if (conf.getBoolean(
FS_AUTOMATIC_CLOSE_KEY, FS_AUTOMATIC_CLOSE_DEFAULT)) {
toAutoClose.add(key);
}
return fs;
}
}
}
我們服務(wù)的fs.%s.impl.disable.cache并沒有開啟,因此肯定有使用cache。所以問題很可能是Cache的key判斷有問題。
/** FileSystem.Cache.Key */
static class Key {
final String scheme;
final String authority;
final UserGroupInformation ugi;
final long unique; // an artificial way to make a key unique
Key(URI uri, Configuration conf) throws IOException {
this(uri, conf, 0);
}
Key(URI uri, Configuration conf, long unique) throws IOException {
scheme = uri.getScheme()==null ?
"" : StringUtils.toLowerCase(uri.getScheme());
authority = uri.getAuthority()==null ?
"" : StringUtils.toLowerCase(uri.getAuthority());
this.unique = unique;
this.ugi = UserGroupInformation.getCurrentUser();
}
@Override
public int hashCode() {
return (scheme + authority).hashCode() + ugi.hashCode() + (int)unique;
}
static boolean isEqual(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Key) {
Key that = (Key)obj;
return isEqual(this.scheme, that.scheme)
&& isEqual(this.authority, that.authority)
&& isEqual(this.ugi, that.ugi)
&& (this.unique == that.unique);
}
return false;
}
@Override
public String toString() {
return "("+ugi.toString() + ")@" + scheme + "://" + authority;
}
}
我們使用 Mat工具 看下對象對比 scheme ,authority,ugi 的 哈希值
查看外部應(yīng)用對象
|
我們集群開啟了kerberos配置,可以看到 UGI對象的 hashCode不一樣。
我們來看下 UGI類?
public static FileSystem get(final URI uri, final Configuration conf,
final String user) throws IOException, InterruptedException {
String ticketCachePath =
conf.get(CommonConfigurationKeys.KERBEROS_TICKET_CACHE_PATH);
//這里每次獲取的ugi的hashcode都不一樣
UserGroupInformation ugi =
UserGroupInformation.getBestUGI(ticketCachePath, user);
return ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {
@Override
public FileSystem run() throws IOException {
return get(uri, conf);
}
});
}
// UserGroupInformation.getBestUGI
public static UserGroupInformation getBestUGI(
String ticketCachePath, String user) throws IOException {
if (ticketCachePath != null) {
return getUGIFromTicketCache(ticketCachePath, user);
} else if (user == null) {
return getCurrentUser();
} else {
//最終走到這里
return createRemoteUser(user);
}
}
// UserGroupInformation.createRemoteUser
public static UserGroupInformation createRemoteUser(String user, AuthMethod authMethod)
{
if (user == null || user.isEmpty()) {
throw new IllegalArgumentException("Null user");
}
Subject subject = new Subject();
subject.getPrincipals().add(new User(user));
UserGroupInformation result = new UserGroupInformation(subject);
result.setAuthenticationMethod(authMethod);
return result;
}
雖然使用cache,但是由于Key的判斷問題,所以基本每次請求都會生成一個新的實(shí)例,就會出現(xiàn)內(nèi)存泄露的問題。
ugi對象的不同是由于我們獲取FileSystem時指定了用戶有關(guān)?,我們開啟了 Kereros 而且指定了用戶,根據(jù)下圖的 UGI 對象可以看出每次請求都會生成一個新的實(shí)例,就會出現(xiàn)內(nèi)存泄露的問題。。
最后我們再看下程序的DEBUG日志:
2022-12-22 09:49:35 INFO [main] (HDFS.java:384) - FileSystem is
2022-12-22 09:49:35 DEBUG [IPC Parameter Sending Thread #0] (Client.java:1122) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM sending #9 org.apache.hadoop.hdfs.protocol.ClientProtocol.getFileInfo
2022-12-22 09:49:35 DEBUG [IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM] (Client.java:1176) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM got value #9
2022-12-22 09:49:35 DEBUG [main] (ProtobufRpcEngine.java:249) - Call: getFileInfo took 1ms
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:254) - hadoop login
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:187) - hadoop login commit
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:199) - using kerberos user:winner_spark@WINNER.COM
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:221) - Using user: "winner_spark@WINNER.COM" with name winner_spark@WINNER.COM
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:235) - User entry: "winner_spark@WINNER.COM"
2022-12-22 09:49:35 INFO [main] (UserGroupInformation.java:1009) - Login successful for user winner_spark@WINNER.COM using keytab file /etc/security/keytabs/winner_spark.keytab
2022-12-22 09:49:35 DEBUG [IPC Parameter Sending Thread #0] (Client.java:1122) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM sending #10 org.apache.hadoop.hdfs.protocol.ClientProtocol.getListing
2022-12-22 09:49:35 DEBUG [IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM] (Client.java:1176) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM got value #10
2022-12-22 09:49:35 DEBUG [main] (ProtobufRpcEngine.java:249) - Call: getListing took 15ms
2022-12-22 09:49:35 DEBUG [IPC Parameter Sending Thread #0] (Client.java:1122) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM sending #11 org.apache.hadoop.hdfs.protocol.ClientProtocol.getListing
2022-12-22 09:49:35 DEBUG [IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM] (Client.java:1176) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM got value #11
2022-12-22 09:49:35 DEBUG [main] (ProtobufRpcEngine.java:249) - Call: getListing took 10ms
2022-12-22 09:49:35 INFO [main] (HDFS.java:279) - Get a list of files and sort them by access time!
2022-12-22 09:49:35 INFO [main] (FtpUtilDS.java:192) - ----------- not exist rename failure file ---------------
程序DEBUG 日志 也顯示每次IPC 遍歷HDFS的文件的時候kerberos 還會進(jìn)行一次登錄。雖然fileSystem使用cache,但是由于Key的判斷問題,所以基本每次請求都會生成一個新的實(shí)例,就會出現(xiàn)內(nèi)存泄露的問題。
可以得出如果指定了用戶,每次都會構(gòu)造一個新的Subject,因此計算出來的UserGroupInformation的hashcode不一樣。這樣也最終導(dǎo)致FileSystem的Cache不生效。
?基于以上的分析,我將過去的舊代碼做稍微修改:
Configuration conf = null;
FileSystem fileSystem = null;
....
finally {
if (fileSystem != null) {
fileSystem.close();
conf.clear();
}
}
我們再看 程序運(yùn)行了?兩個多月沒有重啟,查看 堆內(nèi)存發(fā)現(xiàn) 在新生代大部分的對象被回收,而且老年代占用內(nèi)存持續(xù)維持在? 10% 以下,至此問題解決。文章來源:http://www.zghlxwxcb.cn/news/detail-433366.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-433366.html
?感謝點(diǎn)贊和關(guān)注!
到了這里,關(guān)于HDFS FileSystem 導(dǎo)致的內(nèi)存泄露的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!