Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java内存管理(一、内存分配)

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

作者头像
bear_fish
发布于 2018-09-20 03:21:35
发布于 2018-09-20 03:21:35
3.6K0
举报

关于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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015年11月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java内存分配之堆、栈和常量池
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码定义一个变量时,java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另做他用。
技术从心
2019/08/06
1.5K0
Java内存分配之堆、栈和常量池
Java虚拟机堆和栈详细解析,以后面试再也不怕问jvm了!
Java堆是和Java应用程序关系最密切的内存空间,几乎所有的对象都放在其中,并且Java堆完全是自动化管理,通过垃圾收集机制,垃圾对象会自动清理,不需自己去释放。
程序员追风
2019/11/21
6510
Java虚拟机堆和栈详细解析,以后面试再也不怕问jvm了!
Java_内存分配
new出的空间都是作为动态内存在堆中分配的,比如new出的对象的成员属性、使用new开辟的数组中的各个元素、使用new创建的基本数据类型等
用户10551528
2023/05/09
5910
Java_内存分配
java+内存分配及变量存储位置的区别
Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识。一般Java在内存分配时会涉及到以下区域:
bear_fish
2018/09/20
9230
Java内存分配分析
本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。
shengjk1
2025/05/16
1210
Java内存分配分析
深入理解Java:String
按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。
Java团长
2018/08/07
3750
java内存分配和String类型的深度解析
在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题。下面是本文将要涉及到的一些问题,如果读者对这些问题都了如指掌,则可忽略此文。
哲洛不闹
2018/09/19
7710
java内存分配和String类型的深度解析
JVM - 深入剖析字符串常量池
看 1.8 , 疯狂的intern, 抛出了 heap oom ,由此可以推断出 1.8中的字符串常量池 是在堆中。
小小工匠
2021/08/17
6270
Java内存区域详解
Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区域。JDK1.8和之前版本略有不同。
Vincent-yuan
2021/09/01
5140
Java内存区域详解
JVM 学习笔记(1):Java内存区域
程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器 ,也就是记录下 Java 程序当前指令的地址偏移量,可在线程切换时记录下当前线程执行的位置,给 CPU 提供指令地址,以便下一次切换回来找到继续执行的位置。
玛卡bug卡
2022/09/20
4880
JVM 学习笔记(1):Java内存区域
java内存管理(下)
定义: 是为了JVM运行native方法准备的空间,由于很多native方法都是用C语言实现的,所以通常又叫C栈,它与Java虚拟机栈实现的功能类似,只不过本地方法栈描述本地方法运行过程的内存模型
陈不成i
2021/06/24
3780
【jvm】01- java内存结构分析
每一个方法的执行就是一个栈帧,而且在栈内存中遵循先进后出的原理。听到这里,是不是感觉不是很懂(大佬直接忽略)? 我们来看一个示例:
envoke
2020/09/17
5290
【jvm】01- java内存结构分析
字符串常量池_字符串常量池溢出
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。
全栈程序员站长
2022/09/19
6680
字符串常量池_字符串常量池溢出
JVM内存结构详解
我们知道JVM是内存中的虚拟机,主要使用内存进行存储,所有类、类型、方法,都是在内存中,这决定着我们的程序运行是否健壮、高效。
全栈程序员站长
2022/08/12
4460
JVM内存结构详解
Java 内存区域详解
对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
Vincent-yuan
2021/10/26
5210
Java 虚拟机中内存区域介绍 JDK8区别 对象创建 学习笔记
在之前的JVM介绍中简略讲述了JVM介绍与内存层次结构,这篇博客主要记录内存区域、对象创建流程及JDK8中的更新。
大鹅
2021/06/16
4570
String类型在JVM中的内存分配
字符串在Java中用的非常得多,Jvm为了减少内存开销和提高性能,使用字符串常量池来进行优化。
会说话的丶猫
2020/08/06
3.3K0
String类型在JVM中的内存分配
深入Java源码剖析之字符串常量
字符串在Java生产开发中的使用频率是非常高的,可见,字符串对于我们而言非常关键。那么从C语言过来的同学会发现,在C中是没有String类型的,那么C语言要想实现字符串就必须使用char数组,通过一个个的字符来组拼成字符串。
wangweijun
2020/02/14
4420
Java String Krains 2020-08-05
String源码中是这样定义的,String底层在jdk8及以前是用char数组存储的,而jdk9之后改用byte数组存储,由于都加了final关键字,String是不可变的。
Krains
2020/08/10
3850
Java String  Krains 2020-08-05
深入理解Java中的String
想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码:
lyb-geek
2019/01/08
6480
相关推荐
Java内存分配之堆、栈和常量池
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档