JVM执行Java程序的过程中,会使用到各种数据区域,这些区域有各自的用途、创建和销毁时间。JVM包括下列几个运行时数据区域:
1.程序计数器(Program Counter Register):
此内存区域是唯一一个在VM Spec中没有规定任何OutOfMemoryError情况的区域。
2.Java虚拟机栈(Java Virtual Machine Stacks):
在VM Spec中对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。
3.本地方法栈(Native Method Stacks):
和VM栈一样,这个区域也会抛出StackOverflowError和OutOfMemoryError异常。
4.Java堆(Java Heap)
对于绝大多数应用来说,Java堆是虚拟机管理最大的一块内存。Java堆是被所有线程共享的,在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例(以及数组),绝大部分的对象实例都在这里分配。
Java堆内还有更细致的划分:新生代、老年代,再细致一点的:eden、from survivor、to survivor,甚至更细粒度的本地线程分配缓冲(TLAB)等。
根据VM Spec的要求,Java堆可以处于物理上不连续的内存空间,它逻辑上是连续的即可,就像我们的磁盘空间一样。实现时可以选择实现成固定大小的,也可以是可扩展的,不过当前所有商业的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中无法分配内存,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
5.方法区(Method Area)
6.运行时常量池(Runtime Constant Pool):
运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError异常。
StrictMode:严苛模式,主要用来检测程序中几种违例情况的Android原生开发者工具。 主要分为ThreadPolicy(线程检测策略)跟VmPolicy(虚拟机检测策略)两类检测内容:
可以按照不同场景需求选择检测不同违规操作。推荐在Application onCreate()方法中集成:
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()//线程策略(ThreadPolicy)
.detectDiskReads()//检测在UI线程读磁盘操作
.detectDiskWrites()//检测UI线程写磁盘操作
.detectCustomSlowCalls()//发现UI线程调用的哪些方法执行得比较慢
.detectResourceMismatches()//最低版本为API23 发现资源不匹配
.detectNetwork() //检测在UI线程执行网络操作
.penaltyDialog()//一旦检测到弹出Dialog
.penaltyDropBox()//一旦检测到将信息存到DropBox文件夹中 data/system/dropbox
.penaltyLog()//一旦检测到将信息以LogCat的形式打印出来
.permitDiskReads()//允许UI线程在磁盘上读操作
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()//虚拟机策略(VmPolicy)
.detectActivityLeaks()//最低版本API11 用户检查 Activity 的内存泄露情况
.detectCleartextNetwork()//最低版本为API23 检测明文的网络
.detectFileUriExposure()//最低版本为API18 检测file://或者是content://
.detectLeakedClosableObjects()//最低版本API11 资源没有正确关闭时触发
.detectLeakedRegistrationObjects()//最低版本API16 BroadcastReceiver、ServiceConnection是否被释放
.detectLeakedSqlLiteObjects()//最低版本API9 资源没有正确关闭时回触发
.setClassInstanceLimit(TestLeakActivity.class, 2)//设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露
.penaltyLog()
.build());
}
由上面StrictMode API可知,检测到违规代码时,我们可以选择以应用崩溃/弹框/日志输出等形式表现出来。一般推荐输出到日志即可。下面简单列举下执行到代码中违规操作时日志表现:
2019-05-06 19:51:25.523 22941-22951/com.whh.strictmode E/StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'close' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:180)
at java.io.FileOutputStream.(FileOutputStream.java:222)
at java.io.FileOutputStream.(FileOutputStream.java:169)
at com.whh.strictmode.MainActivity.testReadWrite(MainActivity.java:83)
at com.whh.strictmode.MainActivity.onClick(MainActivity.java:53)
2019-05-06 19:52:48.467 23148-23148/com.whh.strictmode E/WindowManager: android.view.WindowLeaked: Activity com.whh.strictmode.leakactivity.TestLeakActivity has leaked window DecorView@86a60b[] that was originally added here
at android.view.ViewRootImpl.(ViewRootImpl.java:418)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:331)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.app.Dialog.show(Dialog.java:322)
at com.whh.strictmode.leakactivity.TestLeakActivity.onCreate(TestLeakActivity.java:38)
2019-05-06 19:53:18.916 23148-23148/com.whh.strictmode E/StrictMode: class com.whh.strictmode.leakactivity.TestLeakActivity; instances=2; limit=1
android.os.StrictMode$InstanceCountViolation: class com.whh.strictmode.leakactivity.TestLeakActivity; instances=2; limit=1
at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 51916812 byte allocation with 4188384 free bytes and 24MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:620)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:660)
at com.whh.strictmode.MainActivity.testBitmap(MainActivity.java:105)
at com.whh.strictmode.MainActivity.onClick(MainActivity.java:53)
推荐用try-with-resource语句–更优雅的关闭资源。
(JDK7及其之后的资源关闭方式)
try-with-resource并不是JVM虚拟机的新增功能,只是JDK实现了一个语法糖,实际实现原理还是try-catch-finally。
以前用try-catch-finally使用I/O流代码如下:
//try-catch外部定义I/O流
InputStream is = null;
OutputStream os = null;
try {
//try括号中I/O赋值
is = new FileInputStream(src);
os = new FileOutputStream(dst);
//...进行一些I/O操作
} catch (Exception e) {
} finally {
//finally中关闭I/O流
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
}
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
}
}
这种方式代码繁杂不说,有时候还容易忘记关闭输入输出流。而换成try-with-resource语句后,代码如下:
try (InputStream is = new FileInputStream(src);
OutputStream os = new FileOutputStream(dst)) {
//...进行一些I/O操作
} catch (Exception e) {
}
这种方式优雅简洁,而且不用担心忘记关闭输入输出流,因为try()括号中的I/O流会自动关闭
//1、利用inSampleSize进行图片采样
BitmapFactory.Options options = new BitmapFactory.Options();
//inSampleSize越大,压缩率越大。inSampleSize==1时表示原图
//当确定图片大小跟需要的图片大小时,可以计算出采样率进行图片压缩
options.inSampleSize = 2;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
//2、利用inSampleSize进行图片采样
BitmapFactory.Options options = new BitmapFactory.Options();
//当对图片透明度需求不高时,可以将图片颜色质量设为RGB_565
options.inPreferredConfig = Config.RGB_565;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
//3、利用inBitmap复用旧的Bitmap的内存,而不用重新分配以及销毁旧Bitmap,进而改善运行效率
BitmapFactory.Options options = new BitmapFactory.Options();
//mOldBitmap是一个旧的Bitmap,必须是mutable的(支持修改).新的bmp直接复用该mOldBitmap的内存空间
//使用条件是mOldBitmap占用的空间必须大于等于新bmp占用的内存空间
options.inBitmap = mOldBitmap;
Bitmap bmp = BitmapFactory.decodeFile(path, options);