`
xmong
  • 浏览: 262841 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java内存管理

阅读更多
java内存管理

1 Java内存
1.1 Java有几种存储区域
 寄存器:位于CPU内部,是最快的存储器,开发人员不能通过代码来控制寄存器的分配,由编译器来管理。
 栈:位于通用RAM(随机访问存储器)中,是一块连续的内存的区域,通过栈指针可以从处理器那里获得支持,指针想下移动,则分配新内存;向上移动,则释放内存。在java中用于存放基本数据类型和对象的引用。
 堆:一种通用的内存池(也位于RAM中),是不连续的内存区域。在java中用于存放所有的java对象。
 常量存储区与静态存储区:常量存储区用来存放常量类型(final)类型的值,一般在只读存储器中,静态存储区用来存放static类型的变量。
 非RAM存储:如果数据完全活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。如对象流,持久化对象。

1.2 Java内存分配方式
每个java虚拟机都有一个类装载器子系统,他根据给定的全限定名来装入类型(类或接口)。同样每个java虚拟机都一个执行引擎,他负责执行那些包还在被装载类的方法中的指令。当java虚拟机运行程序时,需要分配内存来存储许多东西,如创建的对象,传递方法的参数,返回值等。这时候需要对内存进行相应的分配,内存的分配可以通过三种方式:
静态分配:这是最简单的内存分配策略,将程序中的所有名字在编译时绑定在某个存储位置上,这种绑定不会在运行时改变。
常数池,源代码中的命名常量、String常量和static 变量保存在方法区。
栈分配:块结构语言通过在栈上分配内存,克服了静态分配的一些限制。每次过程调用时,一个活动记录或是帧被压入系统栈,并在返回时弹出。
最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的  方法帧被弹出(pop)。栈中存储的数据也是运行时确定的
堆分配:堆中的数据结构能够以任意次序分配和释放。意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。因而活动记录和动态数据结构可能比创建他们的过程更长寿。
堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。

1.3 Java数据类型的内存分配
     -- 基础数据类型直接在栈空间分配;
     -- 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
     -- 引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
     -- 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
     -- 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收;
     -- 方法调用时传入的 literal 参数,先在栈空间分配,在方法调用完成后从栈空间释放;
     -- 字符串常量在 DATA 区域分配 ,this 在堆空间分配;
     -- 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小

2 实例分析
2.1 常量池
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
     常量池在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。
例:
     String s1=new String("kvill");
     String s2=s1.intern();
     System.out.println( s1==s1.intern() );//false
     System.out.println( s1+" "+s2 );// kvill kvill
     System.out.println( s2==s1.intern() );//true
     这个类中事先没有声名”kvill”常量,所以常量池中一开始是没有”kvill”的,当调用s1.intern()后就在常量池中新添加了一个 ”kvill”常量,原来的不在常量池中的”kvill”仍然存在。s1==s1.intern()为false说明原来的“kvill”仍然存在;s2 现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。

2.2 String常量池问题
(1) 字符串常量的"+"号连接,在编译期字符串常量的值就确定下来, 拿"a" + 1来说,编译器优化后在class中就已经是a1。
     String a = "a1";
     String b = "a" + 1;
     System.out.println((a == b)); //result = true
     String a = "atrue";
     String b = "a" + "true";
     System.out.println((a == b)); //result = true
     String a = "a3.4";
     String b = "a" + 3.4;
     System.out.println((a == b)); //result = true
(2) 对于含有字符串引用的"+"连接,无法被编译器优化。
     String a = "ab";
     String bb = "b";
     String b = "a" + bb;
     System.out.println((a == b)); //result = false
     由于引用的值在程序编译期是无法确定的,即"a" + bb,只有在运行期来动态分配并将连接后的新地址赋给b。
(3) 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝并存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。
     String a = "ab";
     final String bb = "b";
     String b = "a" + bb;
     System.out.println((a == b)); //result = true
(4) jvm对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b。
     String a = "ab";
     final String bb = getbb();
     String b = "a" + bb;
     System.out.println((a == b)); //result = false
     private static string getbb() {
       return "b";
     }
(5) String 变量采用连接运算符(+)效率低下。
     String s = "a" + "b" + "c"; 就等价于String s = "abc";
     String a = "a";
     String b = "b";
     String c = "c";
     String s = a + b + c;
     这个就不一样了,最终结果等于:
       Stringbuffer temp = new Stringbuffer();
       temp.append(a).append(b).append(c);
       String s = temp.toString();
(6) Integer、Double等包装类和String有着同样的特性:不变类。
     String str = "abc"的内部工作机制很有代表性,以Boolean为例,说明同样的问题。
     不变类的属性一般定义为final,一旦构造完毕就不能再改变了。
     Boolean对象只有有限的两种状态:true和false,将这两个Boolean对象定义为命名常量:
     public static final Boolean TRUE = new Boolean(true);
     public static final Boolean FALSE = new Boolean(false);
     这两个命名常量和字符串常量一样,在常数池中分配空间。 Boolean.TRUE是一个引用,Boolean.FALSE是一个引用,而"abc"也是一个引用!由于Boolean.TRUE是类变量 (static)将静态地分配内存,所以需要很多Boolean对象时,并不需要用new表达式创建各个实例,完全可以共享这两个静态变量。其JDK中源 代码是:
     public static Boolean valueOf(boolean b) {
       return (b ? TRUE : FALSE);
     }
     基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。 Boolean b1 = 5>3; 等价于Boolean b1 = Boolean.valueOf(5>3); //优于Boolean b1 = new Boolean (5>3);
    static void foo(){
        boolean isTrue = 5>3;  //基本类型
        Boolean b1 = Boolean.TRUE; //静态变量创建的对象
        Boolean b2 = Boolean.valueOf(isTrue);//静态工厂
        Boolean b3 = 5>3;//自动装箱(autoboxing)
        System.out.println("b1 == b2 ?" +(b1 == b2));
        System.out.println("b1 == b3 ?" +(b1 == b3));
        Boolean b4 = new Boolean(isTrue);////不宜使用
        System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销
    } //这里b1、b2、b3指向同一个Boolean对象。
(7) 如果问你:String x ="abc";创建了几个对象?
     准确的答案是:0或者1个。如果存在"abc",则变量x持有"abc"这个引用,而不创建任何对象。
     如果问你:String str1 = new String("abc"); 创建了几个对象?
     准确的答案是:1或者2个。(至少1个在heap中)
(8) 对于int a = 3; int b = 3;
     编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处 理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

2.3 堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”
     可以看出JVM主要管理两种类型的内存:堆和非堆。
     简单来说堆就是Java代码可及的内存,是留给开发人员使用的;
     非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配
     JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;
     JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。
     默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
     因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
非堆内存分配
     JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
     由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
例子
     -Xms256m
     -Xmx1024m





---------------------------------------


参考:
《thinking in java》
java内存管理机制文档


分享到:
评论

相关推荐

    java内存管理详细介绍.doc

    Java内存管理是Java编程中至关重要的一环,它与C++等其他语言的内存管理机制有着显著的区别。在C++中,程序员需要手动管理内存,包括分配和释放,而在Java中,这一过程则由Java虚拟机(JVM)自动进行,通过垃圾收集...

    Java内存管理机制相关资料汇总

    资源名称:Java内存管理机制相关资料汇总资源目录:【】java内存回收机制及预防【】java内存管理机制【】java内存管理白皮书【】Java虚拟机内存管理_对象和引用_空指针【】深入理解java虚拟机jvm高级行与最佳实践...

    java内存管理 ppt

    Java内存管理是Java编程中的核心概念,它涉及到程序运行时数据的存储、分配以及回收。在Java中,内存主要分为堆内存(Heap)和栈内存(Stack),还有方法区(Method Area)、程序计数器(PC Register)以及本地方法...

    java内存管理精彩概述

    Java内存管理是Java核心技术的重要组成部分,对于每个开发者来说,理解其工作原理都是十分必要的。这一主题既实用又有趣。以下是对Java内存管理的精彩概述,主要基于Sun Hotspot JVM,但请注意,不同JVM可能有不同的...

    Java的内存管理机制分析

    通过对Java内存管理机制的深入分析,我们可以了解到Java如何高效地管理和利用内存资源。理解这些机制对于优化Java应用程序的性能至关重要,特别是在处理大规模数据集或多线程环境时。此外,合理配置JVM参数和选择...

    java内存管理白皮书

    《Java内存管理白皮书》深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是Sun Microsystems在J2SE 5.0版本中的HotSpot JVM。本文将根据标题、描述、标签以及部分内容,详细解析Java内存管理的核心知识点,包括...

    JAVA 内存管理总结

    【JAVA 内存管理总结】 Java 的内存管理主要涉及对象的生命周期,包括对象的分配和释放。对象的分配主要是通过 `new` 关键字在堆内存(Heap)中完成,而对象的释放则由垃圾回收机制(Garbage Collector, GC)自动...

    JAVA内存管理模式研究.pdf

    "JAVA内存管理模式研究" 在 Java 编程语言中,内存管理是非常关键的机制之一。 Java 虚拟机(JVM)通过对内存的分配和回收来管理内存资源。 本文将详细介绍 Java 的内存管理模式,包括内存区域、内存分配策略、垃圾...

    java内存管理问题及解决办法

    Java内存管理是Java编程中至关重要的一环,它涉及到程序的性能和稳定性。本文将深入探讨Java内存管理机制,包括垃圾回收、内存分配与释放、内存泄漏及其预防措施,以及四种引用类型的特点和应用场景。 首先,Java...

    java内存管理 .pptx

    Java内存管理是Java编程中至关重要的一环,它涉及到Java虚拟机(JVM)如何高效地运行Java代码。在Java中,内存管理主要包括内存区域的划分、对象的生命周期以及垃圾回收机制。 1. **内存区域划分** - **方法区...

    java内存管理深入讲解

    ### Java内存管理深入讲解 #### 一、Java内存模型概览 Java程序的运行依赖于一个精心设计的内存模型,该模型确保了程序在不同环境中的一致性和高效性。Java内存模型主要包括两个部分:栈(Stack)和堆(Heap)。这...

    关于java内存管理的一些理解

    主要关于java虚拟机的运行时数据区域,参考了周志明的深入理解java虚拟机,还涉及到了native方法、垃圾回收机制等等。

    java内存管理的原理.docx

    Java内存管理的原理是Java开发中的重要组成部分,它关乎到应用程序的性能和稳定性。Java虚拟机(JVM)负责自动管理内存,包括对象的分配和回收,以减轻开发人员的负担。然而,深入理解这些原理可以帮助优化代码,...

Global site tag (gtag.js) - Google Analytics