Java应用程序中每个类的成本 - 较少的大类或几个较小的类

时间:2014-03-01 04:33:42

标签: java performance java-ee memory

对于添加到Java应用程序的每个新类,内存成本是多少?

  • 拥有5000多个大型课程或几个500-1000个课程类别(如果所有这些课程都已加载)会更好吗
  • 每次实例化对象时,唯一的额外内存使用情况是实例变量引用
  • 对于没有实例变量的5000行类,加载类时的成本比例是多少?类文件的大小是粗略的近似值吗?
  • jar文件的大小是否表示类将占用的内存的常规或最大大小?

在cruftex的回答后编辑: 这是我对课堂分裂的理解:

  • 拆分成逻辑块可以很好地改善代码重用和 减少行数
  • 它也使人们更容易理解和 维护代码

这是我对班级加载的理解:

  • 首次使用Class加载到内存中(使用的内存大致是类文件的大小)
  • 如果使用JIT,JIT编译器会创建一些额外的机器友好二进制版本,它使用更多的内存
  • 如果使用Hotspot,只有部分常用类使用机器友好版本进行优化(以平衡内存和速度)
  • 加载一个类后,创建其他实例的开销可以忽略不计(大约50-100个字节?)(假设没有实例变量)
  • 加载类后,类本身永远不会被垃圾回收

这大概是如何运作的?

2 个答案:

答案 0 :(得分:5)

您的问题无法回答具体问题,因此我会以一般方式进行尝试。你的问题针对记忆消耗。但是,如果你节省了内存,那么你的性能很可能会受到影响,这就是所谓的时空权衡。请参阅:http://en.wikipedia.org/wiki/Space%E2%80%93time_tradeoff

节省内存的最简单方法 - 如果您关注您的程序大小 - 将完全关闭即时编译器。这样就可以保存java类机器代码的程序存储。顺便说一句:有很多VM选项会影响内存消耗!

  

拥有5000多个大型课程或几个500-1000个课程类别(如果所有这些课程都已加载)会更好吗

一般而言,您可能更有可能在较小的单位内更好地重复使用代码。这意味着,您可以在针对较小尺寸时保存代码行。

如果代码/方法保持不变并且您只是在类之间分配它们,则会为类添加额外的开销。

即使您询问了三位专家并且他们查看了代码,他们也会得到不同的答案,因为它在很大程度上取决于您的实际代码,使用模式以及JIT在运行时使用它做了什么

尽管如此,即使是实验也可能导致您走错方向,因为您需要模拟相同的使用模式。

关于这一点的最后一点:看看您的VM加载了多少个类,并确定是否真的值得保存视图。

  

每次实例化Object时,唯一的额外内存使用是针对实例变量引用

加上内存管理的持续开销。

  

对于没有实例变量的5000行类,加载类时的成本比例是多少?类文件的大小是粗略的近似值吗?

没有JIT:是的。

  

jar文件的大小是否表示类将占用的内存的常规或最大大小?

不,完全没有,因为只在需要时才加载类。

一些建议:

如果您担心程序的内存使用情况,请首先检查堆两次。我建议使用jmap和eclipse内存分析器。

然后,您可以查看您的课程和方法。您可以调试JIT并查看编译方法需要多少空间:-XX:-PrintCompilation。

答案 1 :(得分:4)

  

对于添加到Java应用程序的每个新类,内存成本是多少?

通常没关系。一般来说,只有一小部分(比如说5%)的整体内存使用被代码以各种形式占用。因此,即使您确实将代码大小减少了一半,整体内存使用量也只会略有下降。

相比之下,过长的源文件会使代码库难以导航,而较大的范围使得更难以全面了解该类的功能以及某个更改是否安全。因此,长源文件会使修改代码变得更加昂贵且容易出错。

  
      
  • 首次使用Class加载到内存中(使用的内存大致是类文件的大小)
  •   

正确。

  
      
  • 如果使用JIT,JIT编译器会创建一些额外的机器友好二进制版本,它使用更多的内存
  •   
  • 如果使用Hotspot,只有部分常用类使用机器友好版本进行优化(以平衡内存和速度)
  •   

Hotspot是一个JIT,所以你在这里重复一遍。但是,是的,JIT确实增加了代码大小(但速度更快)。

  

加载一个类后,创建其他实例的开销可以忽略不计(大约50-100个字节?)(假设没有实例变量)

这是特定于JVM的。在Oracle Hotspot JVM上,每个对象的内存开销大约为8个字节,如以下程序所示:

public class Test {
    public static void main(String[] args) {
        Object[] array = new Object[10_000_000];
        Runtime rt = Runtime.getRuntime();
        long usedBefore = rt.totalMemory() - rt.freeMemory();
        for (int i = 0; i < array.length; i++ ) {
            array[i] = new Object();
        }
        long usedAfter = rt.totalMemory() - rt.freeMemory();
        System.out.println(usedBefore);
        System.out.println(usedAfter);
        System.out.println((double)(usedAfter - usedBefore) / array.length);
    }
}
  
      
  • 加载类后,类本身永远不会被垃圾回收
  •   

虽然它没有被Java语言规范强制要求,但是我使用的每个JVM在其ClassLoader变得无法访问时释放一个类(被授予,引导类加载器将始终保持可访问状态,但自定义ClassLoader可能无法访问)。