欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

CompressedOops: Java中compressed references介绍

java water 2819℃ 0评论

[原文地址] https://www.javacodegeeks.com/2016/05/compressedoops-introduction-compressed-references-java.html

在这篇文章中,我们来聊聊Compressed oops(压缩了的普通对象指针)。它是JVM的优化技术之一。为什么要提出compressed oops的概念呢?那是因为32位与64位的架构不同导致的。接下来,我们先简单回顾下64位的架构特征,然后再进一步深入地来讨论compressed oops。最后,我们通过 一个小例子来观察它的作用。由于这个小例子十分简单,所以我们不用任何IDE帮忙来写它的代码。

实际上Compressed oops在32位机上是不起任何作用的,并且在JDK6u23之前的版本中,它都默认是被关闭的。所以在这篇文章中我们用的是64位JDK并且版本比6u23版本高。实验中,我们只用到一个内存分析工具——industry standard Eclipse Memory Analyzer Tool (版本1.5).


1. 32位 vs. 64位

32位与64位的对比是在2000年以后兴起的。然而64位CPU早就在超级计算机领域中得到应用了,只是最近几年64位CPU才在PC上成为主流配置。从32位到64位的转变,绝对不是一件简单的工作,因为几乎所有的东西,从硬件到操作系统都必须要发生改变。Java也是在这个改革的趋势中引入了64位的虚拟机。

在32位到64位的转变中,人们最大的获益是内存容量。在一个32位的系统中,内存地址的宽度就是32位,这就意味着,我们最大能获取的内存空间是2^32(或者4 G)字节。这个容量如果放在个人电脑只有640KB内存的时代,那简直就好像是无穷大一样。但是对于现在连一个有1G内存的手机都算是低配的今天,我们就不能这么认为了!在一个64位的机器中,理论上,我们能获取到的内存容量是2^64字节,这是一个十分庞大的数字(ridiculously huge number)。可惜的是,这只是一个理论值,而现实中,因为有一堆有关硬件和软件的因素限制,我们能得到的内存要少得多。举个例了来说,Windows 7 Ultimate系统只能支持192GB的内存。可能许多人会说“192GB好大呀”,但是和2^64比起来,它真的挺小的,真的。好了,我们聊了半天,64位的重要性我们也知道了,那么接下来,我们就谈谈compressed oops能帮我们做什么。


2. Compressed oops 的概述

“天下没有免费的午餐”。我们在64位机器中能获取极大的内存容量也是有花费的。一般来说,一个应用跑在64位机上会花费更多的内存。而且如果不是那种可以乎略不记的小程序的话,这种花费可是不能被忽略不记的哦!而Compressed oops是在64位环境中使用32位类指针(class pointer),这样就可以帮助我们节省一些内存空间,但是要保证内存不大于32GB。接下来,我们细说说一个对象在Java里是如何表示的。


2.1. Java中的对象表示

我们先用一个小例子来帮助我们理解对象(objects)在Java中是如何表示的。我们先给一个Integer对象赋一个值。当你写出下面的这句代码时:

[java] view plain copy

  1. Integer i = new Integer(23);  


编译器实际上要在堆中给这个对象分配比32位多得多的空间。Java中整形数的值是32位,但是每个对象都含有一些
“头部”内容。而这些头部内容在不同的VM中是不同,在32位与64位虚拟机中也是不同的。在32位的虚拟机中,头部的每个“域”(field)的长度是一个字长(4字节)。而在64位虚拟机中,整形数的值域长还是32位,但是其他域的长度却增加到了8个字节(64位机中的一个字长)。如果你以为就这点差别的话,我就呵呵了!对象在内存中是要按字对齐的,也就是说,在64位机中,一个对象所占的内存要能被64整除。而我们要做文章主要的地方就是在Hotspot虚拟机中被称为“Klass”的类指针(class pointer)。从下图中,我们可以看到的是,在一个正常的64位虚拟机中klass的长度是8字节,但是启动compressed oops后,它就变成了4字节了。

Representation of an Integer object in different VMs

                         Representation of an Integer object in different VMs



2.2. compressed oops的实现

oop其实代表的是普通对象指针(ordinary object pointer)。这些对象指针和机器的本地指针的长度是一样长的,所以 oops  在32位机上是32位长,在64位机上是64位长。但是在compressed oops中我们可以在64位机器上使用32位长的指针。

compressed oops的关键就在于内存是按字节编址,还是按字编址。如果按字节编址,我们可以获取到内存中每个字节的内容,但是也需要对每个字节编址。在32位的环境中,这会限制你只有2^32的字节内存。但是如果用字编址的话,你还是可以访问这么多的内存块,但是每个内存块现在是一个字而不再是一个字节。在64位机中,一个字就是8个字节。这就会让JVM的地址最后三位为 0 。Java就利用通过移动(shifting)三位来达到扩大内存的并且实现compressed oops的目的。


3. Compressed oops的执行

为了看compressed oops的执行效果,我们来写一个简单的应用。我们使用一个LinkedList对象 list 200万个整形对象。

为了达到查看堆状态的目的,我们使用Eclipse Memory Analyzer Tool来分析结果。

既然这个例子中,我们不用Eclipse或其他的IDE,所以我们用一个文本编辑器创建一个名叫IntegerApplication.java的文件。把下面的代码敲进这个文件中。注意,文件名与java class的名字要一致!

[java] view plain copy

  1. import java.util.LinkedList;  

  2. import java.util.List;  

  3. import java.util.Scanner;  

  4.   

  5. public class IntegerApplication {  

  6.     public static void main(String[] args) {  

  7.         List<Integer> intList = new LinkedList<>();  

  8.         for(int i=0;i<2000000;i++){  

  9.             Integer number = new Integer(1);  

  10.             intList.add(number);  

  11.         }  

  12.         Scanner scanner = new Scanner(System.in);  

  13.         System.out.println("application is running…");  

  14.         String tmp = scanner.nextLine();  

  15.         System.exit(0);  

  16.     }  

  17. }  


在命令行提示符处,先cd到这个文件所在的目录下,然后用下面的命令行来编译它。

[plain] view plain copy

  1. javac IntegerApplication.java  


现在我们应该得到了一个IntegerApplication.class的文件。接下来,我们运行这个文件两次。第一次打开compressed oops,  第二次关闭。

由于Compressed oops在高于6u32版本的JVM中默认是打开的,所以我们可以直接在命令提示符中键入以下命令运行。

[plain] view plain copy

  1. java IntegerApplication  


上面的源码中有一个Scanner的对象。它用来保证你的程序能一直运行,除非你键入些东西来终止程序。如果你在命令提示符处看到了“application is running…”,你就可以打开内存分析器了(memory analyzer)。基于你的机器,有可能会要等一会儿,因为它要做一些初始化工作。


从文件菜单中选择“Acquire Heap Dumping…”选项。

Process selection window

                                      Process selection window

你可以看到一个进程选择窗口。选择“IntegerApplication”,然后点击“Finish”。

之后,你就可以看到内存分析器的主界面。在工具栏中,选择下图所示位置上的历史分析按钮。

Select Histogram from toolbar

                                   Select Histogram from toolbar

然后,你就可以看到程序中所有对象的信息了。下面是我们这个小例子在compressed oops 打开的情况下的信息。

Heap dump of application with compressed oops enabled.

         Heap dump of application with compressed oops enabled.


接下来,我们就关闭compressed oops。为了关闭它,我们可以使用 -XX:-UseCompressedOops 标识。你不需要重新编译你的程序,直接在命令行那里键入下面的命令:

[plain] view plain copy

  1. java -XX:-UseCompressedOops IntegerApplication  


接下来的步骤跟上面的一样。下面是关闭compressed oops的执行结果:http://www.javacodegeeks.com/wp-coheapDumpNotCompressed

 Heap Dump of application with  Compressed oops disabled

跟我们想的一样,内存占用增多了。 堆内存主要被两种类型的对象占用了,一种是list nodes,另一种是integers。200万个整形数在compressed oops的环境中需要3200万字节的空间,而把compressed oops关闭后,就需要4800万字节空间。这一个简单的小例子的执行结果,跟我们预期的一样。

2000000*(128/8) = 32000000 or 32 megabytes

2000000*(192/8) = 48000000 or 48 megabytes

如果仔细看下第二个等式,我们用了192而不是之前介绍java对象时图中的160。原因是,Java是按字节编址的,所以地址要跟最近的8字节对齐,这里的话就是192位了。


4. 总结

这里提供的例子可能有些差强人意,但是它很能反映实际情况。如果用H2数据库应用作测试用例,compressed oops可以把堆大小从3.6MB减小至3.1MB。这也意味着多了差不多14%可用的堆。显而易见,使用compressed oops并没有什么坏处,并且它带给你的可能还是好处。对于编译器的一些细节的了解可以帮助你写出高效的代码。

转载请注明:学时网 » CompressedOops: Java中compressed references介绍

喜欢 (0)or分享 (0)

您必须 登录 才能发表评论!