这两天一直在跟进一个内存的问题,由于之前对JVM的内存理解的过于粗浅,很多概念都不甚清楚,经常看到YC和OC,根本不知道是什么意思;
借这个机会刚好补了一下知识,希望对大家有用
先看一下JVM运行后有哪些和内存相关的东西:
java堆:当Java程序创建一个类的实例或者数组时,都在堆中为新的对象分配内存。虚拟机中只有一个堆,所有的线程都共享他;可以理解为:新建的对象都是在这个区域申请内存的
方法区:主要存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息;就是后面我们会介绍到的永久代,在应用启动完后大小就基本固定了,后续基本不会增长;因为对象的内存申请都是在java堆里面
java栈区:就是具体的方法调用,平时我们看到的threaddump就是在这里;包含java的栈和本地方法栈
JavaStack(java的栈):虚拟机只会直接对Javastack执行两种操作:以帧为单位的压栈或出栈 Nativemethodstack(本地方法栈):保存native方法进入区域的地址
由于大部分内存都是集中在java堆中,主要介绍java堆的相关配置和gc的过程;初次接触,理解的可能还有点肤浅,大家轻点喷
主要参考链接:
(1) java对象申请的内存并不会在程序内部主动释放,当然你也可以主动调用system.gc(),但是程序基本都没这么做过
java的内存管理都会交给jdk去处理,jdk有一套自己的内存管理和回收机制;当jvm内存占用达到一定的值,就会触发内存回收;也就是我们常看到的YC和OC
(2) 那么什么是YC(Minor GC)、什么是OC(Full GC)呢? YC是young collection的缩写,也就是新生代回收,OC是old collection的回收,也就是老年代回收
注:jdk叫Minor GC,jrokit叫YC触发YC是非常正常的事情,因为随着new对象的不断增加,内存也会不断上升,当达到新生代内存的最大值时,就会触发YC操作释放新生代的内存,但前提是YC的时间控制在ms范围内; 而OC是对整个堆进行垃圾回收,回收的时间长,所以我们需要尽量减少OC的发生频率
(3) YC、OC的相关参数是那些呢?
先了解几个参数的含义:
-Xms和-Xmx是不是很熟悉?对的,这就是配置在我们env文件里最常用的两个参数,一般配置成一样,原因是将heap初始可用内存尽量调大,避免内存不够用发生gc后再去调整heap的大小
-Xmn也是配置在env里的常用参数,就是前面提到的新生代内存的大小;为什么对于新生代和老年代的配置只需要明确指出这一项呢,应为其他参数都是有默认比例的,也就是Ratio和SurvivorRatio
为什么我们看到的instance的内存占用最大的时候会超过-Xmx的值呢,那是因为还有永久代的内存:XX:PermSize 和 -XX:MaxPermSize;永久代主要存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息
注:jrokit里是通过-Xns来设置新生代内存大小的(也就是nursery区)
jrokit是没有永久代的,并且jdk1.8版本也移除了永久代,其目的是Hotspot JVM和JRockit JVM相融合的设计思路。 转移位置: 将java类部分放到java heap里,将字符串常量和类中的静态变量放到内存里面。
-Xms 初始堆大小。如:-Xms256m -Xmx 最大堆大小。如:-Xmx512m -Xmn 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% -Xss JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。 -XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3;这也是新生代一般配置为1/4的原因 -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 -XX:PermSize 永久代(方法区)的初始大小 -XX:MaxPermSize 永久代(方法区)的最大值 -XX:+PrintGCDetails 打印 GC 信息 -XX:+HeapDumpOnOutOfMemoryError让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析
(4) YC、OC的过程又是怎样的?
下图描述了新生代和老年代的比例以及内存回收的过程;
绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快; 当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的); 此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0; 当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。 当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代。 从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活 着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方 式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下高效,如果在老年代采用停止复制,则挺悲剧的。
在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointer和TLAB(Thread- Local Allocation Buffers),这两种技术的做法分别是:由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对 象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;而对于TLAB技术是对于多线程而言的,将Eden区分为若干 段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内 存。
年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行Major GC,也叫 Full GC。
可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。 如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。
可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
(5) 几种GC机制:
在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew) 1)串行GC 在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求 不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定 2)并行回收GC 在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是 server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数 3)并行GC 与旧生代的并发GC配合使用 旧生代的GC: 旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行 GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS),具体算法细节还有待进一步深入研究
(6) jrokit查看内存的命令:
jstat命令可以参考这个链接:
jstat -options:查看jstat可用的参数
jstat -gc 517588 1000 5 :表示进程517588每1秒刷新一次,呈现5次的gc情况
如果想查看对象的内存分配情况,可以使用jrcmd命令:
jrcmd用法可以用jrcm help看一下哈,还有很多有用的参数(例如打threaddump也是通过jrcmd)
...../jrockit-jdk1.6.0_37-R28.2.5-4.1.0/bin/jrcmd 64943 print_object_summary |more64943:--------- Detailed Heap Statistics: ---------37.6% 115666k 987021 +115666k java/net/SocksSocketImpl12.5% 38555k 987017 +38555k java/net/SocketInputStream10.0% 30889k 3953877 +30889k java/lang/Object 7.5% 23134k 987051 +23134k java/net/Inet4Address 7.5% 23133k 987018 +23133k java/net/Socket 5.0% 15436k 987937 +15436k java/util/concurrent/atomic/AtomicInteger 5.0% 15426k 987296 +15426k java/io/FileDescriptor 4.8% 14836k 147270 +14836k [C 1.2% 3813k 162703 +3813k java/lang/String 0.7% 2272k 20718 +2272k [Ljava/lang/Object; 0.7% 2240k 20483 +2240k java/lang/Class 0.6% 1890k 25790 +1890k [Ljava/util/HashMap$Entry; 307931kB total ------------ End of Detailed Heap Statistics ---
(6) jdk查看内存的命令:
jmap的使用方法可以参考:
查看当前堆和永久代的使用情况:
....../jdk1.7.0_67/bin/jmap -heap 495149Attaching to process ID 495149, please wait...Debugger attached successfully.Server compiler detected.JVM version is 24.65-b04using parallel threads in the new generation.using thread-local object allocation.Concurrent Mark-Sweep GCHeap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 1073741824 (1024.0MB) NewSize = 1310720 (1.25MB) MaxNewSize = 134217728 (128.0MB) OldSize = 5439488 (5.1875MB) NewRatio = 2 SurvivorRatio = 8 PermSize = 21757952 (20.75MB) MaxPermSize = 268435456 (256.0MB) G1HeapRegionSize = 0 (0.0MB)Heap Usage:New Generation (Eden + 1 Survivor Space): capacity = 120848384 (115.25MB) used = 46900504 (44.727806091308594MB) free = 73947880 (70.5221939086914MB) 38.80937621805518% usedEden Space: capacity = 107479040 (102.5MB) used = 45424552 (43.320228576660156MB) free = 62054488 (59.179771423339844MB) 42.263637635766% usedFrom Space: capacity = 13369344 (12.75MB) used = 1475952 (1.4075775146484375MB) free = 11893392 (11.342422485351562MB) 11.039823644301471% usedTo Space: capacity = 13369344 (12.75MB) used = 0 (0.0MB) free = 13369344 (12.75MB) 0.0% usedconcurrent mark-sweep generation: capacity = 939524096 (896.0MB) used = 263382056 (251.18070220947266MB) free = 676142040 (644.8192977905273MB) 28.033560514450073% usedPerm Generation: capacity = 46530560 (44.375MB) used = 46330736 (44.18443298339844MB) free = 199824 (0.1905670166015625MB) 99.57055320202464% used
查看永久代里的类分布情况:
....../jdk1.7.0_67/bin/jmap -permstat 495149Attaching to process ID 495149, please wait...Debugger attached successfully.Server compiler detected.JVM version is 24.65-b04finding class loader instances ..done.computing per loader stat ..done.please wait.. computing liveness.............liveness analysis may be inaccurate ...class_loader classes bytes parent_loader alive? type 2656 15721528 null live 0x00000000c7886180 1 1888 0x00000000bbc42350 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000b805c970 995 7300152 0x00000000b805c9c0 live sun/misc/Launcher$AppClassLoader@0x00000000f02295600x00000000b8089080 1 3064 0x00000000b805c970 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c7b48b00 1 3064 0x00000000c66467f0 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c7bbde00 1 1888 0x00000000c66467f0 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000b8060c90 1 3064 0x00000000b805c970 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c74b6178 1 3064 0x00000000c66467f0 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c65d93f8 1 3032 null dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c7b08788 1 1888 0x00000000c66467f0 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c799cf20 1 3064 0x00000000c66467f0 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd80x00000000c7b0dae8 1 3032 0x00000000c66467f0 dead sun/reflect/DelegatingClassLoader@0x00000000f004fcd8
查看堆里的对象分布情况:
....../jdk1.7.0_67/bin/jmap -histo 495149|more num #instances #bytes class name---------------------------------------------- 1: 123387 15578696 [C 2: 24053 14138632 [B 3: 84482 12337176 4: 84482 10825712 5: 7539 8760408 6: 7539 5437192 7: 6354 5003168 8: 121157 2907768 java.lang.String 9: 2979 1698368 10: 19539 1563120 java.lang.reflect.Method 11: 36684 1467360 java.util.LinkedHashMap$Entry 12: 4797 1143976 [Ljava.util.HashMap$Entry; 13: 4611 1126240 [I 14: 34154 1092928 java.util.HashMap$Entry 15: 8145 986832 java.lang.Class 16: 12069 733352 [Ljava.lang.Object; 17: 10887 669408 [S 18: 12404 652928 [[I