Android内存优化1 了解java内存分配 1

开篇废话

今天我们一起来学习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内存分配的分享写完了,一路写下来,确实对内存分配又深入了解了一次。期间参考了以下博客:

Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

Java 内存分配全面浅析

Jvm内存模型

通过对JVM内存模型的认识后,下一章将进行JVM垃圾回收机制的探索。

原文地址:https://www.cnblogs.com/ldq2016/p/8469836.html

时间: 2024-10-10 02:41:08

Android内存优化1 了解java内存分配 1的相关文章

Android内存优化1 了解java GC 垃圾回收机制3

引言 接App优化之内存优化(序), 作为App优化系列中内存优化的一个小部分. 由于内存相关知识比较生涩, 内存优化中使用到的相关工具, 也有很多专有名词. 对Java内存管理, GC, Android内存管理, Dalvik/ART等知识有一个理论的认识, 可以让我们更好的使用这些工具, 分析内存问题. 据此, 我们就先从理论入手, 聊聊GC那些事儿. 1, 何为GC GC 是 garbage collection 的缩写, 垃圾回收的意思. 也可以是 Garbage Collector,

JVM性能优化系列-(1) Java内存区域

1. Java内存区域 1.1 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.主要包括:程序计数器.虚拟机栈.本地方法栈.Java堆.方法区(运 行时常量池).直接内存. 程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器.在虚拟机概念模型中,字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等

Android性能优化之常见的内存泄漏

前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary.MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查. 最近腾讯bu

Java内存管理:深入Java内存区域

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 对于从事C和C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的皇帝,又是从事最基础工作的劳动人民—既拥有每 一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任. 对于Java程序员来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,而且不容易出现 内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切

java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任. 对于Java程序员来说,不需要在为每一个new操作去写配对的delete/free,不容易出现内容泄漏和内存溢出错误,看起来由JVM管理内存一切都很美好.不过,也正是因为Java程序员把内存控制的

Android内存优化1 了解java GC 垃圾回收机制2 GC执行finalize的过程

1. finalize的作用 finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法. finalize()与C++中的析构函数不是对应的.C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性 不建议用finalize方法完成"非内存资源"的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象):② 作为确保某些非内存资源(如Socket.

Android性能优化:谈话Bitmap内存管理和优化

最近除了那些忙着项目开发的事情,目前正在准备我的论文.短的时间没有写博客,今晚难得想总结.只要有一点时间.因此,为了凑合用,行.唠叨罗嗦,直接进入正题. 从事Android自移动终端的发展,想必是常常要与内存问题打交道的,说到Android开发中遇到的内存问题,像Bitmap这样的吃内存的大户略微处理不当就非常easy造成OOM,当然,眼下已经有非常多知名的开源图片载入框架,比如:ImageLoader.Picasso等等,这些框架已经能够非常好的攻克了Bitmap造成的OOM问题,尽管这些框架

Android 如何优化APP内存

极力推荐Android 开发大总结文章:欢迎收藏 程序员Android 力荐 ,Android 开发者需要的必备技能 随机存取存储器(RAM)在任何软件开发环境中都是非常有价值的资源,但对于物理内存经常受到限制的移动操作系统来说,它更有价值. 尽管Android运行时(ART)和Dalvik虚拟机都执行常规垃圾收集,但这并不意味着您可以忽略应用程序分配和释放内存的时间和位置. 您仍然需要避免引入内存泄漏,通常由静态成员变量中的对象引用引起,并在生命周期回调定义的适当时间释放任何引用对象. 本页面

【转】java内存分配和String类型的深度解析

一.引题 在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题.下面是本文将要涉及到的一些问题,如果读者对这些问题都了如指掌,则可忽略此文. 1.java内存具体指哪块内存?这块内存区域为什么要进行划分?是如何划分的?划分之后每块区域的作用是什么?如何设置各个区域的大小? 2.String类型在执行连接操作时,效率为什么会比StringBuffer或者StringBui