jvm调优

JVM中可分为方法区(非堆)、堆、桟、本地桟、程序计数器。今天主要说堆,我们new的对象会存在该区域内,其中可分为Young Generation(新生代)、Old Generation(老年代)和Permanent Generation(永久代),它被多个线程所共享同时也是GC主要负责垃圾回收的主要区域之一。

image

垃圾回收机制

刚被实例化的对象会存在于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中自带了两个监控内存堆的工具,分别是jconsolejvisualvm,他们位于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…

image

运行使用jconsole查看新生代堆内存状况。

image

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,在线程一栏中可以找到检测死锁按钮,通过它就可以找到对应死锁的进程

image

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%

image

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;
}
}
}


jvm调优
http://example.com/2018/04/15/2018-04-15-jvm-jvm调优实战/
Author
Hoey
Posted on
April 15, 2018
Licensed under