JVM调优总结(一)

Posted on

JVM调优总结(一)-- 一些概念 - 高级语言虚拟机

您还未登录 ! 登录 注册

ITeye3.0

群组首页编程语言高级语言虚拟机知识库JVM调优JVM调优总结(一)-- 一些概念 原创作者: 和你在一起 阅读:8621次 评论:14条 更新时间:2011-05-26

数据类型

Java虚拟机中,数据类型可以分为两类:**基本类型**和**引用类型**。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress

引用类型包括:类类型接口类型数组

堆与栈

堆和栈是程序运行的关键,很有必要把他们的关系说清楚。

栈是运行时的单位,而堆是存储的单位

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗

第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

在Java中,Main函数就是栈的起始点,也是程序的起始点

程序要运行总是有一个起点的。同C语言一样,java中的Main就是那个起点。无论什么java程序,找到main就找到了程序执行的入口:)

堆中存什么?栈中存什么

堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。

为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。

Java中的参数传递时传值呢?还是传引用

要说明这个问题,先要明确两点:

     1. **不要试图与C进行类比,Java中没有指针的概念**

     2. **程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题**。不会直接传对象本身。

明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以**它都是进行传值调用**(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。

*但是传引用的错觉是如何造成的呢?*在运行栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。

对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。



堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为可能。

 Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时栈中保存的信息都是方法返回的记录点。

JVM调优总结(二)-一些概念

评论 共 14 条 请登录后发表评论

14 楼 yangpanwww 2013-05-24 10:44

受益匪浅

13 楼 wangpeihu 2013-03-22 10:48

楼主分析的很透彻,很到位。 在这里我有2个问题想请教楼主: 引用

栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等 能否将这些局部变量、程序运行状态、方法返回值等放在堆里,而堆里的对象放在栈里呢?说白了,就是他俩的工作职责对调一下可以吗? 我经常看到人说,栈的运行效率较高,堆的效率较低(相对于栈而言),是否是因为这个原因,而促成了堆栈现在的功能分配呢?

12 楼 chancong 2013-03-21 17:42

学习了,受益匪浅

11 楼 xwl1991 2013-02-18 10:05

受益匪浅 非常感谢!

10 楼 xiaodatao 2012-01-19 16:33

程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。 在Java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。 堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为可能。

9 楼 diecui1202 2012-01-05 14:16

楼主Boolean是不是写错了?是boolean吧!

8 楼 bsr1983 2011-12-16 22:54

写的很不错,赞一个!

7 楼 lknh 2011-09-22 23:46

学习了,写得很好,很详细、易懂!

6 楼 shaomeng95 2011-07-08 10:37

写的很好,通俗易懂!

5 楼 smalltalker 2011-05-30 13:47

非常不错。文章讲的很透彻,和容易让人理解,看了之后有种豁然开朗的感觉。

4 楼 ivin 2011-03-15 11:56

不错啊,非常透彻,感谢感谢!

3 楼 suxianchun 2011-01-13 17:25

gauge

2 楼 suxianchun 2011-01-13 17:24

1 楼 suxianchun 2011-01-13 17:24

发表评论

您还没有登录,请您登录后再发表评论

New-page

文章信息

知识库: 高级语言虚拟机

相关讨论

© 2003-2012 ITeye.com. [ 京ICP证110151号 京公网安备110105010620 ] 百联优力(北京)投资有限公司 版权所有

JVM调优总结(七)

Posted on

JVM调优总结(七)-典型配置举例1

以下配置主要针对分代垃圾回收算法而言。

堆大小设置

年轻代的设置很关键

JVM中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。

典型设置: java -Xmx3550m -Xms3550m -Xmn2g –Xss128k

-Xmx3550m:设置JVM最大可用内存为3550M。

-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxPermSize=16m:设置持久代大小为16m。

-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

回收器选择

JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

吞吐量优先的并行收集器

如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

典型配置: java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20

-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。

-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC

-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100

-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

n java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy

-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

响应时间优先的并发收集器

如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

典型配置: java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。

-XX:+UseParNewGC: 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

辅助信息

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

-XX:+PrintGC:输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails:输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

-XX:+PrintGCTimeStamps-XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用 输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用。输出形式:Application time: 0.5291524 seconds

-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用。输出形式:Total time for which application threads were stopped: 0.0468229 seconds

-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息。输出形式:

34.702: [GC {Heap before gc invocations=7:

def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)

eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)

from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)

to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)

tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)

the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)

compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)

the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)

ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)

rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)

34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:

def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)

eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)

from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)

to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)

tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)

the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)

compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)

the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)

ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)

rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)

}

, 0.0757599 secs]

-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。

JVM调优总结(九)

Posted on

JVM调优总结(九)-新一代的垃圾回收算法

垃圾回收的瓶颈

传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限。但是他无法解决的一个问题,就是Full GC所带来的应用暂停。在一些对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的。这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆的设置限制在一个相对较小范围内,但是这样有限制了应用本身的处理能力,同样也是不可接收的。

分代垃圾回收方式确实也考虑了实时性要求而提供了并发回收器,支持最大暂停时间的设置,但是受限于分代垃圾回收的内存划分模型,其效果也不是很理想。

为了达到实时性的要求(其实Java语言最初的设计也是在嵌入式系统上的),一种新垃圾回收方式呼之欲出,它既支持短的暂停时间,又支持大的内存空间分配。可以很好的解决传统分代方式带来的问题。

增量收集的演进

增量收集的方式在理论上可以解决传统分代方式带来的问题。增量收集把对堆空间划分成一系列内存块,使用时,先使用其中一部分(不会全部用完),垃圾收集时把之前用掉的部分中的存活对象再放到后面没有用的空间中,这样可以实现一直边使用边收集的效果,避免了传统分代方式整个使用完了再暂停的回收的情况。

当然,传统分代收集方式也提供了并发收集,但是他有一个很致命的地方,就是把整个堆做为一个内存块,这样一方面会造成碎片(无法压缩),另一方面他的每次收集都是对整个堆的收集,无法进行选择,在暂停时间的控制上还是很弱。而增量方式,通过内存空间的分块,恰恰可以解决上面问题。

Garbage Firest(G1)

这部分的内容主要参考这里,这篇文章算是对G1算法论文的解读。我也没加什么东西了。

目标

从设计目标看G1完全是为了大型应用而准备的。 支持很大的堆

高吞吐量

--支持多CPU和垃圾回收线程

--在主线程暂停的情况下,使用并行收集

--在主线程运行的情况下,使用并发收集

实时目标:可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收

当然G1要达到实时性的要求,相对传统的分代回收算法,在性能上会有一些损失。

算法详解

G1可谓博采众家之长,力求到达一种完美。他吸取了增量收集优点,把整个堆划分为一个一个等大小的区域(region)。内存的回收和划分都以region为单位;同时,他也吸取了CMS的特点,把这个垃圾回收过程分为几个阶段,分散一个垃圾回收过程;而且,G1也认同分代垃圾回收的思想,认为不同对象的生命周期不同,可以采取不同收集方式,因此,它也支持分代的垃圾回收。为了达到对回收时间的可预计性,G1在扫描了region以后,对其中的活跃对象的大小进行排序,首先会收集那些活跃对象小的region,以便快速回收空间(要复制的活跃对象少了),因为活跃对象小,里面可以认为多数都是垃圾,所以这种方式被称为Garbage First(G1)的垃圾回收算法,即:垃圾优先的回收。

回收步骤:

初始标记(Initial Marking)

G1对于每个region都保存了两个标识用的bitmap,一个为previous marking bitmap,一个为next marking bitmap,bitmap中包含了一个bit的地址信息来指向对象的起始点。

开始Initial Marking之前,首先并发的清空next marking bitmap,然后停止所有应用线程,并扫描标识出每个region中root可直接访问到的对象,将region中top的值放入next top at mark start(TAMS)中,之后恢复所有应用线程。

触发这个步骤执行的条件为:
G1定义了一个JVM Heap大小的百分比的阀值,称为h,另外还有一个H,H的值为(1-h)/*Heap Size,目前这个h的值是固定的,后续G1也许会将其改为动态的,根据jvm的运行情况来动态的调整,在分代方式下,G1还定义了一个u以及soft limit,soft limit的值为H-u/*Heap Size,当Heap中使用的内存超过了soft limit值时,就会在一次clean up执行完毕后在应用允许的GC暂停时间范围内尽快的执行此步骤;

在pure方式下,G1将marking与clean up组成一个环,以便clean up能充分的使用marking的信息,当clean up开始回收时,首先回收能够带来最多内存空间的regions,当经过多次的clean up,回收到没多少空间的regions时,G1重新初始化一个新的marking与clean up构成的环。

并发标记(Concurrent Marking)

按照之前Initial Marking扫描到的对象进行遍历,以识别这些对象的下层对象的活跃状态,对于在此期间应用线程并发修改的对象的以来关系则记录到remembered set logs中,新创建的对象则放入比top值更高的地址区间中,这些新创建的对象默认状态即为活跃的,同时修改top值。

最终标记暂停(Final Marking Pause)

当应用线程的remembered set logs未满时,是不会放入filled RS buffers中的,在这样的情况下,这些remebered set logs中记录的card的修改就会被更新了,因此需要这一步,这一步要做的就是把应用线程中存在的remembered set logs的内容进行处理,并相应的修改remembered sets,这一步需要暂停应用,并行的运行。

存活对象计算及清除(Live Data Counting and Cleanup)

值得注意的是,在G1中,并不是说Final Marking Pause执行完了,就肯定执行Cleanup这步的,由于这步需要暂停应用,G1为了能够达到准实时的要求,需要根据用户指定的最大的GC造成的暂停时间来合理的规划什么时候执行Cleanup,另外还有几种情况也是会触发这个步骤的执行的:
G1采用的是复制方法来进行收集,必须保证每次的”to space”的空间都是够的,因此G1采取的策略是当已经使用的内存空间达到了H时,就执行Cleanup这个步骤;

对于full-young和partially-young的分代模式的G1而言,则还有情况会触发Cleanup的执行,full-young模式下,G1根据应用可接受的暂停时间、回收young regions需要消耗的时间来估算出一个yound regions的数量值,当JVM中分配对象的young regions的数量达到此值时,Cleanup就会执行;partially-young模式下,则会尽量频繁的在应用可接受的暂停时间范围内执行Cleanup,并最大限度的去执行non-young regions的Cleanup。

展望

以后JVM的调优或许跟多需要针对G1算法进行调优了。

记录帖:碰到的一些Java问题(更新于2013

Posted on

记录帖:碰到的一些Java问题(更新于2013-03-06)

BlueDavy之技术blog

{互联网,OSGi,Java, High Scalability, High Performance,HA}

记录帖:碰到的一些Java问题(更新于2013-03-06)

Aug 09

bluedavyJava, jvm btrace, Java Cases, Java Troubleshooting, Java问题 11 Comments 这个贴用于记录自己碰到过的一些Java问题,会根据经验不断增加,以便总结,:)

case: 一次非常诡异的CMS GC频繁问题的排查 详细见此贴:https://bluedavy.com/?p=424

case: 某应用在压测一台物理机上的多台虚拟机时不能保持线性增长的案例 现象描述: 一台16 core(HT)的物理机上,创建了8个虚拟机,每个虚拟机独占两个core,每个虚拟机里部署的都是同样的应用,按道理来说,在应用没有瓶颈的情况下,每增加一台虚拟机,tps应该可以保持线性增长,不过在压测的时候发现,在增加到第四台的时候(每次4台挑选的都是不同的机器),有些时候能线性增长,有些时候不能。 解决过程: 1、经过手工调整其中某台虚拟机的cpu,发现在12/13 cpu加入时,整个tps就会下降,于是猜想莫非和这两个核还有关系; 2、猜测是和中断处理有关系,发现core 12处理的中断确实比较其他的多一些,于是把irqbalance关了,调整了中断的绑定,还是一样的现象; 3、看了下四台虚拟机绑定的cpu的分布状况,发现12/13刚好和其中一台虚拟机绑定的4/5是同样的两个core,4/12其实是同一个core HT的,5/13是另一个core HT的,而这个应用是很耗cpu的,单压某个虚拟机cpu us就可以到90%左右,所以如果压测的4台如果有CPU刚好是同一个Core HT的,性能自然是上不去的。 总结: 和各种文章中讲的一样,CPU HT只有在理论的某些场景能达到2个真实core的性能,但多数情况都达不到,貌似很多场景的经验值是2 core(HT) = 1.3 Core。

case: 一个Java应用频繁抛异常导致cpu us诡异现象的案例 详细见此贴:http://blog.bluedavy.com/?p=409

case: 某应用在运行个一两天后就会把物理内存耗光 解决过程: 1、先按经验查了下有没有错误使用Inflater和Deflater,没有,于是继续; 2、继续上perftools,看看到底什么原因造成的,结果在6u21版本上看到的很诡异,是JVM_handle_linux_signal占了最多,觉得不靠谱,于是先升级成了6u26; 3、再分析,看到os::malloc占用了最多,但其他的就完全没头绪了; 4、在@JosephWang_CN的帮助下,图形分析下了perftools的stack trace,才发现还是unsafe_allocate的地方,但这次现象和上个case不同,不同点在于这次是由于cms gc的bug造成的,bug id为7112034,这个bug会造成即使direct bytebuffer已经无引用了,但在cms gc时其并不会被清除掉,而要等到full gc才会清除,官方版本在6u32中修复此bug(这个很容易验证,如果没开启ExplicitGCInvokesConcurrent,用jmap -histo:live强制触发下); 5、在fix了这个bug的前提下,还需要限制-XX:MaxDirectMemorySize的大小才行,否则可能会出现还没到触发cms gc时,物理内存就用完了的现象。 总结: 根据多次排查Java Heap外内存泄露的问题,目前的经验为: 1、先查查看有没有错误使用Inflater和Deflater,如有则基本就搞定了; 2、多执行几次jmap -histo:live,看看内存会不会下降,如果会的话,多数和GC的bug有关; 3、perftools,对调用次数的那列进行排序(pprof –text … | sort -n -r -k4),如果看到是Unsafe_Allocate比较多,且为server端应用,则通常说明是哪个地方分配了Direct ByteBuffer,但来不及释放引用,然后嘛,就是用btrace跟踪下看看谁干的,分析原因。

case: 某应用在压测一段时间后就会把物理内存耗光 解决过程: 1、从gc log以及jstat信息来看,java heap内的内存消耗并不多,但堆外消耗非常严重,导致了物理内存被耗光; 2、于是装上google perftools,看看堆外到底是什么原因造成的消耗; 3、分析了下google perftools的内存malloc消耗,主要是调用Unsafe.allocate造成的; 4、通常调用Unsafe.allocateMemory来分配内存的,只有Direct ByteBuffer和AWT,这应用是没用AWT的,Direct ByteBuffer倒是用到了; 5、网上google了一会,找到一个貌似和这个应用的场景很像的内存泄露的现象,具体信息请见:http://t.co/S9jvDt8O,号称是SocketChannel.write的时候,如果是Direct ByteBuffer会导致memory leak,而且Trustin Lee(Mina/Netty的作者)也这么说的:”it’s a known bug”; 6、于是建议应用的同学将ByteBuffer的地方改成不用direct方式; 7、改完后,重新压测,物理内存消耗是没那么严重了,但java heap用满了后回收不了了; 8、于是dump内存,用mat分析后发现是由于这个应用本身处理上的一些缺失造成的,简单说下,这个应用是一个基于mina的应用,而应用没有对session上的发送请求做限流,而mina来不及发送,导致积压了很多WriteRequest,从而内存被耗光了; 9、对这个问题的解决方法是:限流,当未发送的字节数到达某个比率后,就暂时先不发送了,或者报错等等,用netty能对这个现象有一定的缓解,但限流动作还是要做,否则可能会出现接收方处理慢,从而导致发送方内存用完的现象。

case: 某应用在压测时很容易出现promotion failed的现象 解决过程: 1、从promotion failed时的gc日志来看,状况为新生代中有1.5g的对象存活,旧生代此时的空余空间为4g,足以放下新生代存活的这些对象,于是只能猜测可能是cms gc的情况下旧生代的碎片问题导致的,其实心里是不太相信的,因为这空间空闲了如此的多呀; 2、首先增加-XX:+PrintPromotionFailure看看Promotion Failed的时候是不是有很大的对象要晋升到旧生代,结果打出来的日志显示在晋升的时候失败的竟然只是申请262146个block的空间而已,这是在开启了压缩指针的情况,因此每个block为4个字节; 3、还是不能确定,于是增加-XX:+CMSDumpAtPromotionFailure,看看OLD Gen的碎片状况; 4、打出来的信息显示old gen拥有最多free block的chunk也只有99977个而已,有53w+个chunk,里面大部分都只有999个free block,可见碎片相当严重; 5、到这步只是确定了碎片很严重,暂时能想到的只有每天半夜的时候主动执行下jmap -histo:live,尽可能减少碎片吧,如果后续有什么改进的动作,再来记录。

case: 某应用在发布的过程中突然crash了几台 解决过程: 1、应用crash后hs_err/*.log以及core.[pid]文件都没生成; 2、先打开core dump; 3、crash后gdb core dump看: ?1

2 3

4 (gdb) bt

/#0 0x00002ba466f82971 in ?? () from /opt/jdk-1.6.0_26/jre/lib/amd64/server/libjvm.so /#1 0x00002aab78a7e20d in Java_java_net_SocketOutputStream_socketWrite0 ()

from /opt/jdk-1.6.0_26/jre/lib/amd64/libnet.so

4、看到上面的core dump完全木有头绪,后来@rednaxelafx 介入处理,才知道原来jstack是可以跟core dump文件提取出当时java程序的状况的; 5、jstack出来一看,发现程序里有个地方无限递归…so… 总结: @rednaxelafx接着又进一步分析了为什么没有生成hs_err/.log,是因为从jdk 5.0以后的版本,hs_err/.log就是在crash的那个线程来生成,而这个案例中crash时的那个线程无限递归,而且正在native栈上,栈空间被消耗光了,所以hs_err/*.log文件就没生成出来。

Case: 某应用偶尔会出现极为频繁的Promotion failed,导致cms gc切换为串行full gc,从而响应变得超级慢 解决过程: 1、从promotion failed的日志来看,应该是在那段时间内内存被消耗光了造成的,因此在promotion failed的时候dump了内存; 2、可惜dump出来的那个内存文件打不开,几次都是如此; 3、还好我们自己定制的jdk版本这个时候发挥了作用,这个版本中有个功能是如果代码中有地方分配了超级大的数组,会打印出堆栈; 4、在某次出问题的时候,我们看到了这样的堆栈信息: ?1

2 3

4 5

6 7

8 9

10 11

12 13==WARNNING== allocating large array: thread_id[0x00002aaac2d85800], thread_name[ajp-0.0.0.0-8009-48], array_size[782611408 bytes], array_length[195652847 el

ememts]

at java.util.Arrays.copyOf(Arrays.java:2734)

at java.util.ArrayList.ensureCapacity(ArrayList.java:167)

at java.util.ArrayList.add(ArrayList.java:351)

at net.sf.json.JSONArray._fromJSONTokener(JSONArray.java:1154)

at net.sf.json.JSONArray.fromObject(JSONArray.java:147)

at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:358)

at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:1128)

at net.sf.json.JSONObject._fromString(JSONObject.java:1317)

at net.sf.json.JSONObject.fromObject(JSONObject.java:185)

at net.sf.json.JSONObject.fromObject(JSONObject.java:154)

at 我们自己的代码...

5、有这个堆栈后就可以知道是由于自己的代码这个地方触发了json创建了一个巨大的array; 6、于是怀疑是我们传给了json一个很大的String,就先在应用层做了个保护,超过一定大小的String就直接抛错; 7、加上了这个保护后还是出问题了; 8、因此怀疑是json内部有bug,导致传了某种格式的String后就会出现死循环什么的; 9、“神”看代码后,构造了一个这样的String s = “{tag:[0,1,”;然后调用JSONObject.fromObject(s),就会OOM; 10、于是修改了json的代码,修复此bug…

Case: 某应用cms gc时concurrent-mark阶段应用竟然没有响应… 解决过程: 1、在cms gc进行的过程中用pstack采集目前java进程正在做什么; 2、从日志分析来看貌似是在concurrent-mark时,很多的java线程在分配内存的时候被锁住了… 3、将相关信息发给官方的gc dev maillist,只是得到了升级到6u28试试的建议… 目前问题尚未解决,先在此mark下,和这个url提到的问题基本一致:http://t.co/EU92I8nN

Case: 某应用cms gc非常频繁,并且很容易出现concurrent mode failure造成Full GC 解决过程: 1、从gc日志来看,cms gc每次在旧生代使用才到18%左右的时候就触发了,而设置的触发比率是80%,并且不允许JVM自行触发; 2、每几次cms gc还会偶尔出现concurrent mode failure的现象,看了下,这时旧生代的空间是非常充足的; 3、根据cms gc的触发机制,猜测是permGen的问题造成的,于是通过jstat -gcutil看了下permGen的占用比例,果然过一会就超过下92%(不过这里有个问题,就是permGen其实是还没达到MaxPermSize的,看来在cms gc的情况下,maxPermSize要采用不同策略); 4、于是适当的调大permGen,不过仍然没有解决,原因是permGen增长实在太快了; 5、应用方发现有地方会不断的创建classLoader,加载class,因此导致了permGen的增长,修改后问题解决。 总结: CMS GC的日志其实打的有些问题,应该把cms gc触发时的原因输出下; 另外就是在大部分情况下,只要不是cms gc触发的比例设置的太有问题,如果cms gc频繁或full gc频繁都是应用本身的问题造成的。

Case: 某应用出现启动后集群中部分node成功,部分node失败 解决过程: 1、失败的node抛出的是NoClassDefFoundError,这些node在环境上和应用包上是完全一致的,因此猜想是classloader装载class时出现了什么问题; 2、在启动参数上增加了-XX:+TraceClassLoading,想看看成功的node和失败的node是不是从第一个地方加载的相应的类,悲催的是成功的node加上了这参数后启动超慢,于是只好放弃; 3、由于不能用TraceClassLoading,只好从ClassLoader方面来跟踪这个类的加载,于是从应用层上做了相应的分析,找出了相应的classloader,然后用btrace相应的进行了跟踪,才终于发现成功的node和失败的node装载此类时不是从同一地方装载的,而其中有一个地方的这个类引用了一个不存在的类,于是就出现了NoClassDefFoundError。 总结: 在经过分析后,猜想是在这两个不同的node上classloader在list一个目录下的文件时,出现了顺序不同的现象,于是后来写了一个简单的程序在两个node上list那个目录的文件,发现返回的顺序果然不同,这个具体的原因为File.listFiles最后会调用到readdir函数,而这个函数返回的文件列表是按inode number排序的,因此在每台linux机器上确实有可能不同,当一个目录下有两个jar中有相同名字但不同内容的class时(话说这也是java应用中比较麻烦的问题,这个专门写篇blog来说下,java 8的模块化真的非常重要),就悲催了,一个保护做法是在实现classloader时,最好是先对listFiles排下序,避免集群中node出现表现不一致的问题。

Case: 某应用GC频繁但不一直不抛OOM的问题 解决过程: 这个应用是期待在内存不够的情况下快速抛出OOM的,但在执行中却发现执行了4个多小时的Full GC,就是没有抛OOM,导致应用出现了问题,从GC代码来看,目前要比较快的触发OOM,只能是调整GCTimeLimit和GCHeapFreeLimit,不过都不好调,后来暂时是靠调整了GCHeapFreeLimit来帮助快速抛出OOM。

Case: 某应用堆外内存泄露的bug 具体解决过程可参考这篇blog:http://blog.bluedavy.com/?p=205这篇blog。 总结: 堆外内存泄露基本就是靠google perf-tools来查找了,目前来看堆外内存泄露基本都是使用Deflater,却没有显式调用end造成的。

Case: 某应用压测时压力上不去的问题 解决过程: 1、查看压测的目标服务器的cpu、网卡流量,发现都没到瓶颈; 2、继续查看压测的目标Server的线程状况,发现Server的线程数也没到瓶颈,有个诡异的现象是随着客户端施加的压力上升,服务器端线程数也没上升,而且网卡流量也没上升; 3、于是猜测是客户端本身的问题,客户端的CPU、网卡流量也没到瓶颈,但发现客户端的线程数随着施加的请求数增加,也没上升,因此猜想是客户端这个地方的线程数已经达到了最大值; 4、由于代码是第三方的,于是用btrace写了个脚本,跟踪查找了该线程池的最大线程数,发现果然是这个最大线程数比较小,因此做了相应的修改,放大最大线程数,继续压测就OK了。 ps: 这个Case还出现了一个状况,就是客户端的网卡流量随着施加的压力上升,却没发生变化,因此猜测是网络上有流量限制… 总结: 从查这次的问题来看,在压测时压力上不去时,需要查看从请求发起到响应返回的整个过程,资源的消耗状况,是否某个地方资源消耗到极限了,如果硬件资源未到极限,通常会是线程blocked或线程被消耗完了。

Case: 某应用GC频繁的问题 这个专门写了一篇blog来描述,具体请见:http://blog.bluedavy.com/?p=290

Case: 某应用一直没响应。 解决过程: 1、jstack多次后分析,很多的线程都在Blocked状态,等待一把锁,而持有这把锁的线程一直停在了Log4j的ConsoleLog的write上; 2、于是问题就集中到为什么ConsoleLog write的时候竟然会停住,最早猜测的原因是console是不是也输出到了一个文件,而这个文件出问题了,但经过检查发现一切正常; 3、通过ps auxf查看脚本看看console有没有输出到文件,发现这个脚本嵌套了好几层,但都输出到了外部的文件,而外部这个文件是没什么问题的; 4、仔细查看发现这个Java进程是在代码里通过调用脚本启动的,猜想是不是没有正确处理标准输出流和错误流造成的,于是翻看代码,发现代码只处理了输出流,但没处理错误流,并且在处理输出流时只处理了10000行,超过10000的时候就没再去读了,对于这种未将console重定向到外部文件的场景,linux会将其写到一个缓冲区,缓冲区写满后就会导致应用程序阻塞住,这个在Java的API上也有说明: The parent process uses these streams(Process.getOutputStream(), Process.getInputStream(), Process.getErrorStream()) to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock. 5、到这步后就可以确认问题的原因了。 解决方法: 1、把输出到Console的Logger方式去掉了,这样自然就没问题了; 2、程序上修改,不是读到1w行就不读了,而是一直读到没有输出流和错误流了才结束,通常来说比较正确的做法请见这篇贴:http://goo.gl/sllmv 总结: 在Java中启动外部程序时一定要注意正确处理输出流和错误流。

一个GC频繁的Case HBase随机写以及随机读性能测试

11 Comments (+add yours?)

  1. nova Aug 29, 2011 @ 13:20:16 你好,看了你的blog,觉得你在jvm方面研究很深。

想问你一个jvm方面的问题,我目前在用一个hudson的服务器,跑在tomcat上,不知道为什么,总是jvm崩溃,然后生成一个 pidXXXX.log的文件,里面的内容不知所云。

请问如何分析这种jvm crash产生的 pidXXX.log文件啊?

  1. bluedavy Aug 29, 2011 @ 16:32:39 只能看看这个日志里的内容是什么了,然后相应的解决。
  2. blueswind Sep 16, 2011 @ 11:36:40 毕玄大牛好! 请教一下,在一个应用中,我发现会偶尔出现Full GC,但是这个应用是不允许Full GC出现的,观察gc.log感觉很奇怪: 553887.031: [GC 553887.031: [ParNew: 6448271K->69532K(7188480K), 0.0676760 secs] 10144883K->3766882K(12513280K), 0.0679280 secs] [Times: user=0.46 sys=0.01, real=0.07 secs] 553891.774: [GC 553891.774: [ParNew: 6459292K->75749K(7188480K), 0.1285440 secs] 10156642K->3774046K(12513280K), 0.1288140 secs] [Times: user=0.92 sys=0.00, real=0.13 secs] 553896.757: [GC 553896.757: [ParNew (promotion failed): 6465509K->6485588K(7188480K), 2.1091440 secs]553898.866: [CMS: 3699438K->1483627K(5324800K), 17.0428180 secs] 10163806K->1483627K(12513280K), [CMS Perm : 95598K->52896K(131072K)], 19.1523340 secs] [Times: user=19.27 sys=0.02, real=19.16 secs] 553943.432: [GC 553943.432: [ParNew: 6389760K->113975K(7188480K), 0.0698770 secs] 7873387K->1597603K(12513280K), 0.0700800 secs] [Times: user=0.47 sys=0.00, real=0.07 secs] 553948.178: [GC 553948.178: [ParNew: 6503735K->90637K(7188480K), 0.0667370 secs] 7987363K->1574265K(12513280K), 0.0669710 secs] [Times: user=0.42 sys=0.01, real=0.07 secs]

导致promotion failed的那个时刻似乎并没有进行MinorGC,而OldGen又没有到达我设置的80%,我的jvm参数如下: -Xmx13000m -Xms13000m -Xmn7800m -Xss256k -XX:PermSize=64m -XX:MaxPermSize=128m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=100 -XX:MaxTenuringThreshold=15 -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=30000 -XX:CMSScheduleRemarkEdenPenetration=1 -server -XX:+PrintGCDetails -Xloggc:./log/gc.log

由于这个Full GC出现的很少,所以很难重现,如果遇到这种问题,应该如何追查呢?请教毕玄大牛哈~~

  1. bluedavy Sep 16, 2011 @ 17:33:47 @blueswind promotion failed就是minor gc进行的时候出现的,so… 至于这个时候为什么产生了full gc,原因就是因为promotion failed了… promotion failed的原因主要是旧生代装不下要晋升到old中的对象,建议调整下参数…把新生代放小点吧,或者把cms的触发比率降低点吧。
  2. blueswind Sep 29, 2011 @ 19:06:16 毕玄你好~ 上次的那个promotion failed的问题仔细分析了一下,怀疑可能是由于CMSGC时的内存碎片导致的,以下是我的分析和解决过程,请拍砖! 发生promotion failed时,OldGen还有1.6G的空间,同时使用gcLogViewer查看平均新生代晋升大小(AVG PTOS),只有几十K。所以怀疑是由于OldGen在距离上次CMSGC后,经过一段时间又产生了大量内存碎片,当某个时间点在OldGen中的连续空间没有一块足够58K的话,就会导致的promotion failed。 这种情况下的解决方法难道只有调小CMS触发比率或缩小新生代吗?因为这样的话必然会导致系统吞吐量的下降,难道没有很好的解决内存碎片的方法吗?
  3. Edward Lee Oct 27, 2011 @ 10:00:01 毕玄,首先非常感谢你与我们分享工作中的经验,真得非常感谢!

对于你的 Case 1 情况,你在文中说道“一个保护做法是在实现classloader时,最好是先对listFiles排下序”。我感觉用处不大。

比如,App 中至少有 3 个 jar 包出现 3 个同样的 class 类型,而正确的那个类处于中间位置(不管你升序,还是降序),还是一样会抛出NoClassDefFoundError异常。

我觉得合理的做法:在自定义classloader装载类时,若发生异常,则继续尝试加载下一个符合的class类型,直到装载成功为止。

不知你是如何看待这个问题的?

  1. bluedavy Oct 27, 2011 @ 10:56:34 @Edward Lee 排序的意思是为了避免集群中各node出现不一致的表现,至少保持一致,冲突的问题这个最好不要智能解决,因为很难说应用到底想加载哪个…
  2. Edward Lee Oct 27, 2011 @ 15:17:06 @bluedavy “冲突的问题这个最好不要智能解决,因为很难说应用到底想加载哪个…” 呵呵,对,多谢提醒啊!
  3. 震子 Dec 13, 2011 @ 16:18:02 对于permGen的问题造成的的full gc貌似gc日志里面可以指示出来的,从《java_performance》这本书里面看到 在304页 Concurrent Permanent Generation Garbage Collection Full garbage collections may also occur as a result of a full permanent generation space. Monitoring the garbage collection data looking for full garbage collections and then observing the occupancy of the permanent generation space identifies whether they occur as a result of permanent generation space filling up. Here is an example full garbage collection initiated by permanent generation space filling up: 2010-12-16T17:14:32.533-0600: [Full GC [CMS: 95401K->287072K(1048576K), 0.5317934 secs] 482111K->287072K(5190464K), [CMS Perm : 65534K->58281K(65536K)], 0.5319635
  4. 震子 Dec 13, 2011 @ 16:23:48 Case: 某应用压测时压力上不去的问题 对于这个case,我也分享一下,呵呵,如果用的loadrunner的话,一定要注意loadrunner的内存使用,如果内存使用一直涨,那么肯定loadrunner的压力上不去,典型的是:我这边在使用loadrunner的Parameter List的时候就出现了这种情况,loadrunner使用的内存一直在涨。
  5. bluedavy Dec 16, 2011 @ 20:43:52 @震子 还是jstat比较容易判断。

    Leave a Reply

Cancel

Name (required)

Mail (required)

Website

CAPTCHA Image

Refresh Image

CAPTCHA Code /*


July 2013 M T W T F S S « Mar 1234567 891011121314 15161718192021 22232425262728 293031

Categories

Tags

btrace c1 c2 Deflater facebook gc gc tuning Grizzly HBase hotspot Inflater interpreter javac java code generation JavaOne javaone general technical session java代码执行 Java 并发 jit jvm memory management Native Memory Leak NoSQL oom oracle keynote pessimism policy references RPC serial gc SOA sun jdk sun jdk oom Web容量规划的艺术 yuanzhuo 书:分布式Java应用 书评 互联网技术 交流 内存管理 分布式Java应用 圆桌交流 容量规划 悲观策略 服务框架 硅谷公司

订阅

推荐书籍

My Book

© BlueDavy之技术blog 2013

Icons & Wordpress Theme by N.Design

JVM调优总结(二)

Posted on

JVM调优总结(二)-一些概念 - 高级语言虚拟机

您还未登录 ! 登录 注册

ITeye3.0

群组首页编程语言高级语言虚拟机知识库JVM调优JVM调优总结(二)-一些概念 原创作者: 和你在一起 阅读:4764次 评论:6条 更新时间:2011-05-26

Java对象的大小

基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的Java对象,其大小就值得商榷。

在Java中,**一个空Object对象的大小是8byte**,这个大小只是保存堆中一个没有任何属性的对象的大小。看下面语句:

Object ob = new Object();

这样在程序中完成了一个Java对象的生命,但是它所占的空间为:**4byte+8byte**。4byte是上面部分所说的Java栈中保存引用的所需要的空间。而那8byte则是Java堆中对象的信息。因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。

有了Object对象的大小,我们就可以计算其他对象的大小了。 Class NewObject {

int count;

boolean flag;

Object ob;

}

其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,因此大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。

这里需要注意一下**基本类型的包装类型的大小**。因为这种包装类型已经成为对象了,因此需要把他们作为对象来看待。包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,因为Java对象大小是8的整数倍,因此**一个基本类型包装类的大小至少是16byte**。这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。在JDK5.0以后,因为加入了自动类型装换,因此,Java虚拟机会在存储方面进行相应的优化。

引用类型

对象引用类型分为**强引用、软引用、弱引用和虚引用**。

强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收

软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。

JVM调优总结(一)-- 一些概念 | JVM调优总结(三)-基本垃圾回收算法

评论 共 6 条 请登录后发表评论

6 楼 xianneng.lin 2012-11-21 15:47

虚引用呢?

5 楼 xiaodatao 2012-01-19 16:43

基本数据的类型的大小是固定的。 Object ob = new Object(); 这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是Java栈中保存实例引用。

4 楼 shaomeng95 2011-07-08 13:13

官方文档上说:This data type represents one bit of information, but its "size" isn't something that's precisely defined.

3 楼 shaomeng95 2011-07-08 13:10

而且12byte没有包含

kthh0226 写道 这个有问题吧,java没有说明boolean的大小,不一定是1byte的 boolean的大小是依赖VM的

2 楼 kthh0226 2011-06-17 17:30

这个有问题吧,java没有说明boolean的大小,不一定是1byte的

1 楼 smalltalker 2011-05-30 20:18

发表评论

您还没有登录,请您登录后再发表评论

New-page

文章信息

知识库: 高级语言虚拟机

相关讨论

© 2003-2012 ITeye.com. [ 京ICP证110151号 京公网安备110105010620 ] 百联优力(北京)投资有限公司 版权所有