1、JVM 内存分布总览
Java Heap 内存
它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
(1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
(2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
(3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
(4) 所有新创建的Object 都将会存储在新生代Yong Generation中。如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。新的Object总是创建在Eden Space。
方法区域(Method Area)
(1)在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。
(2)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息、即时编译器编译后的代码等数据。当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
运行时常量池(Runtime Constant Pool)
存放编译期生成的各种字面量和符号引用:类中的固定的常量信息、方法和Field的引用信息等,在类加载后进入方法区的运行时常量池存放。其空间从方法区域中分配。运行期间也可能放入新的常量,例如 String类的intern()方法。
虚拟机栈、本地方法栈、程序计数器
虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
2、JVM内存分配及GC机制(一般为堆:分代分配&回收)
年轻代:Eden(bump the pointer + TLAB)+ 停止-复制
内存分配:由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对象创建时,只需检查最后一个对象后面是否有足够的内存,从而大大加快内存分配速度;而对于TLAB技术是对于多线程而言的,将Eden区分为若干 段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内存。
内存回收:当回收时,将 Eden + Surivor0 中存活的对象一次性复制到 Survivor1,清理掉 Eden 和 Survivor0。当S1空间不够是,需要依赖老年代进行分配担保(promotion:即直接进入年老代)。
年老代:标记-整理以保证内存的连续
- 对象在年轻代存活了足够长的时间而没有被清理掉(-XX: MaxTenuringThreshold 次 Young GC后存活了下来),则会被复制到年老代。当年老代内存不足时执行Major GC,也叫 Full GC。
可使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。 - 如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用-XX: PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。
- 可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
Minor GC 与 Full GC
在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存)。
永久代
永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
- 类的所有实例都已经被回收
- 加载类的ClassLoader已经被回收
- 类对象的Class对象没有被引用(即无通过反射引用该类的地方)
永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。HotSpot提供-Xnoclassgc进行控制,使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以查看类加载和卸载信息。
有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize进行设置。
GC 机制组合
3、查看内存分析问题工具
jstat: JVM 统计信息监视工具(类装载、内存、GC、JIT编译等运行数据)
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
$ jstat -gc 12543 25 2
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5120.0 5120.0 4583.5 0.0 33280.0 3530.2 87552.0 88.0 16384.0 16034.9 2048.0 1923.7 2 0.012 0 0.000 0.012
5120.0 5120.0 4583.5 0.0 33280.0 3530.2 87552.0 88.0 16384.0 16034.9 2048.0 1923.7 2 0.012 0 0.000 0.012
$ jstat -gcutil 12543
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
89.52 0.00 10.61 0.10 97.87 93.93 2 0.012 0 0.000 0.012
cathardeMacBook-Pro:~ cathar$ java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
jmap(Memory Map)和jhat(Java Heap Analysis Tool)
jmap -permstat pid:打印进程的类加载器和类加载器加载的持久代对象信息,输出:类加载器名称、对象是否存活(不可靠)、对象地址、父类加载器、已加载的类大小等信息。
jmap -heap pid:查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况。
jmap -histo[:live] pid:查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象。
jmap -dump:format=b,file=dumpFileName pid:进程内存使用情况dump到文件中,再用jhat分析查看。dump出来的文件可以用MAT、VisualVM等工具查看,这里用jhat查看:
$ jhat -port 9998 /tmp/dump. //比较少用.
jstack主要用来查看Java进程内的线程堆栈信息
通常用于追踪多线程任务调度过程、对象lock(或者死锁)、并发同步阻塞、IO线程执行状态等;比如排查某个线程为何wait(假死,阻塞等)。
- Step1:找出进程内最耗费CPU的线程:使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid
- Step2:printf “%x\n” 21742 //得到21742的十六进制值为54ee
- Step3:使用jstack输出进程21711的堆栈信息找到线程情况:
$ jstack 21711 | grep 54ee
“PollIntervalRetrySchedulerThread” prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
可以看到CPU消耗在PollIntervalRetrySchedulerThread这个类的Object.wait(),定位代码去分析问题。
可视化故障处理工具:VisualVM(多合一故障处理工具,可安装Btrace插件:通过HotSwap动态加入调试代码)+ jConsole(JVM监视与管理控制台)
参考资料
不错的JVM介绍
多处理器环境下的JVM内存管理
深入理解java虚拟机章节
JVM参数调优
HotSpot的GC机制
jps、jstack、jmap、jhat、jstat、hprof使用详解
VisualVM:http://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/
jConsole:http://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html
Monitoring and Managing JavaSE 6 Applications: http://www.oracle.com/technetwork/articles/javase/monitoring-141801.html
BTrace:https://kenai.com/projects/btrace
http://learnworld.iteye.com/blog/1402763