Gc学习笔记:浅谈GC,简略分析CMS,Jvm堆内存结构,JVM性能调优等

标题测试工具

jvisual
jvisual 自从jdk8之后就被移除掉了,我们需要自己去下载
https://visualvm.github.io/
下载之后,GC图是不存在的,需要自己安装
Tools->Plugins->Available Plugins->Visual GC
勾选,然后Install即可

Jvm Heap 结构

这里写图片描述

上图中,内存一共分为两个大区,堆区域非堆区。

堆区,是随着我们的应用启动而启动的,在程序运行过程中,对象和数组就是存放在这个区的,注意,堆区是一块共享的区域,操作共享区域就会有锁和同步的概念。

跟堆息息相关的还有GC(垃圾回收机制),而堆(Heap)也是GC的主要工作场所

这里写图片描述
上面这张图显示了GC在堆中的工作过程。

Gc过程

一般刚刚new出来的对象,会存在Eden区,也就是新生代,经过垃圾回收之后,剩下的对象会被放置到存活区(Survivor),也就是上图的Form Space和To Space区。这两个区的功能是一样的,可以看做一个是主,一块是副本,也就是主从,但是关系并不是这样,他们是并行工作的。即交替工作的。
我们知道一个对象再经过多次的垃圾回收之后,还是存活,那么他就会被放置到老年区,也就是Old Generation中,在没有进入老年区之前,这个对象会一直在存活区中,但是我们知道,存活区中有些对象也会被回收,这样,久而久之。存活区就是如下的样子

也就是残缺不全,那么需要整理的过程,这就涉及到了垃圾的回收算法,其中有一种算法就是复制拷贝算法。也就是读取我们当前这个区中有效的对象,将按照顺序存放到另外一个区域。这时候也就需要另外一个备用区。这就为什么有了两个存活区的原因。但是GC两个存活区不一定会交替工作,在某些时候,只是单个工作,这个主要取决于GC算法
###GC策略与GC算法
请参考如下写的比较详细的文章,本文只是为了检验效果。
https://www.cnblogs.com/sunfie/p/5125283.html

Code

下面的代码是一个循环的往一个List列表中放入随机数的代码,可以看到这段代码会一直放变量,同事其产生的对象是没有办法被回收,通过下面的图,让我们了解GC机制

public static void main(String[] args) throws InterruptedException {
		var list = new ArrayList<String>();
		while (true) {
			list.add(Math.random() + "");
			System.out.println(list.size());
		}
	}
	

使用默认垃圾回收-XX:+UseG1GC

效果图

这里写图片描述

JVM参数

-XX:MaxTenuringThreshold=0
-XX:+PrintGC
-Xms500m
-Xmx500m

-XX:MaxTenuringThreshold=0

最大转为年老代代数,表示一个存在于EdenSpace的数据,在经过多少次的垃圾回收之后,会进入年老区,一般情况下,如果设置的越大,那么这个数据在Survivor区存活的时间也就越长。因为经过垃圾回收之后,存在于Eden区的数据只有两条必然的道路,就是存活区与老年区。
而决定他的去向是,我们怎么知道他是否适合进入老年区?
这里写图片描述

-XX:MaxTenuringThreshold=16
-XX:+PrintGC
-Xms500m
-Xmx500m
-XX:+UseParallelGC

这里写图片描述

-XX:MaxTenuringThreshold=16
-XX:+PrintGC
-XX:PermSize=32m
-XX:SurvivorRatio=1
-Xms500m
-Xmx500m
-Xmn200m
-XX:+UseParallelGC

上图中,我们把最大堆内存跟初始堆内存设置为500m,
并且设置xmn 即年轻代堆(YoungGenerator)内存为200m
并且Eden:Survivor=1:1
由SurvivorRatio,我们知道

Eden+Survivor*2=Young Generator
假设Eden为x ,Survivor为y
那么x:y=1
所以根据上面的公式
x+2y=200m
即3y=200m
可得x=y=66m
并且OldGeneration=StackSize-YoungGenerator
500-200=300m
如下图展示的结果
注意需要断点情况下看,随着程序的运行JVM会适当的调整各个区块的大小

这里写图片描述

打印GC

-Xloggc is deprecated. Will useinstead.
-XX:+PrintGC
-XX:+PrintGCDetails

-Xlog:gc:gc.log
-Xlog:gc
-Xlog:gc*

Java 死锁实现

public class Runner implements Runnable {

	int a, b;

	public Runner(int a, int b) {
		this.a = a;
		this.b = b;
	}

	public static void main(String[] args) {
		for (int i = 0; i < 4; i++) {
			var t1=new Thread(new Runner(10, 8));
			t1.setName("T"+i+"A");
			var t2=new Thread(new Runner(8, 10));
			t2.setName("T"+i+"B");
			t1.start();
			t2.start();
		}
	}

	@Override
	public void run() {
		// 获取锁
		String name = Thread.currentThread().getName();
		System.out.println(name+" Enter");
		synchronized (Integer.valueOf(a)) {
			System.out.println(name+" got lock a:"+a);
			synchronized (Integer.valueOf(b)) {
				System.out.println(name+" got lock b:"+b);
				System.out.println(a + "==>" + b);
			}
		}
		System.out.println(name+" Leave");
	}
}

我们的代码每次是开启两个线程,线程A,与线程B,并且按照批次进行划分
所以线程的编号是
T0A T0B
T1A T1B
T2A T2B
…以此类推,但是线程的执行顺序是随机的,但是有一点可以知道,A线程之间是不会相互死锁的,发生死锁都是在A线程拿了锁1 的时候,B线程拿了锁2,而此时A线程需要拿锁2,而B线程要拿锁1,这就死锁的

T0A Enter
T0A got lock a:10
T0A got lock b:8					//T0A拿了并且释放了
10==>8
T0A Leave
T0B Enter
T1B Enter
T3B Enter
T3A Enter
T3A got lock a:10				//A类线程拿了锁2(数字10)
T2A Enter
T1A Enter
T2B Enter
T0B got lock a:8					//B类线程拿了锁1(数字8)
//此时已经发生死锁,因为AB类线程互相等待对方的锁,

我们使用Jvisual进行查看,可以看到一直闪烁,说明已经发生了死锁。
在这里插入图片描述

并且可以看到这一块区域,有一个红色的提示,意思即发现死锁,注意,如果没有这么多的面板,请点击plugins进行安装。图中粉红色哪一块线程就是发生锁住的了。这就是发生在等待锁的时候。并不表示红色的是死锁,而是,等待锁。

在这里插入图片描述

在后面,我们可以线程跑到哪里
在这里插入图片描述
请看下图的分析,上一个步骤中,勾选了Threads inspect下面的线程,查看我们的线程现在运行到哪里了,在干嘛。
在这里插入图片描述

jstack命令

jstack 命令用于把线程的信息打印出来,用法如下:

jstack -l [pid]>stack.log
pid可以通过jps 进行查询

之所以使用>导向符,是为了将输出送入到文件,方便查看。这个文件中的信息是所有线程目前运行到的位置。
也就是我们上面分析那个死锁 的线程信息
在这里插入图片描述
效果如下图所示
在这里插入图片描述

jmap 命令

常用的命令是,可以使用这个命令导出堆内存。用于分析内存泄漏

常用命令
jmap -histo:[live] pid
live可以加可以不加,pid是线程id,使用jps命令查看就可以了

在这里插入图片描述

在jvisual 中同样有工具可以查看
使用Jvisual效果比之前的好看多了。并且是实时的,我们点击Heap Dump就可以将堆进行导出
在这里插入图片描述

细心的可以注意到Heap histogram右边有一个Per thread allocations,可以看到每一个不同的线程分配的堆内存大小
在这里插入图片描述

这个视图可以说是很方便了。提供了足够的信息给我们查看

使用一个比较明显的例子来查看分析

public class OutOfMemory {

	public static void main(String[] args) throws IOException {
		var list = new ArrayList<String>();
		for (int i = 0; i < 1000000; i++) {
			list.add(UUID.randomUUID().toString());
		}
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		String line = bufr.readLine();
		System.out.println(line);
	}
}

这个程序是 一个随机产生UUID,并且将这个UUID转成字符串放到List列表中,懂GC的同学应该知道这个是不会被回收的,因为存在着引用。我们把堆内存dump出来,按照前面的方法,很厉害吧,应用里面的String 类字符串实例占用空间居然达到28%,这个一定是内存泄漏啦。再看看实例数,不对,我们就循环了1000000次,怎么会多11935个,看下面的count,其实java运行的时候虚拟机自身也会产生一些字符串,这个很正常的嘛。问题来了,下面的字符串我怎么知道是在哪里的?
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/blueboz/article/details/82187048