下面这4个案例来自大神“你假笨”(任职阿里期间,花名:寒泉子)在qcon上的分享,记录一下:

一、类加载死锁

现象:jstack将线程dump出来后,找不到deadlock字样的死锁信息,但是有大量的线程在调用Class.forName加载类

    @CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
} private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;

可以看到forName0是一个native方法,分析该方法的C++源码实现,可以发现使用了锁(细节略)

tips: jstack -m pid (可以看到native的详细输出信息,但不推荐生产上用,极端情况会让应用不稳定)

类加载在底层要加锁的原因也不难理解 ,如上图,如果三个线程并发加载类C,如果没有锁,最后可能会把类的元数据信息,在perm区(JDK8以前的版本,JDK8后取消了Perm区)中存多份,很容易造成内存泄露,所以需要加锁,加锁后变成下面这样:

这个并发加载的情况,从JDK7开始就做了优化,支持并发类加载,但是要使用该功能,必须注册成并行类加载器,否则仍然存在死锁可能。

参考文章:

https://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html

https://www.cnblogs.com/cz123/p/6918708.html

https://www.jianshu.com/p/8e8a5a773648

解决方法:

既然多线程并发加载可能出问题,那么就放在单线程里加载,可参考下面的示例,假设有2个类:Parent及Child

package com.cnblogs.yjmyzz.test;

public class Parent {
static {
System.out.println("Parent init.");
}
public static final Parent EMPTY = new Child();
public static void test() {
System.out.println("test called in class Parent.");
}
}

package com.cnblogs.yjmyzz.test;

public class Child extends Parent {
static {
System.out.println("Child init.");
}
}

如果用2个线程并发加载:

    public static void main(String[] args) {
new Thread(() -> new Child(), "T-1").start();
new Thread(() -> Parent.test(), "T-2").start();
}

T-1线程中,new Child()时,要先初始化父类Parent,需要加载类Parent,而T-2线程中调用Parent时,其static成员EMPTY又会尝试加载子类Child. 上述这段代码,如果试着运行几次,就有很大概率会遇到死锁:

会一直卡在这里。可以显式在主线程最开始用forName加载这2个类,这样类加载就变成在main线程中串行加载,问题得到解决:

    public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.cnblogs.yjmyzz.test.Parent");
Class.forName("com.cnblogs.yjmyzz.test.Child");
new Thread(() -> new Child(), "T-1").start();
new Thread(() -> Parent.test(), "T-2").start();
}

  

二、FinalReference堆积

现象:用jmap命令分析查看占用内存最多的对象时, 发现java.lang.ref.Finalizer实例排在最前面。

原因:

Object类有一个finalize方法,类似析构器,开发人员可以重载这个方法,用于清理资源。大多数情况下,java并不推荐重载该方法,因为jvm的GC已经把垃圾回收做得很好了。

但如果有某种原因,开发人员确实需要重载该方法:

    @Override
protected void finalize() throws Throwable {
//开发人员自定义的清理逻辑
}

即:这里有些自定义的清理逻辑。这种重载了finalize方法,且实现代码非空的类,在类加载时会被特殊标识,当实例创建时,被包装成FinalReference,放入一个队列里,当GC发生时,如果该实例被标识为垃圾对象,GC清理完后,会用一个额外的线程(重点:这是1个独立的单线程),从队列里一个个取出来,调用重载的finalize方法,如果这种对象在JVM中有大量实例,而且finalize里的清理逻辑,耗时又比较久的话,单线程忙不过来,只能等到下1个GC周期,才会继续清理,因此造成堆积。

建议:不用使用重载finalize的方式来清理资源。

三、堆外内存不释放

先回顾下堆外内存的分布,对于DirectByteBuffer之类的对象,JVM堆上只存放了其"引用",如下图,引用指向的实际内存块在JVM堆外(即:实际分配的堆外内存不受GC管控)

GC能管理的只是堆上的"引用"数据,但是这块数据通常又非常小,就算经过GC不停折腾,从年青代晋升到老年代,只要老年代的空间还够,就会一直存活,因此其指向的堆外内存也不会释放。除非发生Full GC,把"引用"数据给干掉了,其指向的堆外内存,才会被释放。

建议:使用-XX:MaxDirectMemorySize参数,限制堆外内存大小。

四、YGC时间不断拉长

现象:随着系统持续运行,单次YGC的时间越来越长。

可能原因:大量调用了String.intern方法,导致字符串的常量池越来越大,而每次YGC都要先mark标记,字符串常量池越大,需要扫描mark的对象也越多,时间就变长了。

排查方法:jmap -histo:live pid 强制触发一次Full GC,这会强制清理字符串常量池StringTable中无效的对象,如果YGC时间恢复,说明大概率就是这个原因。

JVM问题典型案例定位学习的更多相关文章

  1. NB-IoT的相关资料整理(基本概念,技术优势,典型案例和当前的进展)

            人与人之间的通讯规模已近天花板,物与物的则刚刚进入增长快车道.随着可穿戴.车联网.智能抄表等新兴市场的开启,工业4.0.智慧城市.智慧农业等理念照进现实,万物互联的时代正加速到来. 一 ...

  2. 《SAS编程和数据挖掘商业案例》学习笔记# 19

    继续<SAS编程与数据挖掘商业案例>学习笔记,本文側重数据处理实践.包含:HASH对象.自己定义format.以及功能强大的正則表達式 一:HASH对象 Hash对象又称散列表,是依据关键 ...

  3. 【Java新特性】Lambda表达式典型案例,你想要的的都在这儿了!!

    写在前面 不得不说,有些小伙伴的学习热情真高,学完了Lambda表达式的语法,想来几个典型案例再强化下.于是问冰河能否给几个Lambda表达式的典型使用示例.于是乎,便有了这篇文章. 案例一 需求 调 ...

  4. elasticsearch中的mapping映射配置与查询典型案例

    elasticsearch中的mapping映射配置与查询典型案例 elasticsearch中的mapping映射配置示例比如要搭建个中文新闻信息的搜索引擎,新闻有"标题".&q ...

  5. HBase基本知识介绍及典型案例分析

    本次分享的内容主要分为以下五点: HBase基本知识: HBase读写流程: RowKey设计要点: HBase生态介绍: HBase典型案例分析. 首先我们简单介绍一下 HBase 是什么. HBa ...

  6. 《SAS编程与数据挖掘商业案例》学习笔记之十六

    <SAS编程与数据挖掘商业案例>学习笔记,本次重点:sas宏变量 内容包含:宏变量.宏函数.宏參数.通配函数.字符函数.计算函数.引用函数.宏语句.宏应用 1.宏触发器: %name-to ...

  7. Agora 教程丨一个典型案例,教你如何使用水晶球“数据洞察”

    7 月初,声网Agora 水晶球的"数据洞察"功能正式版上线."数据洞察"可显示两种数据,一种是用量,另一种是质量. "数据洞察"的&quo ...

  8. 《深入理解Java虚拟机》-----第5章 jvm调优案例分析与实战

    案例分析 高性能硬件上的程序部署策略 例 如 ,一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个CPU.16GB物理内存,操作系统为64位CentOS 5.4 , Resin ...

  9. Java之JVM调优案例分析与实战(1) - 高性能硬件上的程序部署策略

    本JVM系列均来源于<深入理解Java虚拟机>一书中,版权归该书作者所有. 环境:一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新系统硬件为4个CPU.16GB物理内存.OS ...

  10. 一个典型案例为你解读TDSQL 全时态数据库系统

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯技术工程官方号发表在腾讯云+社区 经典案例 增量抽取.增量计算等都是T-TDSQL的经典案例.如下以增量计算为例,来分析T-TDS ...

随机推荐

  1. 通过cmd/批处理 开启关闭windows中的mysql数据库

    目录 cmd 开启关闭mysql 批处理 开启关闭mysql 每次通过服务开启关闭mysql太麻烦,可以通过cmd或批处理来解决 cmd 开启关闭mysql cmd需要使用管理员打开 //开启mysq ...

  2. 工具 | ApplicationScanner

    0x00 简介 ApplicationScanner是一款APP客户端安全项检测工具. 下载地址: ApplicationScanner下载: ApplicationScanner下载 0x01 功能 ...

  3. K8s新手系列之Pod的基本存储

    概念 官方文档:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-volume-storage/ 卷:h ...

  4. C语言:高级语言怎样抽象执行逻辑

    平时我们做编程的时候,底层 CPU 如何执行指令已经被封装好了,因此你很少会想到把底层和语言编译联系在一起.但从我自己学习各种编程语言的经历看,从这样一个全新视角重新剖析 C 语言,有助于加深你对它的 ...

  5. @FeignClient注解配置局部超时时间、OkHttp长连接和SocketTimeoutException异常解决办法

    问题描述:open feign配置OKhttp调用远程API,连续调用次数较少时,一切正常,次数非常多时(例如,连续请求600次)就抛出java.net.SocketTimeoutException: ...

  6. 【2020.11.20提高组模拟】祖先(ancestor) 题解

    [2020.11.20提高组模拟]祖先(ancestor) 题解 题目描述 对于每个\(i\),它都要往前面拜访它的祖先.对于\(i\)之前的编号为\(j\)的节点,如果要拜访的话需要满足对于\(\f ...

  7. Ubuntu16.04安装全记录(手工分区版)

    记录我在为SSD+机械硬盘的笔记本上安装Ubuntu16.04的全过程,主要是介绍手工分区需要注意的细节. 一.制作Ubuntu安装U盘 工具:UltraISO 镜像:http://releases. ...

  8. Synchronized是怎么实现的?

    回答重点 synchronized 实现原理依赖于JVM 的 Monitor(监视器锁)和对象头(Object Header) synchronized 修饰代码块:会在代码块的前后插入 monito ...

  9. anaconda基本操作及一些问题的解决记录

    anaconda虚拟环境 # anaconda 创建虚拟环境: conda create -n 虚拟环境名称 python=版本号 查看所有虚拟环境: conda env list 使用虚拟环境: c ...

  10. 爬虫(2)——requests以及xpath的使用

    一.requests requests.request(method,url,**kwargs) # 常见参数 # params/data/json 上传数据 # files 上传文件 # heade ...