CompressedOops: Java中compressed references介绍

原文地址:http://blog.csdn.net/seatalks/article/details/52981819

在这篇文章中,我们来聊聊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对象赋一个值。当你写出下面的这句代码时:

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字节了。

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的名字要一致!

import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;  

public class IntegerApplication {
    public static void main(String[] args) {
        List<Integer> intList = new LinkedList<>();
        for(int i=0;i<2000000;i++){
            Integer number = new Integer(1);
            intList.add(number);
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("application is running...");
        String tmp = scanner.nextLine();
        System.exit(0);
    }
}  

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

javac IntegerApplication.java

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

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

java IntegerApplication

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

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

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

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

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

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

java -XX:-UseCompressedOops IntegerApplication

接下来的步骤跟上面的一样。下面是关闭compressed oops的执行结果:

跟我们想的一样,内存占用增多了。 堆内存主要被两种类型的对象占用了,一种是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并没有什么坏处,并且它带给你的可能还是好处。对于编译器的一些细节的了解可以帮助你写出高效的代码。

时间: 2024-10-11 04:13:33

CompressedOops: Java中compressed references介绍的相关文章

【转】java中Thread类方法介绍

原文: java中Thread类方法介绍 http://blog.csdn.net/seapeak007/article/details/53395609 这篇文章找时间分析一下!!!:http://blog.csdn.net/apei830/article/details/4503112 --------------------------------------------------------------- 方法摘要 static int activeCount()          

Java中泛型的介绍与简单使用

学习目标 掌握泛型的产生意义. 掌握泛型的基本使用. 了解泛型的警告信息及泛型的擦除. 泛型是在JDK1.5之后增加的内容,泛型(Generic) 使用泛型的原因 题目分析: 首先要考虑到,必须建立一好一个表示坐标点的类--Point,此类中有两个属性分别用来表示x坐标和y坐标,但是x和y中所保存的整数类型会有三种(int.float.String),而要想使用一个类型可以同时接收这样的三种类型数据,现在只能使用Object,因为Object类可以接收任何类型的数据,都会自动发生向上转型操作,这

Java中static关键字介绍

static关键字主要有两种作用: 第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关. 第二,实现某个方法或属性与类而不是对象关联在一起 具体而言,在Java语言中,static主要有4中使用情况:成员变量.成员方法.代码块和内部类 (1)static成员变量: Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量.静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使

java中commons-beanutils的介绍

1.   概述 commons-beanutil开源库是apache组织的一个基础的开源库,为apache中许多类提供工具方法,学习它是学习其他开源库实现的基础. Commons-beanutil中包含大量和JavaBean操作有关的工具方法,使用它可以轻松利用Java反射机制来完成代码中所需要的功能,而不需要详细研究反射的原理和使用,同时,该类库中提出了动态Bean的概念,不但提供现有JavaBean的所有功能,而且还可以在运行时动态的对Bean中的属性数据类型进行修改以及增删属性. 本文研究

Java中RunTime类介绍

Runtime 类代表着Java程序的运行时环境,每个Java程序都有一个Runtime实例,该类会被自动创建,我们可以通过Runtime.getRuntime() 方法来获取当前程序的Runtime实例. 获取当前Jvm的内存信息 /* * 获取当前jvm的内存信息,返回的值是 字节为单位 * */ public static void getFreeMemory() { //获取可用内存 long value = Runtime.getRuntime().freeMemory(); Syst

9、Java中方法的介绍

我们在学习Java的过程中,在编写一些简单的程序时,会不断创建一个新的类和main方法.我们会发现这样编写代码非常的繁琐,而且重复的代码过多.能否避免这些重复的代码呢,就需要使用方法来实现. 方法:就是将一个功能抽取出来,把代码单独定义在一个大括号内,形成一个单独的功能. 当我们需要这个功能的时候,就可以去调用.这样即实现了代码的复用性,也解决了代码冗余的现象. 类似于C/C++中的函数 9.1 方法的定义 定义格式: 定义格式解释: 修饰符:目前固定写法:public static: 返回值类

15、java中的内部类介绍

内部类顾名思义就是定义在类中的类,下面做一个简单介绍: 内部类的访问规则:1,内部类可以直接访问外部类中的成员,包括私有. 之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this2,外部类要访问内部类,必须建立内部类对象. class Outer { private int x = 3; class Inner//内部类 { //int x = 4; void function() { //int x = 6; System.out.println("i

Java中的String介绍

一.概述 String是代表字符串的类,本身是一个最终类,使用final修饰,不能被继承. 二.String字符串的特征 1. 字符串在内存中是以字符数组的形式来存储的. 示例如下,可以从String的底层源码中看到. implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char valu

Java Socket应用(一)——java中网络基础介绍

转载请注明:http://blog.csdn.net/uniquewonderq java程序提供了一组功能强大的类,方便我们使用socket进行网络开发.如果两台计算机通过网络进行通信需要满足如下要求: TCP/IP是目前世界上应用最为广泛的协议. TCP: Transmission  Control  Protocol 传输控制协议 IP:Internet Protocol 互联网协议 常用的系统端口号:http:80 ftp:21 telnet:23 版权声明:本文为博主原创文章,未经博主