JVM中可分为方法区(非堆)、堆、桟、本地桟、程序计数器。今天主要说堆,我们new的对象会存在该区域内,其中可分为Young Generation(新生代)、Old Generation(老年代)和Permanent Generation(永久代),它被多个线程所共享同时也是GC主要负责垃圾回收的主要区域之一。
垃圾回收机制 刚被实例化的对象会存在于Eden Space中,当Eden Space空间满了以后,GC会进行垃圾回收,将不需要的对象回收掉,幸存下来的对象会被分到 Surivor Ratio区域中,我们注意到 Survivor Ratio 区域被分为两个空间,那么它们有什么意义呢?
其实分为两个空间的主要目的是避免在垃圾回收以后,产生大量的碎片。空间碎片对Java的性能影响是巨大的,所以要极力避免这种情况。当Eden Space 第一次满了以后,经历过GC回收后,幸存的对象会被放在S0区域内,当Eden Space再次满GC对其进行再次回收以后,幸存的对象会和S0内的对象进行合并,然后复制到S1区域内,当第三次回收后和S1合并复制到S0…如此反复16次以后。最终幸存的对象会被送到Old Generation。
这复制的办法其实只是GC回收算法中的一种。
GC 回收策略
标记-清理 (Mark-Sweep)
标记-删除 (Mark-Compact)
复制 (Copying)
jvm参数列表
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-Xmx3550m:最大堆内存为3550M。
-Xms3550m:初始堆内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 +持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000左右。
-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=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象 再年轻代的存活时间,增加在年轻代即被回收的概论。
收集器设置 常用的GC垃圾收集器有下面三种
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
JVM调优实战 JDK中自带了两个监控内存堆的工具,分别是jconsole
和jvisualvm
,他们位于jdk的bin目录下。
建议玩前几个例子,后面的例子很不友好,会让电脑卡死。悲催的我电脑强行重启了好几次。。。
1.内存溢出
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
内存堆溢出,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.edu;import java.util.ArrayList;public class TestMemory { static class OOMObject { public byte [] placeholder = new byte [64 * 1024 *40 ]; } public static void fillHeap (int num) throws Exception { ArrayList<OOMObject> list = new ArrayList <OOMObject>(); for (int i = 0 ; i < num; i++) { Thread.sleep(50 ); list.add(new OOMObject ()); } System.gc(); } public static void main (String[] args) throws Exception { Thread.sleep(10000 ); fillHeap(100 ); Thread.sleep(20000000 ); } }
需要为虚拟机设置参数-Xms100m -Xmx100m -XX:+UseSerialGC
在IDEA设置找到run-->
Edit configurations…
运行使用jconsole查看新生代堆内存状况。
2.检测死锁 当线程出现死锁时,进程永远不能完成,并且阻碍使用系统资源,阻止了其他作业开始执行,导致系统的资源利用率急剧下载,造成很严重的后果。下面例子中会产生死锁线程,我们需要使用jconsole来找到对应死锁的进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.edu;public class TestDeadThread implements Runnable { int a, b; public TestDeadThread (int a, int b) { this .a = a; this .b = b; } @Override public void run () { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } } public static void main (String[] args) { for (int i = 0 ; i < 100 ; i++) { new Thread (new TestDeadThread (1 , 2 )).start(); new Thread (new TestDeadThread (2 , 1 )).start(); } } }
运行代码后,打开jconsole,在线程
一栏中可以找到检测死锁
按钮,通过它就可以找到对应死锁的进程
3.检测死循环、阻塞 某些线程进入死循环,还有一些线程会阻塞在某个位置,但是它们其实并不是死锁,那么我们要如何排查?我们可以通过jvisualvm工具查看线程运行图来进行排查,下面是示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.edu;import java.io.BufferedReader;import java.io.InputStreamReader;public class TestThread { public static void createBusyThread () { Thread thread = new Thread (new Runnable () { @Override public void run () { System.out.println("createBusyThread" ); while (true ) ; } }, "testBusyThread" ); thread.start(); } public static void createLockThread (final Object lock) { Thread thread = new Thread (new Runnable () { @Override public void run () { System.out.println("createLockThread" ); synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "testLockThread" ); thread.start(); } public static void main (String[] args) throws Exception { BufferedReader br = new BufferedReader (new InputStreamReader (System.in)); br.readLine(); createBusyThread(); br.readLine(); Object object = new Object (); createLockThread(object); } }
main线程追踪到需要键盘录入方便我们检测
testBusyThread线程将在while(true)中一直运行,直到线程切换,很耗性能
testLockThread线程由于调用了watit(),它会一直处于阻塞状态,等待notify()被唤醒
我们运行程序打开jvisualvm工具 在终端输入后查看testBusyThread状态,之后在终端再次输入查看testLockThread状态, 我们看到testBusyThread线程运行
一栏一直是100%,而testLockThread线程的等待
一栏一直是100%
4.直接内存溢出 本例自很危险,会造成机器假死,慎跑…
VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.outofmemory;import java.lang.reflect.Field;import sun.misc.Unsafe;public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024 ; public static void main (String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0 ]; unsafeField.setAccessible(true ); Unsafe unsafe = (Unsafe) unsafeField.get(null ); while (true ) { unsafe.allocateMemory(_1MB); } } }
5.堆内存溢出 -XX:+HeapDumpOnOutOfMemoryError
可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后使用MemoryAnalyzer
工具进行分析,关于MemoryAnalyzer可以百度其使用方法。
VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.outofmemory;import java.util.ArrayList;import java.util.List;public class HeapOOM { static class OOMObject { } public static void main (String[] args) { List<OOMObject> list = new ArrayList <OOMObject>(); while (true ) { list.add(new OOMObject ()); } } }
6.桟内存溢出 本例用于体验OutOfMemoryError异常,容易让电脑假死,参数可以设置大一些
VM Args:-Xss2M
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.outofmemory;public class JavaVMStackOOM { private void dontStop () { while (true ) { } } public void stackLeakByThread () { while (true ) { Thread thread = new Thread (new Runnable () { @Override public void run () { dontStop(); } }); thread.start(); } } public static void main (String[] args) throws Throwable { JavaVMStackOOM oom = new JavaVMStackOOM (); oom.stackLeakByThread(); } }
7.桟内存溢出 本例用于体验StackOverFlow异常,容易让电脑假死
VM Args:-Xss128k
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.outofmemory;public class JavaVMStackSOF { private int stackLength = 1 ; public void stackLeak () { stackLength++; stackLeak(); } public static void main (String[] args) throws Throwable { JavaVMStackSOF oom = new JavaVMStackSOF (); try { oom.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + oom.stackLength); throw e; } } }