`

Java内存分配、管理小结

 
阅读更多



 from: http://legend26.blog.163.com/blog/static/13659026020101122103954365/

想写这篇总结酝酿了有个来月了,却始终感觉还差点什么东西,一直未敢动笔。

       最近两天连夜奋战,重新整理下前面查阅的资料、笔记,还是决定将它写出来。

       现在提出几个问题,如果都能熟练回答的大虾,请您飘过.如以往一样,我是小菜,本文自然也是针对小菜阶层的总结。

 

 

首先是概念层面的几个问题:

 

  • Java中运行时内存结构有哪几种?

  • Java中为什么要设计堆栈分离?

  • Java多线程中是如何实现数据共享的?

  • Java反射的基础是什么?

    然后是运用层面:

  • 引用类型变量和对象的区别?

  • 什么情况下用局部变量,什么情况下用成员变量?

  • 数组如何初始化?声明一个数组的过程中,如何分配内存?

  • 声明基本类型数组和声明引用类型的数组,初始化时,内存分配机制有什么区?

  • 在什么情况下,我们的方法设计为静态化,为什么?(上次胡老师问文奇,问的哑口无言,当时想回答,却老感觉表述不清楚,这里也简单说明一下)

    好了,问题提完了,如果您都能一眼看出答案,那么,没有必要再浪费您宝贵的时间看下去了

    如果您还不太明白,请跟随我一路走下去。

     

    Java中运行时内存结构

       1.1 方法区: 

    方法区是系统分配的一个内存逻辑区域,是JVM在装载类文件时,用于存储类型信息的(类的描述信息)

        

    方法区存放的信息包括:

                1.1.1类的基本信息:

1.      每个类的全限定名

2.      每个类的直接超类的全限定名(可约束类型转换)

3.      该类是类还是接口

4.      该类型的访问修饰符

5.      直接超接口的全限定名的有序列表

             1.1.2已装载类的详细信息

1. 运行时常量池

在方法区中,每个类型都对应一个常量池,存放该类型所用到的所有常量,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。它们以数组形式通过索引被访问,是外部调用与类联系及类型对象化的桥梁。(存的可能是个普通的字符串,然后经过常量池解析,则变成指向某个类的引用)

2. 字段信息

字段信息存放类中声明的每一个字段的信息,包括字段的名、类型、修饰符。

字段名称指的是类或接口的实例变量或类变量,字段的描述符是一个指示字段的类型的字符串,如private A a=null;a为字段名,A为描述符,private为修饰符

3. 方法信息

类中声明的每一个方法的信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码。

(在编译的时候,就已经将方法的局部变量、操作数栈大小等确定并存放在字节码中,在装载的时候,随着类一起装入方法区。)

在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。

4. 静态变量

这个没什么好说的,就是类变量,类的所有实例都共享,我们只需知道,在方法区有个静态区,静态区专门存放静态变量和静态块。

5. 到类classloader的引用:到该类的类装载器的引用。

6. 到类class 的引用:虚拟机为每一个被装载的类型创建一个class 实例,用来代表这个被装载的类。 

   由此我们可以知道反射的基础

在装载类的时候,加入方法区中的所有信息,最后都会形成Class类的实例,代表这个被装载的类。方法区中的所有的信息,都是可以通过这个Class类对象反射得到。我们知道对象是类的实例,类是相同结构的对象的一种抽象。同类的各个对象之间,其实是拥有相同的结构(属性),拥有相同的功能(方法),各个对象的区别只在于属性值的不同
   
同样的,我们所有的类,其实都是Class类的实例,他们都拥有相同的结构-----Field数组、Method数组。而各个类中的属性都是Field属性的一个具体属性值,方法都是Method属性的一个具体属性值。

 

 

 在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。

 

1.2 Java

JVM栈是程序运行时单位,决定了程序如何执行,或者说数据如何处理。

Java中,一个线程就会有一个线程的JVM栈与之对应,因为不过的线程执行逻辑显然不同,因此都需要一个独立的JVM栈来存放该线程的执行逻辑。

对方法的调用:

Java栈内存,以帧的形式存放本地方法的调用状态,包括方法调用的参数、局部变量、中间结果等(方法都是以方法帧的形式存放在方法区的),每调用一个方法就将对应该方法的方法帧压入Java 栈,成为当前方法帧。当调用结束(返回)时,就弹出该帧。

 

这意味着:

在方法中定义的一些基本类型的变量和引用变量都在方法的栈内存中分配。当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后(方法执行完成后),Java 会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作它用--------同时,因为变量被释放,该变量对应的对象,也就失去了引用,也就变成了可以被gc对象回收的垃圾。

 

因此我们可以知道成员变量与局部变量的区别:

局部变量,在方法内部声明,当该方法运行完时,内存即被释放。
成员变量,只要该对象还在,哪怕某一个方法运行完了,还是存在。
从系统的角度来说,声明局部变量有利于内存空间的更高效利用(方法运行完即回收)。
成员变量可用于各个方法间进行数据共享。

 

 

Java 栈内存的组成:
局部变量区、操作数栈、帧数据区组成。
1):局部变量区为一个以字为单位的数组,每个数组元素对应一个局部变量的值。调用方法时,将方法的局部变量组成一个数组,通过索引来访问。若为非静态方法,则加入一个隐含的引用参数this,该参数指向调用这个方法的对象。而静态方法则没有this参数。因此,对象无法调用静态方法。

 

由此,我们可以知道,方法什么时候设计为静态,什么时候为非静态?

前面已经说过,对象是类的一个实例,各个对象结构相同,只是属性不同。
而静态方法是对象无法调用的。
所以,静态方法适合那些工具类中的工具方法,这些类只是用来实现一些功能,也不需要产生对象,通过设置对象的属性来得到各个不同的个体。


2):操作数栈也是一个数组,但是通过栈操作来访问。所谓操作数是那些被指令操作的数据。当需要对参数操作时如a=b+c,就将即将被操作的参数压栈,如将b c 压栈,然后由操作指令将它们弹出,并执行操作。虚拟机将操作数栈作为工作区。
3):帧数据区处理常量池解析,异常处理等

 

1.3 java 

      java的堆是一个运行时的数据区,用来存储数据的单元,存放通过new关键字新建的对象数组,对象从中分配内存。
     
在堆中声明的对象,是不能直接访问的,必须通过在栈中声明的指向该引用的变量来调用。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
   

     由此我们可以知道,引用类型变量和对象的区别

声明的对象是在堆内存中初始化的,真正用来存储数据的。不能直接访问。

引用类型变量是保存在栈当中的,一个用来引用堆中对象的符号而已(指针)。

 

堆与栈的比较
JAVA
堆与栈都是用来存放数据的,那么他们之间到底有什么差异呢?既然栈也能存放数据,为什么还要设计堆呢?

 

1.从存放数据的角度:

      前面我们已经说明:

      栈中存放的是基本类型的变量or引用类型的变量

       堆中存放的是对象or数组对象.

       在栈中,引用变量的大小为32位,基本类型为1-8个字节。
       
但是对象的大小和数组的大小是动态的,这也决定了堆中数据的动态性,因为它是在运行时动态分配内存的,生存期也不必在编译时确定,Java 的垃圾收集器会自动收走这些不再使用的数据。

 

2.从数据共享的角度:

    1).在单个线程类,栈中的数据可共享

    例如我们定义:

Java代码

1.    int a=3;  

2.    int b=3;  

 int a=3;  int b=3;

    译器先处理int a = 3;首先它会在栈中创建一个变量为a 的引用,然后查找栈中是否有3 这个值,如果没找到,就将3 存放进来,然后将a 指向3。接着处理int b = 3;在创建完b 的引用变量后,因为在栈中已经有3这个值,便将b 直接指向3。这样,就出现了a b 同时均指向3的情况。

    而如果我们定义: 

Java代码

1.    Integer a=new Integer(3);//(1)  

2.    Integer b=new Integer(3);//(2)  

Integer a=new Integer(3);//(1) Integer b=new Integer(3);//(2)

   这个时候执行过程为:在执行(1)时,首先在栈中创建一个变量a,然后在堆内存中实例化一个对象,并且将变量a指向这个实例化的对象。在执行(2)时,过程类似,此时,在堆内存中,会有两个Integer类型的对象。 

 

    2).在进程的各个线程之间,数据的共享通过堆来实现

        例:那么,在多线程开发中,我们的数据共享又是怎么实现的呢?


 

  如图所示,堆中的数据是所有线程栈所共享的,我们可以通过参数传递,将一个堆中的数据传入各个栈的工作内存中,从而实现多个线程间的数据共享

(多个进程间的数据共享则需要通过网络传输了。) 

 

3.从程序设计的的角度:

从软件设计的角度看,JVM栈代表了处理逻辑,而JVM堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

 

4.值传递和引用传递的真相

有了以上关于栈和堆的种种了解后,我们很容易就可以知道值传递和引用传递的真相:

1.程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

但是传引用的错觉是如何造成的呢?

在运行JVM栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为传引用值的传值调用,即引用的处理跟基本类型是完全一样的。

但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)JVM堆中的对象,这个时候才对应到真正的对象。

如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。

 

 

最后:

从某种意义上来说对象都是由基本类型组成的。 

可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。 

 

其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别

面向对象的引入,只是改变了我们对待问题的思考方式,而更接近于自然方式的思考。

当我们把对象拆开,其实对象的属性就是数据,存放在JVM堆中;而对象的行为(方法),就是运行逻辑,放在JVM栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。

 

总结:

堆区:
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

 

 

  • 大小: 38.7 KB
  • 大小: 43.3 KB
分享到:
评论

相关推荐

    Java内存分配、管理小结.doc

    Java内存分配、管理小结

    java中jvm内存分配相关资料总结整理

    关于java的内存分配问题,jvm的运行原理相关资料总结

    JAVA 内存管理总结

     Java的内存管理是对象的分配和释放问题。(两部分)  分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。  释放 :...

    JAVA内存逃逸分析.pptx

    这个文档是在学习了Java内存过程中总结出来的,描述了Java内存分配的过程,jdk基本初始化参数,内存逃逸级别等

    深入java虚拟机

    1. JVM调优 1.1 JVM调优总结(一)-一些概念 1.2 JVM调优总结(二)-一些概念 1.3 JVM调优总结(三)-基本垃圾回收算法 1.4 JVM调优总结(四)-垃圾...4.2 JVM内存管理:深入垃圾收集器与内存分配策略 4.3 深入理解JVM

    java垃圾回收以及jvm参数调优概述

    而且这两个问题针对的内存区域就是Java内存模型中的堆。垃圾回收机制的引入可以有效的防止内存泄露、保证内存的有效使用,也大大解放了Java程序员的双手,使得他们在编写程序的时候不再需要考虑内存管理。本文主要对...

    java基础知识总结.docx

    对java一些常用基础的总结,java基本知识,数组,java内存分配,方法总结,面向对象,常用API,常用集合

    java 字符串内存分配的分析与总结(推荐)

    下面小编就为大家带来一篇java 字符串内存分配的分析与总结(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    Java内存以及GC

    Java内存区域与内存溢出  Java虚拟机中的内存分配图:  各个区域的特性总结如下表:  补充说明:  当多线程情形下,可能多个线程要在堆上分配内存,那么可能出现内存分配的同步问题,解决方案有两个...

    Java JVM 面试题总结

    JVM 面试题总结 JVM 的主要作用是什么? 请你描述一下 Java 的内存区域?...内存分配方式有哪些呢? 请你说一下对象的内存布局? 对象访问定位的方式有哪些? 如何判断对象已经死亡? 如何判断一个不再使用的类?

    java 面试题 总结

    内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 41 2.5 本章小结 / 42 第3章 垃圾收集器与内存分配策略 / 43 3.1 概述 / 43 3.2 对象已死? / 44 3.2.1 引用计数算法 / 44 3.2.2 根搜索算法 / 46 3.2.3 再谈引用 / 47 3.2.4 生存还是死亡? / 48 3.2.5 ...

    深入理解Java虚拟机视频教程(jvm性能调优+内存模型+虚拟机原理)视频教程

    视频目录 第1节说在前面的话 [免费观看] 00:05:07分钟 | 第2节整个部分要讲的内容说明 [免费观看] 00:06:58分钟 | 第3节环境搭建以及jdk,...第104节字节码执行引擎小结00:03:38分钟 | 第105节总结与回顾00:10:55分钟

    Java虚拟机

    原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。 前言 第一部分 走近Java 第1章 走近Java 1.1 概述 1.2 ...

    Java语言的科学与艺术 斯坦福大学经典教材

    6.8 复习题 6.9 编程练习 第7章 对象和内存 7.1 内存结构 7.2 将内存分配给变量 7.3 原始类型与对象 7.4 链接对象 7.5 小结 7.6 复习题 7.7 编程练习 第8章 字符串和字符 8.1 枚举的原则 8.2 字符 8.3 作为抽象概念...

    jvm内存管理和垃圾回收

    很久之前就一直在学习JVM,但是一直也没有好好的总结,最近终于有了空闲,将之前学习的内容整理成了一个PPT。 也希望大神们可以批评指正。... Java内存模型 内存分配策略 分代垃圾收集 虚拟机工具 垃圾收集器

    Java语言的科学与艺术(国外计算机科学经典教材)

     7.2 将内存分配给变量  7.3 原始类型与对象  7.4 链接对象  7.5 小结  7.6 复习题  7.7 编程练习 第8章 字符串和字符  8.1 枚举的原则  8.2 字符  8.3 作为抽象概念的字符串  8.4 使用String类中的方法 ...

    疯狂JAVA讲义

    学生提问:能不能只分配内存空间,不赋初始值呢?89 4.5.4 使用数组 90 学生提问:为什么要我记住这些异常信息? 91 4.5.5 JDK1.5提供了foreach循环 91 4.6 深入数组 93 4.6.1 内存中的数组 93 学生提问:为...

    java基础心得总结.rar

    描述java基础容易忽略的问题,如下面几个知识点: int a = 1;1会存放在栈里 String s1 = "thanks";thanks会存放在data segment里 String s2 = new String("thanks");thanks会存放在data segment里 String a ="th...

    java基础入门教程

    所 以 有 人 说 :"现 在 第 三 方 的 开 发 商 都 团 结 在 Java大旗 周 围了 !",纷 纷 推 出 用Java 开 发 的 各 种 软 件 产 品 ,以 期尽 快 地 占 领 市 场 。 四 、 Java 语 言 的 应 用 前 景 Java 语 言 ...

Global site tag (gtag.js) - Google Analytics