其他分享
首页 > 其他分享> > 继续探究:一文理清JVM和GC(下)

继续探究:一文理清JVM和GC(下)

作者:互联网

本文主要介绍 JVM和GC解析
如有需要,可以参考
如有帮助,不忘 点赞 ❥

创作不易,白嫖无义!

一、OOM的认识

StackOverflowError

 public static void main(String[] args) {
     stackOverflowError();   //Exception in thread "main" java.lang.StackOverflowError
 }
private static void stackOverflowError() {
    stackOverflowError();
}
复制代码

OutOfMemeoryError:java heap space

public static void main(String[] args) {
    String str = "cbuc";
    for (; ; ) {
        str += str + UUID.randomUUID().toString().substring(0,5);   //+= 不断创建对象
    }
}
复制代码

OutOfMemeoryError:GC overhead limit exceeded

程序在垃圾回收上花费了98%的时间,却收集不会2%的空间。
假如不抛出GC overhead limit,会造成:

继续探究:一文理清JVM和GC(下)

 

OutOfMemeoryError:Direct buffer memory

ByteBuffer.allocate(capability):这一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。

ByteBuffer.allocateDirect(capability):这一种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝,所以速度相对较快。

但是如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer 对象就不会被回收,这时候堆内存充足,但本地内存可能就已经使用光了,再次尝试分配本地内存就会出现OutOfMemeoryError,那程序就直接奔溃了。

public static void main(String[] args) {
    /**
     * 虚拟机配置参数
     * -Xms10m -Xmx10m -XX:+PrintGCDetails  -XX:MaxDirectMemorySize=5m
     */
    System.out.println("配置的maxDirectMemeory:"+     (sun.misc.VM.maxDirectMemory()/(double)1024/1024)+"MB");
    try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
    // -XX:MaxDerectMemorySize=5m  配置为5m, 这个时候我们使用6m
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);

}
复制代码

高并发请求服务器时,经常会出现该异常
导致原因

  1. 你的应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载权限。
  2. 你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许的那个进程可以创建的线程数是1024个,你的应用创建超过这个数量就会报OutOfMemeoryError:unable to create new native thread

解决办法

  1. 想方法减低你应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,如果不是,该代码将线程数降到最低。
  2. 对于有点应用,确实需要创建很多线程,远超过linux系统默认1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制
public static void main(String[] args) {
        for (int i = 1;  ; i++) {
            System.out.println("输出 i: " + i);
             new Thread(()->{
                 try {TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();}
             },"线程"+i).start();
        }
    }
复制代码

OutOfMemeoryError:Metaspace

Java 8之后的版本使用Metaspace来替代永久代
Metaspace是方法区在HotSpot中的实现,它与持久带最大的区别在于:Metespace并不在虚拟机内存中而是使用本地内存
永久代(java8 后被原空间Metaspace取代了)存放了以下信息:

二、4种垃圾收集器

GC算法(引用计数/复制/标清/标整)是内存回收的方法,垃圾收集器就是算法的实现

目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集

串行垃圾回收器(Serial)

它为单线程环境设计并且只是用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。

并行垃圾回收器(parallel)

多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景

并发垃圾回收器(CMS)

用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,适用于对响应时间有要求的场景

G1垃圾回收器

G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收

三、垃圾收集器解析

查看默认的垃圾收集器

java -XX:+PrintCommandLineFlags -version

继续探究:一文理清JVM和GC(下)

 

默认的垃圾收集器

新生代

继续探究:一文理清JVM和GC(下)

 

最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾手机过程中可能会产生较长的停顿(“Stop-The-World”状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,==没有线程交互的开销可以获得更高的单线程垃圾收集效率,== 因此Serial垃圾收集器依然是Java虚拟机运行在Client 模式下默认的新生代垃圾收集器。

JVM设置参数
-XX:+UseSerialGC开启后会使用:Serial(Young区用)+Serial Old(Old区用的)收集器组合,
表示

新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法

使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其他所有工作的线程知道它收集结束

继续探究:一文理清JVM和GC(下)

 

ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。

JVM设置参数

XX:+UseParNewGC启用 ParNew收集器,只影响新生代的收集,不影响老年代。开启上述参数后,会使用:ParNew (新生代区用)+Serial Old(老年代区用)策略,新生代使用复制算法,老年代使用标记-整理算法

继续探究:一文理清JVM和GC(下)

 

Parallel Scavenge收集器类似ParNew 也是新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。串行收集器在新生代和老年代的并行化

关注点:

  1. 可控制的吞吐量
  2. 自适应调节策略也是ParallelScavenge收集器与ParallelNew收集器的一个重要区别 JVM设置参数
    -XX:UseParallelGC 或 -XX:UseParallelOldGC(可互相激活),开启后:新生代使用复制算法,老年代使用标记-整理算法

老年代

  1. 在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。(Parallel Scavenge+Serial Old)
  2. 作为老年代版中使用CMS收集器的后备垃圾收集方案。

JVM设置参数
-XX:+UseParallelOldGC开启 Parallel Old收集器,设置该参数后,使用 新生代Parallel + 老年代Parallel Old策略

优点: 并发收集低停顿

缺点

  1. Initial Mark (初始标记):标记GC Root可以直达的对象,耗时短。
  2. Concurrent Mark(并行标记):从第一步标记的对象出发,并发地标记可达对象。
  3. Remark(重新标记): 重新进行标记,修正Concurrent Mark期间由于用户程序运行而导致对象间的变化及新创建的对象,耗时短。
  4. Concurrent Sweep(并行回收): 并行地进行无用对象的回收。
继续探究:一文理清JVM和GC(下)

 

如何选择垃圾收集器

四、G1垃圾收集器

以前垃圾收集器的特点

  1. 年轻代和老年代是各自独立且连续的内存块
  2. 年轻代中Eden+S0+S1使用复制算法进行收集
  3. 老年代收集必须扫描整个老年代区域
  4. 都是以尽可能少而快速地执行GC为设计原则

G1 概念:

Garbage-First收集器,是一款面向服务端应用的收集器,优点如下:

G1收集器的设计目标是取代CMS收集器

G1 优势:

  1. G1 是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片
  2. G1 的Stop-The-World (STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间

主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region,每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。

G1特点:

G1底层原理

(1)Region区域化垃圾收集器·

区域化内存划片Region,整体变为了一系列不连续的内存区域,避免了全内存区的GC操作。
核心思想

将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动配置这些子区域的大小。
在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n 可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048=65536MV=64G内存
最大好处就是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可

继续探究:一文理清JVM和GC(下)

 

(2)回收步骤

针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片

继续探究:一文理清JVM和GC(下)

 

(3)执行四步

继续探究:一文理清JVM和GC(下)

 

(4)常用配置参数

(5)与CMS相比的优势

(6)总结

继续探究:一文理清JVM和GC(下)

 

五、诊断生产环境服务器变慢

整机相关

top

继续探究:一文理清JVM和GC(下)

 

前五行是统计信息
第一行是任务队列信息,同uptime命令的执行结果一样
17:16:47:当前时间
up 23:47:系统运行时间
2 users:当前登录用户数
load average:0.21,0.27,0.19:系统负载,即任务队列的平均长度,三个数值分别为1分钟、5分钟、15分钟前到现在的平均值

CPU相关

1)vmstat

继续探究:一文理清JVM和GC(下)

 


vmstat -n 2 3
第一个参数是采样的时间间隔数(单位:秒),第二个参数是采样的次数
主要参数

2)mpstat

mpstat -P ALL 2
查看CPU核信息

继续探究:一文理清JVM和GC(下)

 

3)pidstat

pidstat -u 1 -p 进程号
每个进程使用cpu的用量分解信息

内存相关

free

应用程序中可用内存 / 系统物理内存>70%:内存充足
应用程序可用内存/系统物理内存<20% 内存不足:需要增加内存
20%<应用程序可用内存/系统物理内存<70%: 内存基本够用

继续探究:一文理清JVM和GC(下)

 

硬盘相关

df

查看磁盘剩余空闲数

继续探究:一文理清JVM和GC(下)

 

硬盘IO相关

iostat -xdk 2 3

继续探究:一文理清JVM和GC(下)

 

六、分析生产环境CPU占用过高

步骤1

先用top命令找出CPU占比最高的

步骤2

ps -ef 或者 jps 进一步定位,得知是一个怎样的后台程序

步骤3

定位到具体线程或者代码
ps -mp 进程 ==-o== THREAD,tid,time

-o:该参数是用户自定义格式
-p:pid进程使用cpu的时间
-m: 显示所有线程

步骤4

将需要的线程ID转换为16进制格式(英文小写格式)
再使用:printf "%x/\n" 有问题的线程ID

步骤5:

jstat 进程ID | grep tid(16进制线程ID小写英文)

七、常用的JVM监控和性能分析工具

 

继续探究:一文理清JVM和GC(下)

 


原文链接:
https://juejin.cn/post/6844904119321411591

标签:G1,收集器,理清,GC,内存,JVM,线程,垃圾
来源: https://www.cnblogs.com/0591jb/p/14653997.html