JAVA内存占用

本文详细探讨了Java中基本数据类型的内存占用规则,包括primitive类型、引用、Object类型及其子类如Integer和Byte的内存计算方式,并提供了一套优化程序内存使用的策略,特别针对32位系统的特性进行分析。
最近对程序占用内存方面做了一些优化,取得了不错的效果,总结了一些经验
简要说一下,相信会对大家写出优质的程序有所帮助
下面的论述针对32位系统,对64位系统不适用,后叙

经常你写了一个程序,一测试,功能没问题,一看内存占用也不多,就不去考虑其它的东西了。但可能程序使用了一个什么数据结构,会当数据规模变大时,内存占用激增。

基本&&关键的问题是,Java里各种东东占多少内存?????????

对于primitive类型,有8个
byte short int long float double char boolean 它们的长度分别是
1  2  4  8  4  8  2 1
这个不罗嗦了,举例来说
long[] data=new long[1000];
占用内存  8*1000 bytes
此外,data本身是一个Object,也占用内存若干,后叙,当然它针对  8*1000来说,忽略不计

再说Object的占用,在说这个之前,先说说引用,一惯的说法是
Java里没有指针了,只有引用,引用是安全的

这个说法没错,但是从机理上来说,引用就是指针,只是jvm对指针的使用检查和限制很多,这个引用/指针变得很安全

直接来结论:一个引用占4byte ,在32位系统上

Object obj=null;        //4byte
Object[] objs=new Object[1000]; //至少4*1000byte

你看我定义了一个 obj,还是null,就占4byte
定义了一个 objs,1000个元素,但都是null啊,就都每个占4byte
是的!!!!!
虽然obj==null,但它已经是 一个引用,或者说一个指针了
指针也要占地方啊!!!!啊!!!!啊!!!!

接下来,直接给另一个结论: Object占8byte,注意,纯Object

Object obj=new Object();  //多少????

8byte?? 错!! 12byte,忘了还有一个引用,8byte是Object的内容
记住  Object obj=new Object(); 占12byte

Object[] objs=new Object[1000];
for(int i=0;i<1000;i++) {
  objs[i]=new Object();
}

至少占用  12*1000 bytes

推论: Object占12bytes,似乎和上面的结论矛盾??!!
没有!! 不管Object,没有被垃圾回收之前,总得被别人引用吧?
总的有指针指它吧?  既然指,那个引用or指针就要占地方啊 4byte
加起来是12byte,反正一个Object至少 12bytes

还是直接给结论,推导的过程我就都包办了,咱不是脏活累活抢着干么!!
一个Integer占 16 bytes

这时您可能会有疑问,Integer=Object+int,就是:


public class Integer {
  public int value;
}
Integer应该占 8+4=12 bytes啊
你说的有道理,但是jvm对所有的Object有限制!!
这个限制被我发现了,就是不管什么Object占的空间,要是8的倍数
12不是8的倍数,只能是16了!!!

推论:Byte也占16bytes!!!!!!!!!!!

问:
Byte[] bytes=new Byte[1000];
占用空间多少?
答:  约为(至少为) (16+4)*1000 bytes
好家伙!!!!!!!!


论题:数组空间占用怎么算?
我这里直接给结论了,推导这个花了更长的时间:
对于数组来说,数组这个Object有一个length属性,数组的元素相当于其成员
public class Array {
  public int length;
  //... 其它成员
}
对于数组,我们不是直接可以取length属性么,源于此

public byte[] bytes=new byte[1000]; 
System.out.println(bytes.length);  // 看,有length属性
上面的bytes换算过来是:
public class Array {
 public int length;
 public byte byte0;
 public byte byte1;
 ...
 public byte byte999;
}
上面的bytes占用的内存是:
4+[8+4 + 1*1000] = 4+ [1012]=4+1016=1020
4是 bytes这个引用,8是Object基占的,4是length属性占的
1000是1000个成员占的,本来是 1012,但要求是8的倍数,变成 1016了
总共是 1020
再如:
byte[] bytes=new byte[4];
的内存占用是:
4+[8+4+4*1]=4+[16]=20;

byte[] bytes=new byte[3];  也是 20

对于元素是Object的数组,Object也是当作其成员,(注意只有引用这个数组的空间,这个可以推到普通Class上)

Byte[] bytes=new Byte[1000];
这个 bytes的定义相当于:
public class Array {
  public int length;
  public Byte byte0;
  .....
  public Byte byte999;
}
占用空间是:
4+[8+4+4*1000]+16*1000= 4+ 4016 + 16000  = 你自己算吧

推论:千万不要用  Byte[]  有20倍的差距!!!!!!!


你可能一下子没明白过来,没关系多琢磨一下,对于普通的class来说
,内容占用就是基加成员的占用,Object成员只记引用
public class Abc {
   public int n;
   public byte b;
   public Object obj;
}
它的内容占用是: [8+4+1+4]=24
所以  Abc one=new Abc()的占用是 4+24=28
提醒:对于 Abc的成员 obj没有计,如果要计入的话,循环这个过程就可以了。(琢磨一下)

举例:

public class Abc {
   public byte b;
   public Object obj=null;
}

public class Def {
   public int n;
   public byte b;
   public Abc obj=new Abc();
}
问:
Def one=new Def(); //占多少?
答:
4+[8+4+1+4]+[8+1+4]=4+24+16=44


public class Abc {
   public byte b;
   public Object obj=null;
}

public class Def {
   public int n;
   public byte b;
   public Abc[] objs=new Abc[100];
   {
      for(int i=0;i<10;i++) {
         objs[i]=new Abc();
      }
   }
}
问:
Def one=new Def(); //占多少?
答:
kao,一下我也算不出来,不过我写了程序,可以算出来,你给它一个Object,它就能递归的算出总共占了多少内存,这个程序不复杂,你也可以写出来。我等机会合适了再放出。

单独说一下String,String的结构是:
public class String {
    private final char value[];
    private final int offset;
    private final int count;
    private int hash; // Default to 0
}
所以,不考虑那个char[]的占用,一个String最少占用 [8+4+4+4+4]=24bytes
加上引用,共28bytes
所以
String s="";
占用28bytes!!!!! 尽管它的长度为0
如果精确的算,加上引用一个String的占用是
4+24+[8+4+2*length]
String s=""; 的占用是  28+16=  44
String s="ab" 的占用是  28+16= 44
String s="abc" 的占用是 28+24 = 52

要说的是,String是常用的类,这么看,String耗内存很多,所以jvm有优化,同样的内容尽量重用,所以除了28是必须的外,那个char[] 很可能一样
比方说
String[] s=new String[1000];
for(int i=0;i<1000;i++) {
   s[i]=new String("abcdefasdjflksadjflkasdfj");
}
的占用的数量级是  28*1000,那 1000个字符串本身基本上不占内存,只有一份!!!!!!
反正String 至少是 28,最多也可能是28!!!!!!!!


比较占内存的数据结构,这个很重要:
基本上就是  primitive的包装

实例:
我以前用一个
 Hashtable<String,Integer>的结构,有100万个元素
改为String[]+int[]后,内存占用改观不少,速度也很快
100万的String[] 快排一下,也就2秒多,查找用2分,和hash也差不多少

完!


说明:
1。 以上结论适用于32位系统,对于64位系统,有很多不同。反正结论是虽然64位系统能用的内容更多了,但相同的程序占用内存也多了不少
2。 上面讨论的是类的实例占用的内存,没有考虑静态变量的占用。 静态变量引用的是算在Class数据里的,内容的占用和实例无关,单独计算就可以了
3。 以上没有考虑 Class本身占的内存。 Class本身也需要占地方啊,就是类的结构,以及静态变量的引用的占用。但是这个占用是静态的,不随实例变多而变多的。也不好统计出来,想统计的话,看jvm源码里jclass的表示
4。 别人的劳动成果请尊敬,转载请注明作者zms。
### Java 内存占用过高且不释放的解决方法 Java 程序在运行过程中可能会出现内存占用过高且无法释放的问题,这通常与内存泄漏、对象生命周期管理不当或 JVM 内存参数配置不合理有关。以下是针对这一问题的排查与解决方法。 #### 内存泄漏排查 内存泄漏是导致内存占用过高且无法释放的主要原因之一。Java 中的内存泄漏通常表现为对象不再被使用,但由于某些引用未被释放,导致垃圾回收器(GC)无法回收这些对象。 - **使用内存分析工具**:可以使用 Eclipse Memory Analyzer (MAT) 或 VisualVM 等工具来分析内存快照(heap dump),识别哪些对象占用了大量内存,并检查它们的引用链,找出未被释放的原因[^3]。 - **检查静态集合类**:静态集合类(如 `static List` 或 `static Map`)的生命周期与应用程序相同,如果不断向其中添加对象而不移除,很容易导致内存泄漏。 - **监听器和回调**:注册的监听器(如事件监听器)如果没有正确注销,也可能导致内存泄漏。 #### 优化对象创建与销毁 频繁创建和销毁对象会增加垃圾回收的负担,进而影响内存使用效率。 - **对象池化**:对于频繁创建的对象,可以使用对象池技术来复用对象,减少垃圾回收的压力。例如,使用 Apache Commons Pool 或自定义对象池。 - **避免不必要的对象创建**:在代码中尽量避免在循环或高频调用的方法中创建临时对象。例如,使用 `StringBuilder` 而不是字符串拼接操作符 `+` 来构建字符串。 - **及时释放资源**:确保在使用完资源(如 IO 流、数据库连接)后及时关闭并释放相关对象,避免资源泄露。 #### JVM 内存参数调优 JVM 的内存配置对程序的内存占用有直接影响,合理的参数设置可以有效降低内存占用。 - **调整堆内存大小**:通过 `-Xms` 和 `-Xmx` 参数设置 JVM 堆内存的初始值和大值。如果内存占用过高,可以适当减少大堆内存,迫使垃圾回收器更频繁地回收内存[^1]。 - **调整新生代与老年代比例**:通过 `-XX:NewRatio` 参数调整新生代和老年代的比例。如果程序创建了大量生命周期的对象,可以适当增加新生代的大小,以减少老年代的内存压力。 - **选择合适的垃圾回收器**:不同的垃圾回收器对内存的管理方式不同。例如,G1GC(Garbage-First Garbage Collector)在处理大堆内存时表现更优,适合内存密集型应用;而 ZGC 或 Shenandoah GC 则适用于低延迟场景。 #### 监控与日志分析 持续监控程序的内存使用情况和垃圾回收行为有助于及时发现内存问题。 - **使用 JVM 自带工具**:如 `jstat`、`jmap` 和 `jconsole`,可以实时查看内存使用情况、GC 频率及对象分配情况。 - **启用 GC 日志**:通过 `-Xlog:gc*:time`(JDK 9+)或 `-XX:+PrintGCDetails` 和 `-XX:+PrintGCDateStamps` 参数启用详细的 GC 日志输出,分析 GC 行为是否正常。 - **使用 APM 工具**:如 New Relic、AppDynamics 或 SkyWalking 等应用性能管理工具,可以提供更全面的内存监控和分析功能。 #### 示例:使用 `jmap` 生成堆转储文件 ```bash jmap -dump:live,format=b,file=heapdump.hprof <pid> ``` 该命令会生成当前 Java 进程的堆转储文件,后续可以使用 MAT 工具进行分析,查找内存占用高的对象及其引用链。 #### 示例:JVM 启动参数配置 ```bash java -Xms512m -Xmx2g -XX:NewRatio=3 -XX:+UseG1GC -Xlog:gc*:time -jar yourapp.jar ``` 此配置设置了初始堆内存为 512MB,大堆内存2GB,使用 G1GC 垃圾回收器,并启用了详细的 GC 日志输出。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值