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

4 篇文章 0 订阅
3 篇文章 0 订阅

标题测试工具

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运行的时候虚拟机自身也会产生一些字符串,这个很正常的嘛。问题来了,下面的字符串我怎么知道是在哪里的?
在这里插入图片描述

深究Jvm 之OQL语言

众所周知,Jvm 中的内存结构设计的非常严谨,堆区与非堆区两个大区。还记得上一步已经对Heap进行dump 了,也就是把内存中的堆区当出来。这么大的堆区,怎么知道哪里内存泄漏?哪里出了问题,以及我们想对堆区进行更加深入细致的查询处理与学习。光靠上一步的还是远远不够的,我们在想,如果我们能用类似于SQL脚本,把堆区中的对象按照一定的查询过滤映射条件进行查询出来,那该有多好啊,OQL应运而生。OQL 全称Objects Query Language,对象查询语言,每一个类全限定名为可以想象成为表名,即java.lang.String是一个表名。那么尝试写一个OQL.

基本语法

   select <JavaScript expression to select>
         [ from [instanceof] <class name> <identifier>
         [ where <JavaScript boolean expression to filter> ] ]

脚本示例记录

--字符串长度限制
select t.toString() from java.lang.String t where t.value.length==36
--正则匹配-开头匹配
select t.toString() from java.lang.String t where /^java/.test(t.toString())
--正则匹配-结尾匹配
select t.toString() from java.lang.String t where /java$/.test(t.toString())
--正则匹配所有的uuid
--/.../双斜杆里面的文字是正则表达式,.test()是目的匹配变量
select 
	t.toString() 
from 
	java.lang.String t 
where 
	/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/
	.test(t.toString())
	
--查询所有UserInfo类的实例对象的对象ID 
select objectid(t) from cn.boz.jvm.pojo.UserInfo t
--同时查询多个字段的方法
select {oid:objectid(t),co:classof(t)} from cn.boz.jvm.pojo.UserInfo t
--将堆中的cn.boz.jvm.pojo.UserInfo类所有实例都打印出来。并且按照一定格式进行打印
map(heap.objects("cn.boz.jvm.pojo.UserInfo"),"it.name.toString() +':'+ it.age+':'+objectid(it)")
--查询堆中所有类的个数
count(unique(map(heap.objects(),'classof(it)')))
--最小值
max(heap.objects("cn.boz.jvm.pojo.UserInfo"),"rhs.age>lhs.age")
--最大值
max(heap.objects("cn.boz.jvm.pojo.UserInfo"),"rhs.age<lhs.age")

部分难理解方法详细介绍

首先如下是例子使用到的JavaBean

public class UserInfo {

	private String userId;
	private String name;
	private String gender;
	private String addr;
	private int age;
	private double salary;
}

reachables方法

给定一个对象,查找到这个对象所能够到达的实例
一般情况是这个类的私有,保护,公开属性。

--查询给定对象所能够到达的
select reachables(p) from java.util.Properties p

--初始脚本
select 
    reachables(t)
from 
    cn.boz.jvm.pojo.UserInfo t
where 
    t.userId.toString().trim().equals("dca3ef0f-25ce-4a76-b693-6992be3b063d")    

在这里插入图片描述

对部分不需要的属性字段进行过滤,如下是不需要显示userId 字段引用

select 
    reachables(t,'cn.boz.jvm.pojo.UserInfo.userId')
from 
    cn.boz.jvm.pojo.UserInfo t
where 
    t.userId.toString().trim().equals("dca3ef0f-25ce-4a76-b693-6992be3b063d")

效果如下图所示
在这里插入图片描述

select 
    sum(reachables(t),'sizeof(t)')
from 
    cn.boz.jvm.pojo.UserInfo t
where 
    t.userId.toString().trim().equals("3e91ad39-6511-4e93-bb50-e3eef50fdf1a")
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值