JVM故障诊断与性能优化--读书笔记(一)

Java对象之家--Java堆

java堆一般被分为新生代和老年代。其中新生代存放新生对象,老年代存放老年对象(年龄判断见深入理解jvm二),新生代有可能分为eden区,s0区,s1区,s0和s1区也被称为from区和to区,他们是两块大小相等、可以互换角色的内存空间(实际上是为了进行垃圾回收,即复制算法)。


下面通过示例展示java堆、方法区、java栈之间的关系:

public class SimpleHeap {
	private int id;

	public SimpleHeap(int id) {
		super();
		this.id = id;
	}
	
	public void show(){
		System.out.println("My ID is "+id);
	}
	public static void main(String[] args) {
		SimpleHeap s1 = new SimpleHeap(1);
		SimpleHeap s2 = new SimpleHeap(2);
		s1.show();
		s2.show();
	}
}

上述示例中,main函数创建的两个simpleHeap示例放在java堆中,描述simpleHeap类的信息存放在方法区,main函数中s1、s2局部变量存放在java栈中,并指向堆中的两个实例。

对象工位--出入java栈

Java虚拟机提供xss来指定线程的最大栈空间,也就决定了调用的最大深度。

实验一:探究栈空间的大小对函数调用深度的影响

public class TestStackDeep1 {
	private static int count = 0;
	private static void recursion(){
		count++;
		recursion();
	}
	public static void main(String[] args) {
		try{
			recursion();
		}catch(Throwable e){
			System.out.println("deep of calling = "+count);
			e.printStackTrace();
		}
	}
}

1. jvm参数为

-Xss128K

   输出结果为:

deep of calling = 1083
java.lang.StackOverflowError
	at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:6)
	at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)
	at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)

2.设置jvm参数为

-Xss256K

  输出结果为:

deep of calling = 2726
java.lang.StackOverflowError
	at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:6)
	at chapter2_4.TestStackDeep1.recursion(TestStackDeep1.java:7)

实验结果表明,java栈的空间越大,函数调用深度越大。

局部变量表

局部变量表用于保存函数的参数以及局部变量。局部变量表中的变量只在当前函数调用中有效,当前函数调用结束后,随着函数栈帧销毁。而函数的参数和局部变量的增多又会使局部变量表膨胀,占用的栈空间变大,从而减少可调用次数。

实验二:局部变量表的膨胀对函数调用深度的影响

public class TestStackDeep2 {
	private static int count =0 ;
	public static void recursion(long a,long b,long c){
		long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
		count++;
		recursion(a, b, c);
	}
	public static void recursion(){
		count++;
		recursion();
	}
	public static void main(String[] args) {
		try{
			recursion(0, 0, 0);
			//recursion();
		}catch(Throwable e){
			System.out.println("deep of calling = "+count);
			e.printStackTrace();
		}
	}
}

设置jvm参数为

-Xss128K
当选用有参函数时,输出结果为:
deep of calling = 304
java.lang.StackOverflowError
	at chapter2_4.TestStackDeep2.recursion(TestStackDeep2.java:6)
	at chapter2_4.TestStackDeep2.recursion(TestStackDeep2.java:8)

用jclasslib可以看出,第一个recursion函数的最大局部变量表大小为:


它的局部变量表为


第二个recursion函数的最大局部变量表大小为:


它的局部变量表为


当选用无参函数时结果与实验一同参数结果一致,所以可以看到,但局部变量和函数参数增多时,局部变量表膨胀导致函数调用的最大深度下降。

实验三:探究局部变量表中的槽位是否可重用

public class Localvar {

	public void localvar1() {
		int a = 0;
		System.out.println(a);
		int b = 0;
	}

	public void localvar2() {
		{
			int a = 0;
			System.out.println(a);
		}
		int b=0;
	}
}

查看localvavr1函数的局部变量表

查看函数localvar2的局部变量表


可以看到如果一个局部变量过了其作用域,那么在其作用域之后声明的局部变量就有可能过期局部变量的槽位,从而节省资源。

实验四:局部变量对垃圾回收的影响

public class LocalvarGC {
	public void localcarGC1(){//申请空间后立即回收
		byte[] a = new byte[6*1024*1024];
		System.gc();
	}
	public void localcarGC2(){//先置为null,使其失去强引用再回收
		byte[] a = new byte[6*1024*1024];
		a=null;
		System.gc();
	}
	public void localcarGC3(){//先使局部变量失效再回收
		{
			byte[] a = new byte[6*1024*1024];
		}
		System.gc();
	}
	public void localcarGC4(){//使变量c复用变量a的字,对数组回收
		{
			byte[] a = new byte[6*1024*1024];
		}
		int c=10;
		System.gc();
	}
	public void localcarGC5(){//调用函数1,再函数返回后在进行回收
		localcarGC1();
		System.gc();
	}
	public static void main(String[] args) {
		LocalvarGC gc = new LocalvarGC();
		gc.localcarGC1();
		gc.localcarGC2();
		gc.localcarGC3();
		gc.localcarGC4();
		gc.localcarGC5();
	}
}

设置jvm参数:

-XX:+PrintGC

输出结果为:

[GC 7475K->6744K(124416K), 0.0046744 secs]
[Full GC 6744K->6606K(124416K), 0.0092658 secs]//无回收
[GC 14081K->6638K(124416K), 0.0003266 secs]
[Full GC 6638K->461K(124416K), 0.0067632 secs]//有回收
[GC 7271K->6605K(124416K), 0.0011679 secs]
[Full GC 6605K->6605K(124416K), 0.0030256 secs]//无回收
[GC 13415K->6605K(124416K), 0.0003572 secs]
[Full GC 6605K->461K(124416K), 0.0060987 secs]//有回收
[GC 7271K->6605K(124416K), 0.0010584 secs]
[Full GC 6605K->6605K(124416K), 0.0031301 secs]//无回收
[GC 6605K->6605K(124416K), 0.0002726 secs]
[Full GC 6605K->461K(124416K), 0.0065306 secs]//有回收

可以看到,但局部变量的作用域失效、失去强引用、所在栈帧销毁时,才执行垃圾回收。

jvm优化--栈上分配

对于那些线程私有的对象,可以将他们打散分配在栈上,而不是分配在堆上,分配在栈上的好处是函数调用后自行销毁,不许进行垃圾回收,从而提高系统性能。栈上分配的基础是进行逃逸分析,判断对象作用域是否有可能逃逸出函数体

实验五:对非逃逸对象的栈上分配

public class OnStackTest {
	public static class User{
		public int id;
		public String name;
	}
	//private static User u;//逃逸对象
	public static void alloc(){
		User u = new User();//非逃逸对象,该对象没有被alloc函数返回,未发生逃逸
		u.id = 5;
		u.name = "geym";
	}
	public static void main(String[] args) {
		long b = System.currentTimeMillis();
		for (int i = 0; i < 100000000; i++) {
			alloc();
		}
		long e = System.currentTimeMillis();
		System.out.println(e-b);
	}
}

设置jvm参数(在server模式下启用逃逸分析并打印gc日志,同时开启标量替换):

-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations

程序输出结果

7

可以看到,程序没有进行任何形式的gc回收就执行完成,说明user对象的分配过程被优化。

类的户口注册--方法区

大量类信息存放在方法区时会产生溢出

实验六 方法区的溢出观察

需要的jar包:


import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class PermTest {
	static class OOMObject {
	}

	public static void main(String[] args) {
		while (true) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				@Override
				public Object intercept(Object obj, Method method,
						Object[] args, MethodProxy proxy) throws Throwable {
					// TODO Auto-generated method stub
					return proxy.invokeSuper(obj, args);
				}
			});
			enhancer.create();
		}
	}
}

输出结果:

[GC [PSYoungGen: 12016K->1247K(38400K)] 12016K->1255K(124416K), 0.0020854 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 1247K->0K(38400K)] [ParOldGen: 8K->1147K(64000K)] 1255K->1147K(102400K) [PSPermGen: 4095K->4094K(4096K)], 0.0148559 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(38400K)] 1147K->1147K(102400K), 0.0003406 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1147K->1147K(124928K)] 1147K->1147K(163328K) [PSPermGen: 4095K->4095K(4096K)], 0.0050305 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(38400K)] 1147K->1147K(163328K), 0.0002956 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1147K->968K(196096K)] 1147K->968K(234496K) [PSPermGen: 4095K->4095K(4096K)], 0.0115525 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(39424K)] 968K->968K(235520K), 0.0003338 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(39424K)] [ParOldGen: 968K->968K(309760K)] 968K->968K(349184K) [PSPermGen: 4095K->4090K(4096K)], 0.0116310 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
Exception in thread "main" [GC [PSYoungGen: 1392K->128K(35328K)] 2361K->1096K(345088K), 0.0005686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 128K->0K(35328K)] [ParOldGen: 968K->789K(429568K)] 1096K->789K(464896K) [PSPermGen: 4094K->4094K(4096K)], 0.0122008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(36864K)] 789K->789K(466432K), 0.0003058 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(36864K)] [ParOldGen: 789K->789K(595456K)] 789K->789K(632320K) [PSPermGen: 4094K->4094K(4096K)], 0.0058110 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 1090K->96K(38400K)] 1880K->885K(633856K), 0.0004882 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 96K->0K(38400K)] [ParOldGen: 789K->798K(766464K)] 885K->798K(804864K) [PSPermGen: 4095K->4095K(4096K)], 0.0132138 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 737K->32K(39424K)] 1536K->830K(805888K), 0.0005683 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 32K->0K(39424K)] [ParOldGen: 798K->798K(986624K)] 830K->798K(1026048K) [PSPermGen: 4095K->4095K(4096K)], 0.0077948 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
[GC [PSYoungGen: 0K->0K(39424K)] 798K->798K(1026048K), 0.0002922 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 0K->0K(39424K)] [ParOldGen: 798K->798K(1223168K)] 798K->798K(1262592K) [PSPermGen: 4095K->4095K(4096K)], 0.0059662 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 39424K, used 768K [0x00000000d5d00000, 0x00000000d8580000, 0x0000000100000000)
  eden space 38400K, 2% used [0x00000000d5d00000,0x00000000d5dc0030,0x00000000d8280000)
  from space 1024K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8380000)
  to   space 1024K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8580000)
 ParOldGen       total 1223168K, used 798K [0x0000000081800000, 0x00000000cc280000, 0x00000000d5d00000)
  object space 1223168K, 0% used [0x0000000081800000,0x00000000818c7a80,0x00000000cc280000)
 PSPermGen       total 4096K, used 4095K [0x0000000081400000, 0x0000000081800000, 0x0000000081800000)
  object space 4096K, 99% used [0x0000000081400000,0x00000000817ffff0,0x0000000081800000)

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

可以看到当大量的动态类生成后,方法区产生了溢出。要注意类和对象实例的区别,类主要是指类文件编译后的代码等数据,而实例是指从类文件中创建的对象。

猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/80624259