算法复杂度之 空间复杂度(Java)

0、说明

根据算法书上的定义,一个算法的空间复杂度包括算法程序所占用的空间,输入初始数据所占用的空间以及算法执行过程中所需要的额外空间。
本文各种结论全部参考过标准文献,本人也进行过验证。验证过程本文不做说明。例如:当前主流虚拟机boolean类型运行时确实是1字节。
部分与计算空间无关的细节也不做说明,例如:对象头具体包含哪些信息、分别在哪几位、什么是指针压缩等。
细节信息,本人以后会在《JVM浅析》栏目中一一补充,敬请期待哦~

一、基础知识

1. 数据类型以及大小

  基本类型
类型名称 占用字节数
 boolean  1
 byte  1
 char  2
 short  2
 int  4
 float  4
 long  8
 double  8
  引用类型
 操作系统位数  占用字节数
 32位  4
 64位  8 (指针压缩后4字节)

*注:引用类型(注意是引用类型变量,不是对象实例,本质上是指针,其中数组类型变量也属于引用类型变量)。

2. 内存计算公式

对象占用内存 = 对象头开销 + 实例数据(如果是引用类型则包括 变量 和 实例 两部分开销) + 填充数据。

a.对象头开销

指向类引用、垃圾收集信息、同步信息等。
32位JVM 对象头8字节,数组对象头16字节。
64位JVM 对象头16字节(指针压缩后12字节),数组对象头24字节(指针压缩后16字节)。
空对象 / 空数组 都只有对象头。

b.实例数据

基本类型在内存只有值所占用空间大小。
引用类型包含 变量 和 值 两部分占用空间大小(引用变量就相当于指针,用一个系统存储单元存储。值则是堆中实例的大小)。

c.填充数据

hotspot JVM里,对象占用空间是8字节对齐的。因为在内存中一个存储单元是8字节的。
意思是一个Java对象使用的内存一定是8字节的整数倍,如果通过计算后发现对象所需内存不是8字节的整数倍,则会将其填充为8字节的倍数。
例如:对象实例可能需要内存为30字节或者28字节等,都会被填充为32字节。

d.继承关系(不计算父类对象头开销)

子类对象占用内存 = 子类对象头开销 + 子类实例数据 + (父类实例数据+填充数据) + 填充数据。

二、计算实例

因为现在主流都是64位系统,下面测试类会按照64位环境计算内存。并且没有考虑指针压缩的情况。
测试类演示了最复杂的情况,只为演示计算过程,命名不规范,请勿在实际项目中模仿。 

new B()总共占用内存空间 = [ (4(a) + 2(bs) + 2(填充数据)) + 4(ba) + 8(cs数组引用变量) + 4(填充数据) ] + [ 24(cs数组对象头) + 3(cs数组内引用变量个数,即cs[0]、cs[1]、cs[2]) * 8(cs数组中引用变量占用内存) ] + [ (16(C实例字节头信息) + 1(c变量) + 7(填充字节)) * 3(C实例数量) ] = 144字节(8字节的整数倍)。

备注

64位JVM才会有指针压缩的情况。-XX:+UseCompressedOops(开启)  -XX:-UseCompressedOops(关闭)。
JDK1.6以后才加入此项功能并且默认开启指针压缩。但计算公式不变,只是有些对象占用内存数值会变。大家自行计算压缩指针后或者32位JVM下的占用内存。

查看指针压缩等虚拟机参数信息可用如下两个命令:
jcmd    //查看进程信息 (数字列显示的是PID,要保证程序在运行过程中,想测试的话可以到公司服务器上查看)。
jcmd [PID] VM.flags    //查看该pid对应进程设置的JVM参数。

原文地址:https://www.cnblogs.com/leon618/p/7487021.html