本文主要介绍JVM和GC解析
本文较长,分为上下篇(可收藏,勿吃尘)
若有须要,能够参考
若有帮助,不忘 点赞 ❥java
一文理清JVM和GC上篇
linux
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却没有任何成果ios
OutOfMemeoryError:Direct buffer memory
- 写NIO程序常用 ByteBuffer 来读取或者写入数据,这是一种基于通道(Channel)和缓冲区(Buffer)的 I/O 方式,它可使用 Native 函数库直接分配堆外内存,而后经过一个存储在Java 堆里面的DirectByteBuffer 对象做为这块内存的引用进行操做。这样能在一些场景中显著提升性能,由于避免了在Java堆和Native堆中来回复制数据。
- 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);
}
复制代码
OutOfMemeoryError:unable to create new native thread
高并发请求服务器是,常常会出现该异常
致使缘由web
- 你的应用建立了太多线程了,一个应用进程建立多个线程,超过系统承载权限。
- 你的服务器并不容许你的应用程序建立这么多线程,linux系统默认容许的那个进程能够建立的线程数时1024个,你的应用建立超过这个数量就会报OutOfMemeoryError:unable to create new native thread
解决办法算法
- 想方法减低你应用程序建立线程的数量,分析应用是否真的须要建立那么多线程,若是不是,改代码将线程数降到最低。
- 对于有点应用,确实须要建立不少线程,远超过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取代了)存放了如下信息:服务器
- 虚拟机加载的类信息
- 常量池
- 静态常量
- 即时编译后的代码
- GC算法(引用计数/复制/标清/标整)是内存回收的方法,垃圾收集器就是算法的实现
- 目前为止尚未完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集
串行垃圾回收器(Serial)
它为单线程环境设计而且只是用一个线程进行垃圾回收,会暂停全部的用户线程。因此不适合服务器环境。并行垃圾回收器(parallel)
多个垃圾回收线程并行工做,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景并发垃圾回收器(CMS)
用户线程和垃圾收集线程同时执行(不必定是并行,可能交替执行),不须要停顿用户线程,适用于对响应时间有要求的场景G1垃圾回收器
G1垃圾回收器将堆内存分割成不一样的区域而后并发的对其进行垃圾回收
查看默认的垃圾收集器
java -XX:+PrintCommandLineFlags -version
网络
默认的垃圾收集器
多线程
新生代
并发
一个单线程的收集器,在进行垃圾收集的时候,必须暂停其余全部的工做线程知道它收集结束
JVM设置参数
-XX:+UseSerialGC开启后会使用:Serial(Young区用)+Serial Old(Old区用的)收集器组合,表示:
新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法app
并行GC(ParNew)使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其余全部工做的线程知道它收集结束
JVM设置参数
XX:+UseParNewGC 启用 ParNew收集器,只影响新生代的收集,不影响老年代。开启上述参数后,会使用:ParNew (新生代区用)+Serial Old(老年代区用)策略,新生代使用复制算法,老年代使用标记-整理算法。
并行回收GC(Parallel)/(Parallel Scavenge)
关注点:
JVM设置参数
-XX:UseParallelGC 或 -XX:UseParallelOldGC(可互相激活),开启后:新生代使用复制算法,老年代使用标记-整理算法。
老年代
ParallelScavenge
收集器,只能保证新生代的吞吐量优先,没法保证总体的吞吐量。在JDK1.6以前(Parallel Scavenge+Serial Old)JVM设置参数
-XX:+UseParallelOldGC 开启 Parallel Old收集器,设置该参数后,使用 新生代Parallel + 老年代Parallel Old 策略
并发标记清除GC(CMS)
优势: 并发收集低停顿
缺点:
- 并发执行,对CPU资源压力大:
因为并发进行,CMS在收集与应用线程会同时会增长对堆内存的占用,也就是说,CMS必需要在老年代堆内存用尽以前完成垃圾回收,不然CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而形成较大停顿时间。- 采用的标记清除算法会致使大量碎片:
标记清除算法没法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,随后将不得不经过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFulllGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集以后,进行一次压缩的Full GC。
关键4步:
1.Initial Mark (初始标记): 标记GC Root能够直达的对象,耗时短。
- Concurrent Mark(并行标记): 从第一步标记的对象出发,并发地标记可达对象。
- Remark(从新标记): 从新进行标记,修正Concurrent Mark期间因为用户程序运行而致使对象间的变化及新建立的对象,耗时短。
- Concurrent Sweep(并行回收): 并行地进行无用对象的回收。
![]()
-.-
如何选择垃圾收集器
之前垃圾收集器的特色
1.年轻代和老年代是各自独立且连续的内存块
2.年轻代中Eden+S0+S1使用复制算法进行收集
3.老年代收集必须扫描整个老年代区域
4.都是以尽量少而快速地执行GC为设计原则
G1概念
Garbage-First 收集器,是一款面向服务端应用的收集器
- 整理空闲空间更快
- 须要更多的时间来预测GC停顿时间
- 不但愿牺牲大量的吞吐性能
- 不须要更大的Java Heap
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)总结
17:16:47:
当前时间up 23:47:
系统运行时间2 users:
当前登陆用户数load average:0.21,0.27,0.19:
系统负载,既任务队列的平均长度,三个数值分别为1分钟、5分钟、15分钟前到如今的平均值 procs
cpu
内存充足
须要增长内存
内存基本够用
-o:该参数是用户自定义格式
-p:pid进程使用cpu的时间
-m : 显示全部线程
- 步骤4:
将须要的线程ID转换为16进制格式(英文小写格式)
再使用:printf "%x/\n" 有问题的线程ID- 步骤5:
jstat 进程ID | grep tid(16进制线程ID小写英文)
本文较长,能看到这里的都是好样的,成长之路学无止境
今天的你多努力一点,明天的你就能少说一句求人的话!好久好久以前,有个传说,听说:
看完不赞,都是坏蛋