《零基础学 Java》 FAQ 之 8-Java方法调用是传值还是传引用

2020 年 05 月 18 日 阅读数: 15
《零基础学 Java》 FAQ 之 8-Java方法调用是传值还是传引用

来自@谭低调 同学的问题:引用占用多少内存?

@谭低调 同学在“34 | 认识引用类型(下)”里问了这么一个问题:

请问老师,Merchandise m1这个引用类型变量是多大呢?或者说为了存储实例的地址,需要给m1多大的内存?

我思来想去,这事儿没有一篇文章还真扯不清楚。有兴趣的同学请继续阅读

Java 程序员需要关心数据类型对内存的占用情况吗?

Java 用跑在虚拟机里,并且有一个严格得虚拟机规范,就是让程序员可以在不知道这这些底层细节的情况下,也能好好安心写程序。这个目标是非常正确的,甚至是对 Java 的成功都是非常关键的。

软件的世界就是这样,一个层次解决一个层次的问题,既然 JVM 已经屏蔽了底层细节信息,上层的 Java 代码就可以不必关心更下层的细节了。

但是不必了解,并非是了解了没用。不了解也能写出好代码,了解了之后在某些情况下可以写出更好的代码。尤其是在对程序关键点的数据结构设计和预估程序总体内存占用情况时,会有帮助。

每种数据类型占用的内存是固定的吗?

不是。不是。不是。重要的事情说三遍。

不固定有几个维度,下面分别说说在不同的情况下占用的内存为什么不固定,以及在不同情况下具体占用多少内存。

JVM 规范没规定

JVM 规范从来没有规定每种数据类型占用多大的内存。它只是给出了每种数据类型的值域。严谨的不要不要的。既然规范没有说,那么肯定占用的内存就是不固定的喽。具体占用多少,得看规范得实现。

那么具体到某种实现,就是固定得了么?

JDK 实现:看位数和内存区域

前面我们讲解基本数据类型的时候,不严谨的说了每种数据类型占用的内存。那个数字也是主流的 JDK 占用的内存。但是那个数字忽略了 JDK 的位数和数据所在的内存区域。

JDK 的位数

很多年前,主流的操作系统和 CPU 都是32位的。什么意思呢,就是 CPU 的指令能够处理的数,都是32位的,包括内存寻址,也是32位的。所以32位的系统下,可以使用的内存不能超过4G,因为32位的寻址空间只能表示这么大的内存地址范围。

后来出现了64位的操作系统和 CPU,寻址空间和 CPU 操作数都是64位了。CPU的操作数都是64位了,那么应用程序喂给 CPU 的数据也得是64位的呀。

Java 进程也是操作系统上的应用程序。我们知道程序都分64位版和32位版。JDK 也一样。不同的版本在不同情况下占用的内存也不一定一样。

主流 JDK 的内存按照用途主要被分成方法区,堆,和方法栈(其实还有本地方法栈和 PC 寄存器,但是存在感和前三个比太低了,这里不说了)。在不同的区域,占用内存也不大一样,下面大概说说。

JDK 内存区域:方法区

什么是方法区内存呢?直观来说就是保存我们写的方法里的代码的内存。也就是包括我们写的方法里的各种表达式指令,局部变量(当然也包括引用),字面值(literal value),参数这些数据。对于一个类来说,无论它的对象有多少个,方法区占用的内存是不变的,因为他们的代码是一样的嘛。

那么各种数据类型在方法区里占用的内存是多大呢?就是我们之前说的那个大小。比如int是4个字节,long是8个字节。无论是32位还是64位。

JDK 内存区域:方法栈

方法栈是执行方法区里代码的时候需要的内存,也就是我们debug的时候看到的那个一层压着一层的frame。

作为 Java 进程中给 CPU 喂数据的栈(方法栈),就是以64位为一个单位安排的。也就是说,通常情况下,在方法栈上,无论是什么数据类型,都最少占用64个bit,也就是8个byte。说的更具体一点,方法里写的局部变量,字面值(literal value),参数,都是至少占用8个字节。当然好像也没有需要占用更多字节的数据类型,毕竟double和long都是8个字节就够表示规范中的值域了。

JDK 内存区域:内存堆

Java 的对象是分配在堆上的。就是我在课程中说的那个公告板。堆上的数据在被 JVM 指令使用的时候,会被弄到栈上去。但是保存在堆上的时候,和方法区一样,就是按照之前说的普通情况占用该占用的内存。

引用类型:磨人的小妖精

引用类型作为一种非常特殊的类型,和内存寻址相关。所以,JDK 底层实现上一般会使用C/C++的 pointer 来封装成 Java 里的引用。那么它占用的内存就是和 JDK 是32位还是64位有关。如果是64位的 JDK,那么在上述的内存区域里,引用类型都是占用8个字节的内存。32位的 JDK 就占4个字节。

如果引用类型这么简单直接,就不是磨人的小妖精了。引用类型毕竟太多,而且绝大多数情况下用不了这么大空间,太浪费。所以 Oracle 的JDK就针对这种情况,开发了一个压缩指针的功能,主要针对 Java 内存堆小于32g的情况。这个 JVM 参数是-XX:+UseCompressedOops 下面是 Oracle 官方文档对这个参数的说明:

Enables the use of compressed pointers (object references represented as 32 bit offsets instead of 64-bit pointers) for optimized 64-bit performance with Java heap sizes less than 32gb.

引自 https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html


这篇文章来自极客时间推出的《零基础学Java》中的FAQ。除了在每节视频课下方回答大家的问题之外,针对大家提出的优质问题或者普遍问题,如果需要更大篇幅的文章解答,则会在FAQ中以文章的方式给出回答。带你零基础入门,夯实Java,课程地址:https://time.geekbang.org/course/intro/181

用户头像

臧萌

关注

一线程序员,偶尔写写字 2017.10.20 加入

《零基础学 Java》 视频课作者 《Java入门1·2·3》作者

评论

发布
暂无评论
《零基础学 Java》 FAQ 之 8-Java方法调用是传值还是传引用