1.AnalysisExtraData.kt 包含原因,分析时间,当前页面信息
class AnalysisExtraData {
var reason: String? = null //reason原因
var usageSeconds: String? = null //时间
var currentPage: String? = null //当前页面
}
2.HeapReport上报的信息
public class HeapReport {
public RunningInfo runningInfo = new RunningInfo();
public List<GCPath> gcPaths = new ArrayList<>();//引用链gc path of suspected objects
public List<ClassInfo> classInfos = new ArrayList<>();//类及其实例数量Class's instances count list
public List<LeakObject> leakObjects = new ArrayList<>();//泄漏的对象
public Boolean analysisDone;//flag to record whether hprof is analyzed already.
public Integer reAnalysisTimes;//flag to record hprof reanalysis times.
//device and app running info
public static class RunningInfo {
//JVM info
public String jvmMax;//jvm max memory in MB jvm最大内存
public String jvmUsed;//jvm used memory in MB jvm已经用过多少了
//https://my.oschina.net/u/4592355/blog/5004330
//https://www.cnblogs.com/liyuanhong/articles/7839762.html
//在linux下表示内存的耗用情况有四种不同的表现形式:
// VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
// RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
// PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
// USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
//memory info
public String vss;//vss memory in MB 进程vss
public String pss;//pss memory in MB 进程pss
public String rss;//rss memory in MB 进程rss
public String threadCount;//线程个数
public String fdCount; //fd个数
public List<String> threadList = new ArrayList<>();//线程信息
public List<String> fdList = new ArrayList<>(); //fd信息
//Device info
public String sdkInt; //sdk版本
public String manufacture; //厂商
public String buildModel; //版本
//App info
public String appVersion;
public String currentPage; //页面
public String usageSeconds; //耗费时间
public String nowTime; //上报时间
public String deviceMemTotal; //device内存
public String deviceMemAvaliable; //device可用内存
public String dumpReason;//heap dump trigger reason,dump原因
public String analysisReason;//analysis trigger reason,分析原因
//KOOM Perf data
public String koomVersion;
public String filterInstanceTime; //过滤泄漏对象所花的时间
public String findGCPathTime; //发现GC引用链时间
}
/**
* GC Path means path of object to GC Root, it can also be called as reference chain.
*/
public static class GCPath {
public Integer instanceCount;//todo instances number of same path to gc root
public String leakReason;//reason of why instance is suspected
public String gcRoot;
public String signature;//signature are computed by the sha1 of reference chain
public List<PathItem> path = new ArrayList<>();
//引用链Item
public static class PathItem {
String reference;//referenced instance's classname + filed name
String referenceType;//todo such as INSTANCE_FIELD/ARRAY_ENTRY/STATIC_FIELD
String declaredClass;//对于从祖先类继承字段的情况。for cases when filed is inherited from ancestor's class.
}
}
/**
* ClassInfo contains data which describes the instances number of the Class.
* ClassInfo 包含描述类的实例数量的数据。
*/
public static class ClassInfo {
public String className;
public String instanceCount;//类的实例数量All instances's count of this class.
public String leakInstanceCount;//All leaked instances's count of this class.
}
public static class LeakObject {
public String className;
public String size;
public String objectId;
public String extDetail;//todo
}
}
3.OOMReportUploader.kt 上传问题
interface OOMReportUploader {
/**
* 注意:外部调用完upload后,切记自行删除
*/
fun upload(file: File, content: String)
}
4.OOMHprofUploader 上传hprof
interface OOMHprofUploader {
enum class HprofType {
ORIGIN, STRIPPED
}
/**
* 注意:外部调用完upload后,切记自行删除
*/
fun upload(file: File, type: HprofType)
}
5.OOMPreferenceManager 保存分析次数,第一次启动时间
internal object OOMPreferenceManager {
private const val PREFERENCE_NAME = "koom_hprof_analysis"
private val mPreferences by lazy { mSharedPreferencesInvoker(PREFERENCE_NAME) }
private lateinit var mSharedPreferencesInvoker: (String) -> SharedPreferences
private lateinit var mPrefix: String
fun init(sharedPreferencesInvoker: (String) -> SharedPreferences) {
mSharedPreferencesInvoker = sharedPreferencesInvoker
mPrefix = "${MonitorBuildConfig.VERSION_NAME}_"
}
//分析次数
fun getAnalysisTimes(): Int {
return mPreferences.getInt("${mPrefix}times", 0)
}
fun increaseAnalysisTimes() {
mPreferences.edit()
.also { clearUnusedPreference(mPreferences, it) }
.putInt("${mPrefix}times", mPreferences.getInt("${mPrefix}times", 0) + 1)
.apply()
}
//第一次启动时间
fun getFirstLaunchTime(): Long {
var time = mPreferences.getLong("${mPrefix}first_analysis_time", 0)
if (time == 0L) {
time = System.currentTimeMillis()
setFirstLaunchTime(time)
}
return time
}
fun setFirstLaunchTime(time: Long) {
if (mPreferences.contains("${mPrefix}first_analysis_time")) {
return
}
return mPreferences.edit()
.putLong("${mPrefix}first_analysis_time", time)
.apply()
}
//清除之前版本的
private fun clearUnusedPreference(//清除之前版本的
preferences: SharedPreferences,
editor: SharedPreferences.Editor
) {
for (key in preferences.allKeys) {
if (!key.startsWith(mPrefix)) {
editor.remove(key)
}
}
}
}
6.OOMFileManager 文件相关的
internal object OOMFileManager {
private const val TIME_FORMAT = "yyyy-MM-dd_HH-mm-ss_SSS"
private lateinit var mRootDirInvoker: (String) -> File//mRootDirInvoker表达式
private lateinit var mPrefix: String //前缀是个version
private lateinit var mRootPath: String //root路径
val rootDir by lazy {
if (this::mRootDirInvoker.isInitialized)//已初始化
mRootDirInvoker("oom")
else
File(mRootPath)
}
@JvmStatic
val hprofAnalysisDir by lazy { File(rootDir, "memory/hprof-aly").apply { mkdirs() } }
@JvmStatic
val manualDumpDir by lazy { File(rootDir, "memory/hprof-man").apply { mkdirs() } }
@JvmStatic
val threadDumpDir by lazy { File(hprofAnalysisDir, "thread").apply { mkdirs() } }
@JvmStatic
val fdDumpDir by lazy { File(hprofAnalysisDir, "fd").apply { mkdirs() } }
@JvmStatic
fun init(rootDirInvoker: (String) -> File) {//有两个init,如果有mRootDirInvoker,则以mRootDirInvoker为主
mRootDirInvoker = rootDirInvoker
mPrefix = "${MonitorBuildConfig.VERSION_NAME}_"
}
@JvmStatic
fun init(rootPath: String?) {//有两个init,如果没有有mRootDirInvoker,以mRootPath为主
if (rootPath != null) {
mRootPath = rootPath
}
mPrefix = "${MonitorBuildConfig.VERSION_NAME}_"
}
@JvmStatic
fun createHprofAnalysisFile(date: Date): File {
val time = SimpleDateFormat(TIME_FORMAT, Locale.CHINESE).format(date)
return File(hprofAnalysisDir, "$mPrefix$time.hprof").also {
hprofAnalysisDir.mkdirs()
}
}
@JvmStatic
fun createJsonAnalysisFile(date: Date): File {
val time = SimpleDateFormat(TIME_FORMAT, Locale.CHINESE).format(date)
return File(hprofAnalysisDir, "$mPrefix$time.json").also {
hprofAnalysisDir.mkdirs()
}
}
@JvmStatic
fun createHprofOOMDumpFile(date: Date): File {
val time = SimpleDateFormat(TIME_FORMAT, Locale.CHINESE).format(date)
return File(manualDumpDir, "$mPrefix$time.hprof").also {
manualDumpDir.mkdirs()
}
}
@JvmStatic
fun createDumpFile(dumpDir: File): File {
return File(dumpDir, "dump.txt").also {
dumpDir.mkdirs()
}
}
@JvmStatic
fun isSpaceEnough(): Boolean {
//https://blog.csdn.net/suyimin2010/article/details/86680731
//getPath() 方法跟创建 File 对象时传入的路径参数有关,返回构造时传入的路径
//getAbsolutePath() 方法返回文件的绝对路径,如果构造的时候是全路径就直接返回全路径,如果构造时是相对路径,就返回当前目录的路径 + 构造 File 对象时的路径
//getCanonicalPath() 方法返回绝对路径,会把 ..\ 、.\ 这样的符号解析掉
val statFs = StatFs(hprofAnalysisDir.canonicalPath)
val blockSize = statFs.blockSizeLong//文件系统上块的大小(以字节为单位)
val availableBlocks = statFs.availableBlocks.toLong()
return blockSize * availableBlocks > 1.2 * 1024 * 1024//todo 1.2G ? 1.2M
}
}
7.OOMHeapDumper dump有四种,simple + fork + strip + forkstrip
//dump有四种,simple+fork+strip+forkstrip
object OOMHeapDumper {
private const val TAG = "OOMHeapDumper"
private fun dump(dumper: HeapDumper) {
try {
MonitorLog.i(TAG, "dump hprof start")
val hprofFile = OOMFileManager.createHprofOOMDumpFile(Date())
val start = System.currentTimeMillis()
hprofFile.createNewFile()
dumper.dump(hprofFile.absolutePath)
val end = System.currentTimeMillis()
MonitorLog.i(TAG, "dump hprof complete," +
" dumpTime:" + (end - start) +
" fileName:" + hprofFile.name +
" origin fileSize:" + SizeUnit.BYTE.toMB(hprofFile.length()) +
" JVM max memory:" + SizeUnit.BYTE.toMB(Runtime.getRuntime().maxMemory()) +
" JVM free memory:" + SizeUnit.BYTE.toMB(Runtime.getRuntime().freeMemory()) +
" JVM total memory:" + SizeUnit.BYTE.toMB(Runtime.getRuntime().totalMemory()), true)
} catch (e: Throwable) {
e.printStackTrace()
MonitorLog.i(TAG, "dumpStripHprof failed: ${e.message}")
}
}
@JvmStatic
fun simpleDump() {
MonitorLog.i(TAG, "simpleDump")
dump(StandardHeapDumper())
}
@JvmStatic
fun forkDump() {
MonitorLog.i(TAG, "forkDump")
dump(ForkJvmHeapDumper())
}
@JvmStatic
fun stripDump() {
MonitorLog.i(TAG, "dumpStripHprof")
dump(StripHprofHeapDumper())
}
@JvmStatic
fun forkDumpStrip() {
MonitorLog.i(TAG, "forkDumpStrip")
dump(ForkStripHeapDumper())
}
}
8.OOMMonitorConfig配置
class OOMMonitorConfig(
val analysisMaxTimesPerVersion: Int, //每个版本最多分析次数
val analysisPeriodPerVersion: Int, //每个版本的前15天才分析,超过这个时间段不再dump
val heapThreshold: Float, //heap阈值
val fdThreshold: Int, //fd阈值
val threadThreshold: Int, //线程阈值
val deviceMemoryThreshold: Float, //device memory阈值 还剩多少容量? todo
val maxOverThresholdCount: Int, //超过最大次数阈值 3次 todo
val forceDumpJavaHeapMaxThreshold: Float,//强制dump java heap最大阈值 0.9大于等于heapThreshold todo
val forceDumpJavaHeapDeltaThreshold: Int,//强制dump java heap delta阈值 Delta 三角洲 意思是增量 todo
val loopInterval: Long, //轮询间隔
val enableHprofDumpAnalysis: Boolean,//是否开启hprofdump分析
val hprofUploader: OOMHprofUploader?,//hprof上传
val reportUploader: OOMReportUploader?//日志上传
) : MonitorConfig<OOMMonitor>() {
class Builder : MonitorConfig.Builder<OOMMonitorConfig> {
companion object {
private val DEFAULT_HEAP_THRESHOLD by lazy {
val maxMem = SizeUnit.BYTE.toMB(Runtime.getRuntime().maxMemory())
when {
maxMem >= 512 - 10 -> 0.8f
maxMem >= 256 - 10 -> 0.85f
else -> 0.9f
}
}
private val DEFAULT_THREAD_THRESHOLD by lazy {
if (MonitorBuildConfig.ROM == "EMUI" && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
450
} else {
750
}
}
}
private var mAnalysisMaxTimesPerVersion = 5 //每个版本最多分析5次
private var mAnalysisPeriodPerVersion = 15 * 24 * 60 * 60 * 1000 //每个版本的前15天才分析,超过这个时间段不再dump
private var mHeapThreshold: Float? = null
private var mVssSizeThreshold = 3_650_000 //Only for 32 bit cpu devices. 360M todo 有用到吗
private var mFdThreshold = 1000 //fd阈值1000
private var mThreadThreshold: Int? = null //线程阈值
private var mDeviceMemoryThreshold: Float = 0.05f//todo
private var mForceDumpJavaHeapMaxThreshold = 0.90f//达到javaheap的90%的时候强制dump todo
private var mForceDumpJavaHeapDeltaThreshold = 350_000 //java heap rise 350m in a very short time.短时间增量350M的时候
private var mMaxOverThresholdCount = 3//超过3次
private var mLoopInterval = 15_000L //15s轮询一次
private var mEnableHprofDumpAnalysis = true //enable hprof analysis
private var mHprofUploader: OOMHprofUploader? = null
private var mReportUploader: OOMReportUploader? = null
fun setAnalysisMaxTimesPerVersion(analysisMaxTimesPerVersion: Int) = apply {
mAnalysisMaxTimesPerVersion = analysisMaxTimesPerVersion
}
fun setAnalysisPeriodPerVersion(analysisPeriodPerVersion: Int) = apply {
mAnalysisPeriodPerVersion = analysisPeriodPerVersion
}
/**
* @param heapThreshold: 堆内存的使用比例[0.0, 1.0]
*/
fun setHeapThreshold(heapThreshold: Float) = apply {
mHeapThreshold = heapThreshold
}
/**
* @param vssSizeThreshold: 单位是kb
*/
fun setVssSizeThreshold(vssSizeThreshold: Int) = apply {
mVssSizeThreshold = vssSizeThreshold
}
fun setFdThreshold(fdThreshold: Int) = apply {
mFdThreshold = fdThreshold
}
fun setThreadThreshold(threadThreshold: Int) = apply {
mThreadThreshold = threadThreshold
}
fun setMaxOverThresholdCount(maxOverThresholdCount: Int) = apply {
mMaxOverThresholdCount = maxOverThresholdCount
}
fun setLoopInterval(loopInterval: Long) = apply {
mLoopInterval = loopInterval
}
fun setEnableHprofDumpAnalysis(enableHprofDumpAnalysis: Boolean) = apply {
mEnableHprofDumpAnalysis = enableHprofDumpAnalysis
}
fun setDeviceMemoryThreshold(deviceMemoryThreshold: Float) = apply {
mDeviceMemoryThreshold = deviceMemoryThreshold
}
fun setForceDumpJavaHeapDeltaThreshold(forceDumpJavaHeapDeltaThreshold: Int) = apply {
mForceDumpJavaHeapDeltaThreshold = forceDumpJavaHeapDeltaThreshold
}
fun setForceDumpJavaHeapMaxThreshold(forceDumpJavaHeapMaxThreshold: Float) = apply {
mForceDumpJavaHeapMaxThreshold = forceDumpJavaHeapMaxThreshold
}
fun setHprofUploader(hprofUploader: OOMHprofUploader) = apply {
mHprofUploader = hprofUploader
}
fun setReportUploader(reportUploader: OOMReportUploader) = apply {
mReportUploader = reportUploader
}
override fun build() = OOMMonitorConfig(
analysisMaxTimesPerVersion = mAnalysisMaxTimesPerVersion,
analysisPeriodPerVersion = mAnalysisPeriodPerVersion,
heapThreshold = mHeapThreshold ?: DEFAULT_HEAP_THRESHOLD,
fdThreshold = mFdThreshold,
threadThreshold = mThreadThreshold ?: DEFAULT_THREAD_THRESHOLD,
deviceMemoryThreshold = mDeviceMemoryThreshold,
maxOverThresholdCount = mMaxOverThresholdCount,
loopInterval = mLoopInterval,
enableHprofDumpAnalysis = mEnableHprofDumpAnalysis,
forceDumpJavaHeapMaxThreshold = mForceDumpJavaHeapMaxThreshold,
forceDumpJavaHeapDeltaThreshold = mForceDumpJavaHeapDeltaThreshold,
hprofUploader = mHprofUploader,