精华内容
下载资源
问答
  • java内存分配

    2014-04-04 17:22:43
     在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题。下面是本文将要涉及到的一些问题,如果...

    一、引题

        在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题。下面是本文将要涉及到的一些问题,如果读者对这些问题都了如指掌,则可忽略此文。

        1、java内存具体指哪块内存?这块内存区域为什么要进行划分?是如何划分的?划分之后每块区域的作用是什么?如何设置各个区域的大小?

        2、String类型在执行连接操作时,效率为什么会比StringBuffer或者StringBuilder低?StringBuffer和StringBuilder有什么联系和区别?

        3、java中常量是指什么?String s = "s" 和 String s = new String("s") 有什么不一样?

        本文经多方资料的收集整理和归纳,最终撰写成文,如果有错误之处,请多多指教!

    二、java内存分配

        1、JVM简介
          Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境,它是Java 最具吸引力的特性之一。Java虚拟机有自己完善的硬体架构,如处理器堆栈寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
                一个运行时的Java虚拟机实例的天职是:负责运行一个java程序。当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。如果同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例。每个Java程序都运行于它自己的Java虚拟机实例中。
            如下图所示,JVM的体系结构包含几个主要的子系统和内存区:
                 垃圾回收器(Garbage Collection):负责回收堆内存(Heap)中没有被使用的对象,即这些对象已经没有被引用了。
                 类装载子系统(Classloader Sub-System):除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。
                 执行引擎(Execution Engine):负责执行那些包含在被装载类的方法中的指令。
                 运行时数据区(Java Memory Allocation Area):又叫虚拟机内存或者Java内存,虚拟机运行时需要从整个计算机内存划分一块内存区域存储许多东西。例如:字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数,返回值、局部变量等等。


        2、java内存分区
          从上节知道,运行时数据区即是java内存,而且数据区要存储的东西比较多,如果不对这块内存区域进行划分管理,会显得比较杂乱无章。程序喜欢有规律的东西,最讨厌杂乱无章的东西。 根据存储数据的不同,java内存通常被划分为5个区域:程序计数器(Program Count Register)、本地方法栈(Native Stack)、方法区(Methon Area)、栈(Stack)、堆(Heap)。
          程序计数器(Program Count Register):又叫程序寄存器。JVM支持多个线程同时运行,当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)。如果线程正在执行的是一个Java方法(非native),那么PC寄存器的值将总是指向下一条将被执行的指令,如果方法是 native的,程序计数器寄存器的值不会被定义。 JVM的程序计数器寄存器的宽度足够保证可以持有一个返回地址或者native的指针。
                栈(Stack):又叫堆栈。
    JVM为每个新创建的线程都分配一个栈。也就是说,对于一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。我们知道,某个线程正在执行的方法称为此线程的当前方法。我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧,这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算过程和其他数据。从Java的这种分配机制来看,堆栈又可以这样理解:栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。其相关设置参数:

    • -Xss --设置方法栈的最大值

              本地方法栈(Native Stack):存储本地方方法的调用状态。


              方法区(Method Area):当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息,然后把这些类型信息(包括类信息、常量、静态变量等)放到方法区中,该内存区域被所有线程共享,如下图所示。本地方法区存在一块特殊的内存区域,叫常量池(Constant Pool),这块内存将与String类型的分析密切相关。


              堆(Heap):Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。在此区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存,但是这个对象的引用却是在栈(Stack)中分配。因此,执行String s = new String("s")时,需要从两个地方分配内存:在堆中为String对象分配内存,在栈中为引用(这个堆对象的内存地址,即指针)分配内存,如下图所示。


                JAVA虚拟机有一条在堆中分配新对象的指令,却没有释放内存的指令,正如你无法用Java代码区明确释放一个对象一样。虚拟机自己负责决定如何以及何时释放不再被运行的程序引用的对象所占据的内存,通常,虚拟机把这个任务交给垃圾收集器(Garbage Collection)。其相关设置参数:

    • -Xms -- 设置堆内存初始大小
    • -Xmx -- 设置堆内存最大值
    • -XX:MaxTenuringThreshold -- 设置对象在新生代中存活的次数
    • -XX:PretenureSizeThreshold -- 设置超过指定大小的大对象直接分配在旧生代中

            Java堆是垃圾收集器管理的主要区域,因此又称为“GC 堆”(Garbage Collectioned Heap)。现在的垃圾收集器基本都是采用的分代收集算法,所以Java堆还可以细分为:新生代(Young Generation)和老年代(Old Generation),如下图所示。分代收集算法的思想:第一种说法,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍,以便让出更多的系统资源供应用系统使用;另一种说法,在分配对象遇到内存不足时,先对新生代进行GC(Young GC);当新生代GC之后仍无法满足内存空间分配需求时, 才会对整个堆空间以及方法区进行GC(Full GC)。


             

            在这里可能会有读者表示疑问:记得还有一个什么永久代(Permanent Generation)的啊,难道它不属于Java堆?亲,你答对了!其实传说中的永久代就是上面所说的方法区,存放的都是jvm初始化时加载器加载的一些类型信息(包括类信息、常量、静态变量等),这些信息的生存周期比较长,GC不会在主程序运行期对PermGen Space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen Space错误。其相关设置参数:

    • -XX:PermSize --设置Perm区的初始大小
    • -XX:MaxPermSize --设置Perm区的最大值

             新生代(Young Generation又分为:Eden区和Survivor区,Survivor区有分为From Space和To Space。Eden区是对象最初分配到的地方;默认情况下,From Space和To Space的区域大小相等。JVM进行Minor GC时,将Eden中还存活的对象拷贝到Survivor区中,还会将Survivor区中还存活的对象拷贝到Tenured区中。在这种GC模式下,JVM为了提升GC效率, 将Survivor区分为From Space和To Space,这样就可以将对象回收和对象晋升分离开来。新生代的大小设置有2个相关参数:

    • -Xmn -- 设置新生代内存大小。
    • -XX:SurvivorRatio -- 设置Eden与Survivor空间的大小比例

                老年代(Old Generation)  OLD 区空间不够时, JVM 会在 OLD 区进行 major collection 完全垃圾收集后,若SurvivorOLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现"Out of memory错误"  。


    转自:http://my.oschina.net/xiaohui249/blog/170013

    展开全文
  • Java内存分配

    千次阅读 2016-03-25 22:30:37
    而对于Java程序员来说,JVM自动进行内存管理,程序员不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄露和内存溢出问题。 但是,正因为JVM帮我们管理了内存,一旦出现内存泄露或溢出问题,如果...

    概述

    对从事C和C++的程序员来说,在内存管理方面,他们既是拥有最高权利的人,也是从事最基础工作的“劳动人民”。

    而对于Java程序员来说,JVM自动进行内存管理,程序员不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄露和内存溢出问题。

    但是,正因为JVM帮我们管理了内存,一旦出现内存泄露或溢出问题,如果不了解虚拟机是怎么管理内存的,那么排查错误会成为一项异常艰难的工作

    So, 小伙伴们,走起,让我带你们进入JVM的内存世界!


    运行时数据区域(Run-Time Data Areas)

    首先,叫“Java内存分配”并不是我本意,我是为了能让大家搜索进来才起的这个名字,Java(JVM)关于内存的管理是一个叫:“运行时数据区”的章节

    JVM在运行时(运行时就是执行Java程序时)根据《Java虚拟机规范(Java SE 7)》的规定,会包括如下几个运行时数据区域

    这里写图片描述


    1. 程序计数器(Program Counter Register)

    或者叫:程序计数寄存器、PC寄存器,学过计算机组成原理应该就懂

    a. 作用

    1. 程序计数器是一块较小的内存空间,可以看做是当前线程(Thread)所执行的字节码的行号指示器

      在JVM概念模型中(各种JVM可能并不依照此开发),解释器(Interpreter)就是通过改变这个计数器的值来选取下一条需要执行的字节码指令

    2. 为了在线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器。这种内存区域称为“线程私有”的内存

    3. 线程正在执行Java方法时,计数器记录的是虚拟机字节码指令的地址;如果执行的是Native方法,则计数器值为空(Undefined)

    b. Error

    这块内存是JVM规范中唯一没有规定任何OutOfMemoryError的区域


    2. 虚拟机栈(Virtual Machine Stacks)

    也是线程私有的,其生命周期和线程一样,每个Java线程有一个虚拟机栈

    a. 作用

    虚拟机栈描述的是Java方法执行的内存模型,即:每个方法在执行的时候都会创建一个栈帧(Stack Frame),栈帧中存储:

    1. 局部变量表

      1. 存放了编译期就可知的:各种基本数据类型(8个基本数据类型)、对象引用、returnAddress类型(指向一条字节码指令地址)
      2. 局部变量表所需的内存大小在编译期就完成了分配,也就是说当进入一个方法时,此方法需要在栈帧中分配多大的局部变量表空间时完全确定的,运行期不会改变
    2. 操作数栈

    3. 动态链接

    4. 方法出口等

    方法从调用到执行完成的过程,就对应了,一个栈帧在虚拟机栈中的入栈和出栈的过程

    b. Error

    有两种异常:
    1. 如果线程请求的栈深度大于JVM所允许的深度,将抛出StackOverflowError异常
    2. 如果栈扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常


    3. 本地方法栈(Native Method Stack)

    a. 作用

    作用和虚拟机栈基本一样,只不过这里为Native方法服务
    JVM规范没有强制规定本地方法栈中的方法使用的语言、使用方式、数据结构,所以具体JVM不同实现

    HotSpot虚拟机直接把虚拟机栈和本地方法栈合二为一了

    b. Error

    如1.2的虚拟机栈一样


    4. Java堆(Java Heap)

    基本上Java堆是虚拟机管理的内存中最大的一块。是被所有线程共享的一块区域,在虚拟机启动时创建,通过参数“-Xmx和-Xms”控制

    a. 作用

    所有对象实例和数组都要在堆上存放(例外的情况在我另一篇博客有描述JVM - JIT编译器 - 6.1
    Java堆是垃圾回收器管理的主要区域

    b. 分类

    下面是一些具体的细分,但是不论如何分类,其存储的仍然是对象实例,进一步划分的目的是为了更好的回收、更快的分配内存

    1. 从内存回收的角度看

    由于现代GC基本都采用分带收集算法,所以Java堆还可以细分为:

    1. 新生代
    2. 老年代

    再细分一下还可分为:

    1. Eden空间
    2. From Survivor空间
    3. To Survivor空间

    2. 从内存分配角度看

    线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)

    c. Error

    如过堆中内存不够继续进行实例分配,且堆也无法再扩展时,将会抛出OutOfMemoryError


    5. 方法区(Method Area)

    方法区和Java堆一样,被所有线程共享

    a. 作用

    用于存储已被虚拟机加载的

    1. 类信息(class metadata)
    2. 常量(包括interned Strings)
    3. 静态变量(类变量 class static variables)
    4. 即时编译器编译后的代码等

    虽然JVM规范把方法区描述为堆的一个逻辑部分,但是它确有一个别名叫Non-Heap,目的应该是与Java堆区分开来

    b. 永久代(HotSpot特有)

    加删除线的原因请看b.2. 现状
    对于使用HotSpot VM的程序员来说,很多人把方法区称之为“永久代(Permanent Generation)”

    为什么叫永久带?如1.4中所说,按内存回收的角度,有新生代、老年代,所以就有了这里的“永久代”。另外,其它虚拟机是没有永久代这个概念的

    本质上两者不等价,仅仅是因为HotSpot团队把GC分代收集扩展到了方法区(或者说用永久代这种方式来实现方法区),这样的话GC就可以像管理Java堆一样管理这部分内存,省去了为方法区编写内存管理代码的工作

    1. 坏处

    如何实现方法区JVM规范并没有强制规定,但是现在看来“永久代”并不是一个好主意

    1. 更容易遇到内存溢出问题
      因为有参数“-XX:MaxPermSize”的上限限制,其它虚拟机只要不达到进程可用内存上限,例如32系统的2GB,就不会出现内存溢出
    2. 有极少数方法(如String.intern()),会因此导致在不同的JVM下有不同表现

    2. 现状

    1. 在JDk1.7的HotSpot中,字符串常量池已经被从永久代中移除了
    2. 在Java8中,根据JEP122,永久带PermanentGeneration已经被从HotSpot中removed.
    3. 这是JDK1.8中JRockit和HotSpot合并的成果
    4. 一篇翻译自国外的译文:Java永久代去哪儿了

    c. 垃圾回收

    JVM规范对方法区限制非常宽松,甚至可以选择不实现垃圾收集

    但并不是如其“永久代”的名字一样,方法区的垃圾回收只是比较少出现。回收目标是:类信息的卸载、常量池的回收

    d. Error

    当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常


    6. 运行时常量池(Run-Time Constant Pool)

    运行时常量池其实是1.5方法区的一部分↑

    class文件中有一项信息是常量池表(constant_pool table),用于存放编译期生成的“字面量”和“符号引用”,这部分内容将在类加载后进入方法区的运行时常量池中(Run-Time Constant Pool)存放

    也就是说:每一个class都会根据constant_pool table 来1:1创建一个此class对应的Run-Time Constant Pool

    a. 作用

    1. 就是运行时所需要的常量数据的容器
    2. JVM规范对class文件的每一部分(包括constant_pool table)都有严格的规范,但是对于运行时常量池却没有做任何细节要求,不过一般来说,除了class文件中的符号引用外,直接引用也会存储在运行时常量池中
    3. 运行时常量池具备动态性,Java语言并没有要求常量一定只能编译期产生,运行期也可以将新常量放入池中。这个特性用的较多的便是String类的intern()方法

    b. Error

    既然运行时常量池是方法区的一部分,自然受到方法区限制,当运行时常量池无法再申请到内存时,将抛出OutOfMemoryError异常

    x. 字符串常量池 - String pool

    注意:这并不是Run-Time Constant Pool的一部分,放在这里只是为了能让大家在比较相似的地方找到

    与上面两个概念不一样,String pool是用来存储被驻留的字符串的(interned string),是JVM实例全局唯一的,被所用类共享

    HotSpot中实现string pool的方式是一个哈希表:StringTable,这个StringTable的value是字符串实例的引用,也就是说某个普通的字符串实例如果被纳入StringTable的引用之后就等同于被赋予了“interned string”的身份

    再有,类的运行时常量池的CONSTANT_STRING类型的常量,经过解析(resolve)后,同样是存字符串的引用,解析的过程中回去查询StringTable,来保证运行时常量池和StingTable所引用的字符串是一致的

    关于字符串部分,请看我的另一篇专门写字符串常量池的博文


    7. 直接内存(Direct Memory)

    直接内存不是JVM规范中定义的内存区域,也不是运行时数据区的内容
    (我现在理解为:直接控制的属于物理机的内存,不属于JVM线程使用的内存)
    但是,这部分内从也被频繁的使用,且可能导致OutOfMemoryError异常,所以罗列为1.7在这里叙述

    a. 缘由

    JDK1.4中加入的NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,这个类可以使用Native函数库直接分配堆外内存,然后通过一个存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场合显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

    b. Error

    直接内存不会受到Java堆大小限制,但是会受到本机总内存大小,以及处理器寻址空间的限制。JVM管理员在配置JVM参数时,会根据本机实际内存设置(如-Xmx等参数),但是经常忽略了要被使用的这一份“直接内存”。最终使得各个内存总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常



    TIPS

    1. 字面量(literal)

      int a;//a变量
      static final int b=10;//b为常量,10为字面量
      string str=”hello world”;//str为变量,hello world为也字面量
      字面量只能以右值的形式出现

    2. 符号引用

      符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用 项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样,对于其他类的符号引用必须给出 类的全名。对于其他类的字段,必须给出类名、字段名以及字段描述符。对于其他类的方法的引用必须给出 类名、方法名以及方法的描述符。


    参考文献:

    [ 1 ] 周志明.深入理解Java虚拟机[M].第2版.北京:机械工业出版社,2015.8.
    [ 2 ] Tim Lindholm,Frank Yellin,Gilad Bracha,Alex Buckley.The Java® Virtual Machine Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.
    [ 3 ] James Gosling,Bill Joy,Guy Steele,Gilad Bracha,Alex Buckley.The Java® Language Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.

    展开全文
  • Java 内存分配策略Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。静态存储区(方法区):主要...

    Java 内存分配策略

    Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。

    静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

    栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

    堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

    展开全文
  • Java 内存分配全面浅析

    万次阅读 多人点赞 2013-02-20 17:54:45
    本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。 进入正题前首先要知道的是Java程序运行在JVM...
    本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。

    进入正题前首先要知道的是Java程序运行在JVM(Java Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性。所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提。

    简单通俗的讲,一个完整的Java程序运行过程会涉及以下内存区域:

    l 寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

    l 栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

    l 堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

    l 常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中

    l 代码段:用来存放从硬盘上读取的源程序代码。

    l 数据段:用来存放static定义的静态成员。

    下面是内存表示图:



    上图中大致描述了Java内存分配,接下来通过实例详细讲解Java程序是如何在内存中运行的(注:以下图片引用自尚学堂马士兵老师的J2SE课件,图右侧是程序代码,左侧是内存分配示意图,我会一一加上注释)。

    预备知识:


    1.一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

    2.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

    示例:



    1.JVM自动寻找main方法,执行第一句代码,创建一个Test类的实例,在栈中分配一块内存,存放一个指向堆区对象的指针110925。

    2.创建一个int型的变量date,由于是基本类型,直接在栈中存放date对应的值9。

    3.创建两个BirthDate类的实例d1、d2,在栈中分别存放了对应的指针指向各自的对象。他们在实例化时调用了有参数的构造方法,因此对象中有自定义初始值。



    调用test对象的change1方法,并且以date为参数。JVM读到这段代码时,检测到i是局部变量,因此会把i放在栈中,并且把date的值赋给i。



    把1234赋给i。很简单的一步。



    change1方法执行完毕,立即释放局部变量i所占用的栈空间。



    调用test对象的change2方法,以实例d1为参数。JVM检测到change2方法中的b参数为局部变量,立即加入到栈中,由于是引用类型的变量,所以b中保存的是d1中的指针,此时b和d1指向同一个堆中的对象。在b和d1之间传递是指针。



    change2方法中又实例化了一个BirthDate对象,并且赋给b。在内部执行过程是:在堆区new了一个对象,并且把该对象的指针保存在栈中的b对应空间,此时实例b不再指向实例d1所指向的对象,但是实例d1所指向的对象并无变化,这样无法对d1造成任何影响。



    change2方法执行完毕,立即释放局部引用变量b所占的栈空间,注意只是释放了栈空间,堆空间要等待自动回收。



    调用test实例的change3方法,以实例d2为参数。同理,JVM会在栈中为局部引用变量b分配空间,并且把d2中的指针存放在b中,此时d2和b指向同一个对象。再调用实例b的setDay方法,其实就是调用d2指向的对象的setDay方法。



    调用实例b的setDay方法会影响d2,因为二者指向的是同一个对象。



    change3方法执行完毕,立即释放局部引用变量b。


    以上就是Java程序运行时内存分配的大致情况。其实也没什么,掌握了思想就很简单了。无非就是两种类型的变量:基本类型和引用类型。二者作为局部变量,都放在栈中,基本类型直接在栈中保存值,引用类型只保存一个指向堆区的指针,真正的对象在堆里。作为参数时基本类型就直接传值,引用类型传指针。

    小结:


    1.分清什么是实例什么是对象。Class a= new Class();此时a叫实例,而不能说a是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

    2.栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

    3.以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

    4.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

    以上分析只涉及了栈和堆,还有一个非常重要的内存区域:常量池,这个地方往往出现一些莫名其妙的问题。常量池是干嘛的上边已经说明了,也没必要理解多么深刻,只要记住它维护了一个已加载类的常量就可以了。接下来结合一些例子说明常量池的特性。

    预备知识:


    基本类型和基本类型的包装类。基本类型有:byteshortcharintlongboolean。基本类型的包装类分别是:ByteShortCharacterIntegerLongBoolean。注意区分大小写。二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。


    实例:

    public class test {
        public static void main(String[] args) {    
            objPoolTest();
        }
    
        public static void objPoolTest() {
            int i = 40;
            int i0 = 40;
            Integer i1 = 40;
            Integer i2 = 40;
            Integer i3 = 0;
            Integer i4 = new Integer(40);
            Integer i5 = new Integer(40);
            Integer i6 = new Integer(0);
            Double d1=1.0;
            Double d2=1.0;
            
            System.out.println("i=i0\t" + (i == i0));
            System.out.println("i1=i2\t" + (i1 == i2));
            System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));
            System.out.println("i4=i5\t" + (i4 == i5));
            System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));    
            System.out.println("d1=d2\t" + (d1==d2)); 
            
            System.out.println();        
        }
    }

     

    结果:

    i=i0    true
    i1=i2   true
    i1=i2+i3        true
    i4=i5   false
    i4=i5+i6        true
    d1=d2   false

     

    结果分析

    1.ii0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i40,不会再添加一个新的40

    2.i1i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer包装类实现了常量池技术,因此i1i240均是从常量池中获取的,均指向同一个地址,因此i1=12

    3.很明显这是一个加法运算,Java的数学运算都是在栈中进行的Java会自动对i1i2进行拆箱操作转化成整型,因此i1在数值上等于i2+i3

    4.i4i5均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4i5不相等,因为他们所存指针不同,所指向对象不同。

    5.这也是一个加法运算,和3同理。

    6.d1d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1d2存放的指针不同,指向的对象不同,所以不相等。

    小结:

    1.以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1i2就不相等了。

    2.String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

    凡是涉及内存原理,一般都是博大精深的领域,切勿听信一家之言,多读些文章。我在这只是浅析,里边还有很多猫腻,就留给读者探索思考了。希望本文能对大家有所帮助!

    脚注:

    (1) 符号引用,顾名思义,就是一个符号,符号引用被使用的时候,才会解析这个符号。如果熟悉linuxunix系统的,可以把这个符号引用看作一个文件的软链接,当使用这个软连接的时候,才会真正解析它,展开它找到实际的文件

    对于符号引用,在类加载层面上讨论比较多,源码级别只是一个形式上的讨论。

    当一个类被加载时,该类所用到的别的类的符号引用都会保存在常量池,实际代码执行的时候,首次遇到某个别的类时,JVM会对常量池的该类的符号引用展开,转为直接引用,这样下次再遇到同样的类型时,JVM就不再解析,而直接使用这个已经被解析过的直接引用。

    除了上述的类加载过程的符号引用说法,对于源码级别来说,就是依照引用的解析过程来区别代码中某些数据属于符号引用还是直接引用,如,System.out.println("test" +"abc");//这里发生的效果相当于直接引用,而假设某个Strings = "abc"; System.out.println("test" + s);//这里的发生的效果相当于符号引用,即把s展开解析,也就相当于s"abc"的一个符号链接,也就是说在编译的时候,class文件并没有直接展看s,而把这个s看作一个符号,在实际的代码执行时,才会展开这个。

    参考文章:

    java内存分配研究:http://www.blogjava.net/Jack2007/archive/2008/05/21/202018.html

    Java常量池详解之一道比较蛋疼的面试题:http://www.cnblogs.com/DreamSea/archive/2011/11/20/2256396.html

    jvm常量池:http://www.cnblogs.com/wenfeng762/archive/2011/08/14/2137820.html

    深入Java核心 Java内存分配原理精讲:http://developer.51cto.com/art/201009/225071.htm

     

     

    转载请标明出处 http://blog.csdn.net/shimiso 欢迎有识之士加入我们的技术交流群:361579846

    展开全文
  • 再探Java内存分配

    千次阅读 多人点赞 2017-09-01 20:56:54
    我觉得:要回答这个问题不妨先搁置这个问题,先往这个问题的上游走走——Java内存分配。一提到内存分配,我想不少人的脑海里都会浮现一句话:引用放在栈里,对象放在堆里,栈指向堆。嗯哼,这句话听上去没有错;但是...
  • Java内存分配图解

    千次阅读 2018-03-09 22:32:02
    JAVA内存分配图解: 一个数组的内存分配图解 两个数组的内存分配图解 两个数组指向同一个地址。 package com.zws; public class ArrayDemo { public static void main(String[] args) { int[] ...
  • Java内存分配策略

    2018-09-12 10:03:24
    上一篇博文讲述了Java内存回收策略,这一次我们讲述一下Java内存分配策略。 这里我们先假设一个模型,我们假设:新生代总内存是10MB,其中Eden区与Survivor区的内存占比是8:1,也就是Eden区是8MB,而Survivor区是1...
  • Java内存分配
  • java内存分配分析

    万次阅读 多人点赞 2013-09-02 16:40:46
    java内存分配分析 本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。 进入正题前首先要知道的是...
  • Java内存分配方式

    2013-08-06 16:03:29
    Java内存分配涉及到的区域: 静态域:存放对象中static定义的静态成员 栈:存放基本数据类型和对象的引用 堆:存放用new创造的对象 常量池:存放常量 Java内存分配中的栈: 在函数中定义的一些基本类型的变量...
  • Java内存分配与垃圾收集
  • Java内存分配机制详解

    千次阅读 2016-10-10 16:14:54
    Java内存分配机制 这里所说的内存分配,主要指的是在堆上的分配,一般的,对象的内存分配都是在堆上进行,但现代技术也支持将对象拆成标量类型(标量类型即原子类型,表示单个值,可以是基本类型或String等)
  • Java内存分配与管理

    千次阅读 2016-11-24 12:02:50
    Java内存分配与管理是Java的核心技术之一,一般Java在内存分配时会涉及到以下区域: 1.栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。  2.堆区:由程序员分配...
  • java内存分配分析/栈内存、堆内存

    千次阅读 2015-09-06 21:58:07
    java内存分配分析 本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。 进入正题前首先要知道的是...
  • Java内存分配浅析1

    千次阅读 2017-07-01 15:54:07
    本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。进入正题前首先要知道的是Java程序运行在JVM...
  • Java内存分配浅析

    2013-05-24 16:44:53
    作为一个普通的编程人员,可能...但是在有些时候,当我们碰到棘手的性能等问题的时候,了解一些Java内存分配的知识还是有好处的。  Java编程语言把内存划分成栈内存和堆内存。在函数中定义的一些基本类型的变量和
  • Java内存分配与回收原理

    千次阅读 2017-04-25 13:37:48
    引言  由于java虚拟机内部是自动分配和回收内存,因此,大部分同学的...一旦遇到这些问题,不了解java内存区域及垃圾回收机制,是很难解决的,因此,本文将分析java内存分配和回收机制。 运行时数据区  Java虚拟
  • java内存分配机制

    2012-06-13 12:11:33
    通过这几天对一个内存溢出程序的监控,学习了程序运行时对内存的使用机制,在这里和大家分享下。  Java程序运行在JVM(Java ...所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,849
精华内容 16,739
关键字:

java内存分配

java 订阅