Android性能调优篇之探索JVM内存分配
开篇废话
今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础。
一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析。
欢迎访问我的个人博客:senduo's blog
希望能在我们平时开发写代码的时候,能够知道当前写的这段代码,内存方面是如何分配的。
我们深知,一个Java程序员在很多时候根本不用操心内存的释放,而是依靠JVM去管理,以前写C++代码的时候,却要时刻记着new的空间要及时释放掉,不然程序很容易出现内存溢出的情况。因为,Java在这方面确实方便了许多,让我们有更多精力去考虑业务方面的实现。但是,这并不意味着我们就能肆无忌惮的使用内存,因为:
1.JVM并不会及时的去清理内存
2.我们无法通过代码去控制JVM去清理内存
这就要求我们平时在开发过程中,要了解JVM的垃圾回收机制,合理安排内存。
那么怎么样才能合理安排内存呢?那么就需要我们了解JVM的内存分配机制,而后才能真正控制好,让程序运行在我们鼓掌之中。
技术详情
1.JVM内存模型
平时我们对于Java内存都有一个比较粗略的概念,就是分堆和栈,但实际上还是复杂得多,以下给出完整内存模型:

相对应区域的内容为:

1.1程序计数器PC
这一个区域我概括了以下几个要点:
1.这一区域不会出现OOM(Out Of Memory)错误的情况
2.属于线程私有,因为每一个线程都有自己的一个程序计数器,来表示当前线程执行的字节码行号
3.标识Java方法的字节码地址,而不是Native方法
4.处于CPU上,我们无法直接操作这块区域
1.2虚拟机栈
这个区域也是我们平时口中说的堆栈的栈,关于这个块区域有如下要点:
1.属于线程私有,与线程的生命周期相同
2.每一个java方法被执行的时候,这个区域会生成一个栈帧
4.栈帧中存放的局部变量有8种基本数据类型,以及引用类型(对象的内存地址)
5.java方法的运行过程就是栈帧在虚拟机栈中入栈和出栈的过程
6.当线程请求的栈的深度超出了虚拟机栈允许的深度时,会抛出StackOverFlow的错误
7.当Java虚拟机动态扩展到无法申请足够内存时会抛出OutOfMemory的错误
1.3本地方法栈
这个区域,属于线程私有,顾名思义,区别于虚拟机栈,这里是用来处理Native方法(Java本地方法)的,而虚拟机栈是处理Java方法的。对于Native方法,Object中就有不少的Native的方法,hashCode,wait等,这些方法的执行很多时候都是借助于操作系统。
这一区域也有可能抛出StackOverFlowError 和 OutOfMemoryError
1.4 Java堆
我们平时说得最多,关注得最多的一个区域,就是他了。我们后期进行的性能优化主要针对这部分内存,GC的主战场,这个地方存放的几乎所有的对象实例和数组数据。这里我大概进行了如下概括:
1.Java堆属于线程共享区域,所有的线程共享这一块内存区域
2.从内存回收角度,Java堆可被分为新生代和老年代,这样分能够更快的回收内存
3.从内存分配角度,Java堆可划分出线程私有的分配缓存区(Thread Local Allocation Buffer,TLAB),这样能够更快的分配内存
4.当Java虚拟机动态扩展到无法申请足够内存时会抛出OutOfMemory的错误
1.5 方法区
方法区主要存放的是已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。GC在该区域出现的比较少。概括如下:
1.方法区属于线程共享区域
2.习惯性加他永久代
3.垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载
4.常量池用于存放编译期生成的各种字节码和符号引用,常量池具有一定的动态性,
里面可以存放编译期生成的常量
5.运行期间的常量也可以添加进入常量池中,比如string的intern()方法。
1.6 运行时常量池
运行时常量池也是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。单独拿出来说明一下,是因为我们平时使用String比价多,涉及到这一块的知识,但这一块区域不会抛出OutOfMemoryError
2.JVM内存源码示例说明
首先写了一个main方法,来做演示,代码如下:
package senduo.com.memory.allocate;
/**
* *****************************************************************
* * 文件作者:ouyangshengduo
* * 创建时间:2017/8/11
* * 文件描述:内存分配调用过程演示代码
* * 修改历史:2017/8/11 9:39*************************************
**/
public class MemoryAllocateDemo {
public static void main(String[] args){ //JVM自动寻找main方法
/**
* 执行第一句代码,创建一个Test实例test,在栈中分配一块内存,存放一个指向堆区实例对象的指针
*/
Test test = new Test();
/**
* 执行第二句代码,声明定义一个int型变量(8种基本数据类型),在栈区直接分配一块内存存储这个变量的值
*/
int date = 9;
/**
* 执行第三句代码,创建一个BirthDate实例bd1,在栈中分配一块内存,存放一个指向堆区实例对象的指针
*/
BirthDate bd1 = new BirthDate(13,6,1991);
/**
* 执行第四句代码,创建一个BirthDate实例bd2,在栈中分配一块内存,存放一个指向堆区实例对象的指针
*/
BirthDate bd2 = new BirthDate(30,4,1991);
/**
* 执行第五句代码,方法test1入栈帧,执行完出栈
*/
test.test1(date);
/**
* 执行第六句代码,方法test2入栈帧,执行完出栈
*/
test.test2(bd1);
/**
* 执行第七句代码,方法test3入栈帧,执行完出栈
*/
test.test3(bd2);
}
}
演示过程一
1.JVM自动寻找main方法,执行第一句代码,创建一个Test类的实例test,
在栈中分配一块内存,存放一个指向堆区对象的指针110925。
2.创建一个int型的变量date,由于是基本类型,直接在栈中存放date对应的值9。
3.创建两个BirthDate类的实例bd1、bd2,在栈中分别存放了对应的指针指向各自的对象
,他们在实例化时调用了有参数的构造方法,因此对象中有自定义初始值。
图解如下:

演示过程二
1.test1方法入栈帧,以date为参数
2.value为局部变量,把value放在栈中,并且把date的值赋值给value
3.把123456赋值给value局部变量
4.test1方法执行完,value内存被释放,test1方法出栈



演示过程三
1.test2方法入栈帧,以实例bd1为参数
2.birthDate为局部变量,把birthDate放在栈中,把bd1的引用的值赋值给birthDate,
也就是bd1与birthDate的地址都是指向同一个堆区的实例
3.在堆区new了一个对象,并且把这个堆区的指针保存在栈区中birthDate对应的内存空
间,这个时候,bd1与birthDate指向了不同的堆区,那么birthDate的改变,并不会对
bd1造成影响
4.test2方法执行完,栈中的birthDate空间被释放,test2方法出栈,但堆区的内存空间
则要等待系统自动回收



演示过程四
1.test3方法入栈帧,以实例bd2为参数
2.birthDate为局部变量,把birthDate放在栈中,把bd2的引用的值赋值给birthDate,
也就是bd2与birthDate的地址都是指向同一个堆区的实例
3.调用birthDate的setDay方法,因为birthDate与bd2指向的是同一个对象,也就是bd2调用了setDay方法,所以,也会bd2造成影响
4.test3方法执行完,栈中的birthDate空间被释放,test3方法出栈



3.JVM内存分配小结
跟着上面四个步骤,走一遍,会发现其实也不会那么复杂,掌握思想就能摸到门路了,我们平时注意区分一下基本数据类型的变量和引用数据类型变量,以下进行了几点概括:
1.局部变量中的基本数据类型的值直接存栈中
2.局部变量中的引用数据类型在栈中存的是引用类型的指针(地址)
3.栈中的数据与堆中的数据内存回收并不是同步的,栈中的只要方法运行完,就会直接
销毁局部变量,但堆中的对象不一定立即销毁
4.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中
)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被
压入栈,方法不使用则不占用内存
干货总结
终于把JVM内存分配的分享写完了,一路写下来,确实对内存分配又深入了解了一次。期间参考了以下博客:
通过对JVM内存模型的认识后,下一章将进行JVM垃圾回收机制的探索。
作者:进击的欧阳
链接:http://www.jianshu.com/p/88f9993acce1
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Android性能调优篇之探索JVM内存分配的更多相关文章
- Android性能调优篇之探索垃圾回收机制
开篇废话 如果我们想要进行内存优化的工作,还是需要了解一下,但这一块的知识属于纯理论的,有可能看起来会有点枯燥,我尽量把这一篇的内容按照一定的逻辑来走一遍.首先,我们为什么要学习垃圾回收的机制,我大概 ...
- JVM性能调优(1) —— JVM内存模型和类加载运行机制
一.JVM内存模型 运行一个 Java 应用程序,必须要先安装 JDK 或者 JRE 包.因为 Java 应用在编译后会变成字节码,通过字节码运行在 JVM 中,而 JVM 是 JRE 的核心组成部分 ...
- Spark性能调优篇七之JVM相关参数调整
降低cache操作的内存占比 方案: 通过SparkConf.set("spark.storage.memoryFraction","0.6")来设定.默认是0 ...
- 【Java/Android性能优3】Android性能调优工具TraceView使用介绍
本文转自:http://blog.csdn.net/innost/article/details/9008691 在软件开发过程中,想必很多读者都遇到过系统性能问题.而解决系统性能问题的几个主要步骤是 ...
- 性能调优案例分享:jvm crash的原因 1
性能调优案例分享:jvm crash的原因 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq: ...
- 【Java/Android性能优2】Android性能调优工具TraceView介绍
本文参考:http://www.trinea.cn/android/android-traceview/ Android自带的TraceView堪比java的性能调优工具visualvm线程视图,可以 ...
- Android性能调优实例
本文主要分享自己在appstore项目中的性能调优点,包括同步改异步.缓存.Layout优化.数据库优化.算法优化.延迟执行等. 目前性能优化专题已完成以下部分: 性能优化总纲——性能问题及性能调优方 ...
- 【Java/Android性能优化1】Android性能调优
本文参考:http://www.trinea.cn/android/android-performance-demo/ 本文主要分享自己在appstore项目中的性能调优点,包括同步改异步.缓存.La ...
- Android性能调优
本文主要分享自己在appstore项目中的性能调优点,包括同步改异步.缓存.Layout优化.数据库优化.算法优化.延迟执行等.一.性能瓶颈点整个页面主要由6个Page的ViewPager,每个Pag ...
随机推荐
- BZOJ 4823: [Cqoi2017]老C的方块
分析: 我觉得我的网络流白学了...QAQ... 其实数据范围本是无法用网络流跑过去的,然而出题者想让他跑过去,也就跑过去了... 看到题目其实感觉很麻烦,不知道从哪里入手,那么仔细观察所给出的有用信 ...
- 【HDU】6146 Pokémon GO
[题意]一个2*n的网格,再保证步数最少的情况下,求从任意格出发遍历完所有格的方案数,格子八连通.n<=10000,T<=100. [算法]递推,DP [题解]原题链接:蓝桥杯 格子刷油漆 ...
- NYOJ 42 一笔画问题 (并查集+欧拉回路 )
题目链接 描述 zyc从小就比较喜欢玩一些小游戏,其中就包括画一笔画,他想请你帮他写一个程序,判断一个图是否能够用一笔画下来. 规定,所有的边都只能画一次,不能重复画. 输入 第一行只有一个正整数 ...
- css的@符号的作用简单介绍
- Linux下进程描述(1)—进程控制块【转】
转自:http://www.cnblogs.com/33debug/p/6705391.html 进程概念介绍 进程是操作系统对运行程序的一种抽象. • 一个正在执行的程序: • 一个正在计算机上执行 ...
- SSM+Maven的JavaWeb项目中的异常的可能性
1.404 可能:1):被拦截了,即:springmvc中的controller可能不存在,可能没有被配置,可能配置出错 2):资源确实不存在 3):路径出错 2.500,程序异常,但是业务逻辑什么都 ...
- 让div垂直居中于浏览器窗口
<style type="text/css"> div { position:absolute; top:50%; left:50%; margin ...
- linux常用命令 ps
linux常用命令 ps Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些线程的快照,就是执行ps命令的那个时刻的那些进程 ...
- 第一篇:Hello World
Hello World RabbitMQ是一个消息代理:它接受和转发消息.你可以把它想象成一个邮局:当你把邮件放在邮箱里时,你可以确定邮差先生最终会把邮件发送给你的收件人.在这个比喻中,RabbitM ...
- poj 1947(树形DP+背包)
Rebuilding Roads Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 10663 Accepted: 4891 ...