本文转载自微信公众号「Java极客技术」,作者鸭血粉丝。转载本文请联系Java极客技术公众号。
堆内存
我们大家都知道JVM内存是划分了一个是堆内存,一个是非堆内存,而堆内存分为了(年轻代),(老年代)这些,而非堆内存就是一个元空间了(1.8之后变更的,之前是永久代)。
相比较来说,大家肯定都非常的熟悉分代的概念,知道对象首先应该放在哪里,然后移动到哪里,最后执行什么样子的方法来进行垃圾回收。而我们今天要说的却是如何考虑运行程序的机器内存限制下,让我们的对象更小一点就能完成我们的功能。
减少对象大小
阿粉和大家都一样,都知道对象会占用一定数量的堆内存,毕竟你新生成的对象首先就是要放到Eden区的,当Eden空间被占满的时候,出发Minor GC,存活下来的对象移动到Survivor区去,而我们想要减少内存的使用,最简单的方法就是在写程序的时候,也需要考虑对象的大小,毕竟如果说如果说以后再做CodeReview的时候,你会发现你的代码运行起来,你看JVM的时候会赏心悦目,但是代码也得好看不是?
阿粉就给大家看看最基础的Java基础实例变量的大小
图1:
实例变量,这是一个和对象息息相关的,一个对象一份实例变量,而实例变量的个数和实例变量的大小也就决定你在占用内存的大小。
大家可以想象一下,如果内存不够,那么有两种方式供你选择:
- 选择一:增加百分之10的堆内存
- 选择二:堆中的对象的大小减少百分之10
你会选择什么方式?一般情况你想选择第一种方式,但是这种方式好像不是那么的实际,你堆内存都不够了,你还想再继续增加点?那么只能你来选择第二种了。
但是再你选择了第二种方式之后,你又遇到了一个问题,减少对象的方式也是有两种方式:
- 方式一:直接减少实例变量的数量
- 方式二:减少实例变量的大小
其实这两种方式都可以,这个就是要取决于你在之前代码中做过什么,比如说你在之前的代码已经进行过实例变量的优化了,在写代码之前就已经考虑到这件事了,那么你肯定是只能选择第一种。
如果说你之前在代码中并没有去考虑过实例变量的大小,那么选择第一种将会是你最佳的方案。
分析对象大小
一个对象的大小,我们要把它分开,由三部分来组成,对象头、实例变量、内存补充,在32位的系统中,假设我们定义一个int i ,那么对象头在其中就要占据 4 字节,int 在对象中占用 4 字节,而如果是64位的话,那么对象头就变了,从4字节变成8字节,在这里我们就得注意一个事情了,如果说成员变量不论是否引用了其他的对象,它占用的字节始终是 4 字节。
这里我们就引入了一个概念:Shallow Size
Shallow Size
其实简单来说,Shallow Size 就是对象本身占用内存的大小,但是不包含其中引用的对象,这局话的后半段就是相对应的阿粉刚才所说的注意事项了。
而 Shallow Size 也是有针对的,就比如说是非数组类型的对象,他的大小就是对象和他所有成员变量大小的总和,
针对数组类型的对象,它的大小是数组元素对象的大小的总和。
举个例子:
- public class A(){
- private int i ;
- private boolen x;
- }
我们的A对象在我们New出来之后,发现,不是一个数组类型的,那么就得看成员变量,然后把成员变量加起来,是不是就等于 Shallow Size 了。
Retained Size
说了Shallow Size了,那么我们就不得不提 Retained Size了,因为阿粉在学习的时候,去专门翻找了资料,发现这都是一体的,你看这个,你发现下面还有和他有关联的,不学吧,弄不明白心里难受,那还是学习吧。
英文复制
Retained Size = 当前对象的大小+当前对象的引用大小(直接或者间接)都是
示例图:
图片网址如下https://www.yourkit.com/docs/java/help/sizes.jsp 里面也有解释,但是阿粉还是要解释一波。
在上图中 obj1 的 Retained Size = obj1 + obj2 + obj4 的 Shallow size
这是左边的,右边的是obj1 的 Retained Size = obj1 + obj2 + obj4 +obj3 的 Shallow size
在我们进行GC的时候,Retained Size是必不可少的,它有助于了解内存的结构(聚类)和对象子图之间的依赖关系,以及查找这些子图的潜在根源。
不过说实在的,因为JVM的存在,他自己的垃圾回收机制已经算是非常的不错了,但是因为我们在日常的业务中的需要,我们仍然需要去学习这些内容,毕竟万一在以后的实际工作中真的遇到了,你会发现你现在学的内容是非常有用的。
文献参考
《YouKit》 《Java性能权威指南》