`

Java内存管理(一、内存分配)

阅读更多

     关于Java内存分配,很多问题都模模糊糊,不能全面贯通理解。今查阅资料,欲求深入挖掘,彻底理清java内存分配脉络,只因水平有限,没达到预期效果,仅以此文对所研究到之处作以记录,为以后学习提供参考,避免重头再来。

 

一、Java内存分配
1、 Java有几种存储区域?
* 寄存器
     -- 在CPU内部,开发人员不能通过代码来控制寄存器的分配,由编译器来管理
* 栈
     -- 在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域,即栈顶的地址和栈的最大容量是系统预先规定好的。
     -- 优点:由系统自动分配,速度较快。
     -- 缺点:不够灵活,但程序员是无法控制的。
     -- 存放基本数据类型、开发过程中就创建的对象(而不是运行过程中)
* 堆
     -- 是向高地址扩展的数据结构,是不连续的内存区域
     -- 在堆中,没有堆栈指针,为此也就无法直接从处理器那边获得支持
     -- 堆的好处是有很大的灵活性。如Java编译器不需要知道从堆里需要分配多少存储区域,也不必知道存储的数据在堆里会存活多长时间。
* 静态存储区域与常量存储区域
     -- 静态存储区用来存放static类型的变量
     -- 常量存储区用来存放常量类型(final)类型的值,一般在只读存储器中
* 非RAM存储
     -- 如流对象,是要发送到另外一台机器上的
     -- 持久化的对象,存放在磁盘上
2、 java内存分配
     -- 基础数据类型直接在栈空间分配;
     -- 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
     -- 引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
     -- 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
     -- 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收;
     -- 方法调用时传入的 literal 参数,先在栈空间分配,在方法调用完成后从栈空间释放;
     -- 字符串常量在 DATA 区域分配 ,this 在堆空间分配;
     -- 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
3、Java内存模型
* Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。
    -- 方法区是静态分配的,编译器将变量在绑定在某个存储位置上,而且这些绑定不会在运行时改变。
        常数池,源代码中的命名常量、String常量和static 变量保存在方法区。
    -- Java Stack是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。
        最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的  方法帧被弹出(pop)。栈中存储的数据也是运行时确定的?
    -- Java堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。
        堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。
4、Java内存分配实例解析
     常量池(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。

 

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的情况。
5、堆(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
     -XX:PermSize=128M
     -XX:MaxPermSize=256M

分享到:
评论

相关推荐

    java实现内存动态分配

    Java 实现内存动态分配主要涉及Java内存模型以及内存管理机制,包括堆内存和栈内存的分配,以及垃圾回收等概念。下面将详细解释这些知识点。 1. **Java内存模型** Java程序运行时,内存分为堆内存(Heap)和栈内存...

    50.java内存分配.zip

    50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java内存分配.zip50.java...

    java内存分配情况

    Java内存管理的关键点还包括以下几点: - **对象引用**:当一个对象仅被一个栈上的引用变量指向时,这个引用变量可以被视为对象的名称。当引用变量不再指向对象,对象就变为不可达,等待垃圾回收。 - **垃圾回收**...

    java内存分配机制详解

    通过上述介绍可以看出,Java内存管理机制通过不同的存储区域来满足不同类型的变量和对象的需求。了解这些基础知识对于提高Java程序的性能和避免内存泄漏等问题至关重要。希望本文能够帮助读者更深入地理解Java内存...

    Java内存分配浅析

    Java内存分配是Java编程中非常重要的概念,它涉及到程序运行时的数据存储和管理。Java程序在JVM(Java Virtual Machine,Java虚拟机)上运行,...Java内存管理是一个深度和广度并存的话题,需要不断学习和实践来提升。

    Java的内存管理机制分析

    #### 一、Java内存区域划分 Java的内存管理机制将内存分为以下几个区域: 1. **栈(Stack)**: - 存储局部变量(如基本类型的变量和对象的引用)。 - 每个线程拥有一个独立的栈。 - 栈内存中的数据在方法执行...

    java中内存分配

    "java中内存分配" Java 中的内存分配是 Java 程序员必须掌握的重要知识。JAVA 中内存分配的问题是指 Java 程序在运行时如何在内存中存储数据的过程。Java 程序运行时有 6 个地方可以存储数据,它们分别是寄存器、栈...

    java核心 内存分配问题

    Java 内存分配是理解Java程序性能和内存管理的关键。Java内存主要分为以下几个部分: 1. **寄存器**:这是最快速的存储区域,但由编译器和硬件直接控制,程序员无法直接操作。 2. **栈内存**:栈主要用于存储基本...

    Java中堆内存与栈内存分配浅析

    本文将深入探讨Java中堆内存与栈内存的分配机制,并通过对比分析它们之间的差异,帮助读者更好地掌握Java内存管理的核心概念。 #### 二、堆内存与栈内存概述 ##### 1. 堆内存 堆内存是Java虚拟机(JVM)用于存储...

    java内存对象分配过程研究

    1. **内存分配**:当使用`new`关键字创建对象时,JVM会在堆上为新对象分配一块内存空间,并返回一个指向这块内存空间的引用。例如: ``` new Color(100, 100, 120); ``` 这里`new`运算符负责为对象分配内存空间...

    java内存管理 ppt

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

    Java内存分配原理精讲

    通过对Java内存分配原理的深入探讨,我们可以看出,Java内存管理设计得十分巧妙且高效。通过对不同内存区域特性的理解,开发者能够更好地优化程序性能,避免内存泄漏等问题。希望本文能帮助大家更深刻地理解Java内存...

    java实现的内存分配

    内存分配通常由Java虚拟机(JVM)自动进行,但程序员也可以通过理解和利用特定的策略来优化这一过程。本篇文章将深入探讨两种内存分配方法——轮转法和高优先权法,并结合Java的内存模型进行分析。 首先,我们要...

    java内存管理精彩概述

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

    java内存管理详细介绍.doc

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

    java内存分配演示程序

    "java内存分配演示程序"是一个用于理解Java内存模型和内存分配过程的项目。在这个课程设计中,你将深入学习Java如何在运行时为对象分配内存,以及垃圾收集器如何回收不再使用的内存。以下是关于Java内存分配的一些...

    JAVA内存分配

    #### 一、JAVA内存结构概述 Java程序在运行过程中涉及的内存主要包括以下几个部分: 1. **方法区(Method Area)** 2. **栈内存(Stack Memory)** 3. **堆内存(Heap Memory)** 4. **本地方法栈(Native Method ...

    深入Java核心_Java内存分配原理精讲

    Java内存分配原理是Java编程中的重要一环,它关乎到程序的性能、稳定性和资源管理。深入理解这一主题,能够帮助开发者编写出更高效、更稳定的代码。在Java中,内存分为堆内存、栈内存、方法区(在Java 8之后被元空间...

    深入Java核心 Java内存分配原理精讲

    Java内存分配与管理是Java的核心技术之一,今天我们深入Java核心,详细介绍一下Java在内存分配方面的知识。一般Java在内存分配时会涉及到以下区域:  ◆寄存器:我们在程序中无法控制  ◆栈:存放基本类型的...

Global site tag (gtag.js) - Google Analytics