本文主要介绍JVM和GC解析
本文较长,分为上下篇(可收藏,勿吃尘)
如有需要,可以参考
如有帮助,不忘 点赞 ❥
一文理清JVM和GC上篇
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 ,会造成:
1.GC清理的一点点内存很快会再次填满,迫使GC再次执行,这样就形成了恶性循环。
2.CPU的使用率一直是100%,而GC却没有任何成果
OutOfMemeoryError:Direct buffer memory
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); } 复制代码
OutOfMemeoryError:unable to create new native thread
高并发请求服务器是,经常会出现该异常
导致原因
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取代了)存放了以下信息:
串行垃圾回收器(Serial)
并行垃圾回收器(parallel)
并发垃圾回收器(CMS)
G1垃圾回收器
查看默认的垃圾收集器
java -XX:+PrintCommandLineFlags -version
默认的垃圾收集器
新生代
串行GC(Serial)/(Serial Coping)
一个单线程的收集器,在进行垃圾收集的时候,必须暂停其他所有的工作线程知道它收集结束
表示:
新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法 并行GC(ParNew)
使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其他所有工作的线程知道它收集结束
JVM设置参数
XX:+UseParNewGC 启用 ParNew收集器,只影响新生代的收集,不影响老年代。开启上述参数后,会使用:ParNew (新生代区用)+Serial Old(老年代区用)策略,新生代使用复制算法,老年代使用标记-整理算法。
关注点:
JVM设置参数
-XX:UseParallelGC 或 -XX:UseParallelOldGC(可互相激活),开启后:新生代使用复制算法,老年代使用标记-整理算法。
老年代
ParallelScavenge
收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge+Serial Old) JVM设置参数
-XX:+UseParallelOldGC 开启 Parallel Old收集器,设置该参数后,使用 新生代Parallel + 老年代Parallel Old 策略
优点:并发收集低停顿
缺点:
关键4步:
1. Initial Mark (初始标记): 标记GC Root可以直达的对象,耗时短。
如何选择垃圾收集器
以前垃圾收集器的特点
1.年轻代和老年代是各自独立且连续的内存块
2.年轻代中Eden+S0+S1使用复制算法进行收集
3.老年代收集必须扫描整个老年代区域
4.都是以尽可能少而快速地执行GC为设计原则
G1概念
Garbage-First 收集器,是一款面向服务端应用的收集器
G1收集器的设计目标是取代CMS收集器
优势:
主要改变是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内存
最大好处就是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可
(2)回收步骤
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
(3)4步过程
(4)常用配置参数
开启G1垃圾收集器
设置G1区域的大小。值是2的幂,范围是1M到32M。目标是根据最小的Java堆大小划分出约2048个区域
最大停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿时间小于这个时间
堆占用了多少的时候就触发GC,默认是45
并发GC使用的线程数
设置作为空闲时间的预留内存百分比,以降低目标空间溢出的风险,默认值是10%
(5)与CMS相比的优势
1)G1不会产生内存碎片。
2)是可以精确控制停顿,该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
(6)总结
top
前五行是统计信息
第一行是任务队列信息,同uptime命令的执行结果一样
17:16:47:
当前时间
up 23:47:
系统运行时间
2 users:
当前登录用户数
load average:0.21,0.27,0.19:
系统负载,既任务队列的平均长度,三个数值分别为1分钟、5分钟、15分钟前到现在的平均值
procs
cpu
free
应用程序中可用内存/系统物理内存>70%内存充足
需要增加内存
内存基本够用
df
查看磁盘剩余空闲数
iostat -xdk 2 3
-o:该参数是用户自定义格式
-p:pid进程使用cpu的时间
-m : 显示所有线程
本文较长,能看到这里的都是好样的,成长之路学无止境
今天的你多努力一点,明天的你就能少说一句求人的话!
很久很久之前,有个传说,据说:
看完不赞,都是坏蛋