大家好,我是小菜,一个渴望在互联网行业做到蔡不菜的小菜。可柔可刚,点赞则柔,白嫖则刚!
死鬼~看完记得给我来个三连哦!
本文主要介绍
JVM和GC解析
如有需要,可以参考
如有帮助,不忘 点赞 ❥创作不易,白嫖无义!
public static void main(String[] args) {
stackOverflowError(); //Exception in thread "main" java.lang.StackOverflowError
}
private static void stackOverflowError() {
stackOverflowError();
}
public static void main(String[] args) {
String str = "cbuc";
for (; ; ) {
str += str + UUID.randomUUID().toString().substring(0,5); //+= 不断创建对象
}
}
程序在垃圾回收上花费了98%的时间,却收集不会2%的空间。
假如不抛出GC overhead limit
,会造成:
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
高并发请求服务器时,经常会出现该异常
导致原因:
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();
}
}
Java 8之后的版本使用Metaspace来替代永久代
Metaspace
是方法区在HotSpot
中的实现,它与持久带最大的区别在于:Metespace
并不在虚拟机内存中而是使用本地内存
永久代(java8 后被原空间Metaspace取代了)存放了以下信息:
**GC算法(引用计数/复制/标清/标整)**是内存回收的方法,垃圾收集器就是算法的实现
目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集
它为单线程环境设计并且只是用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。
多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景
用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,适用于对响应时间有要求的场景
G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
java -XX:+PrintCommandLineFlags -version
UseSerialGC
UseParallelGC
UseConcMarkSweepGC
UseParNewGC
UseParallelOldGC
UseG1GC
最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾手机过程中可能会产生较长的停顿(“Stop-The-World”状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得更高的单线程垃圾收集效率, 因此Serial垃圾收集器依然是Java虚拟机运行在Client 模式下默认的新生代垃圾收集器。
JVM设置参数:
-XX:+UseSerialGC
开启后会使用:Serial(Young区用)+Serial Old(Old区用的)收集器组合
,
表示:
新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其他所有工作的线程知道它收集结束
ParNew
收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC
工作,其余的行为和Serial收集器完全一样,ParNew
垃圾收集器在垃圾收集过程中同样也要暂停所有的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。
JVM设置参数:
XX:+UseParNewGC
启用 ParNew收集器,只影响新生代的收集,不影响老年代。开启上述参数后,会使用:ParNew (新生代区用)+Serial Old(老年代区用)策略
,新生代使用复制算法,老年代使用标记-整理算法。
并行回收GC(Parallel)/(Parallel Scavenge)
Parallel Scavenge
收集器类似ParNew
也是新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。串行收集器在新生代和老年代的并行化
关注点:
JVM设置参数
-XX:UseParallelGC
或 -XX:UseParallelOldGC
(可互相激活),开启后:新生代使用复制算法,老年代使用标记-整理算法。
串行GC(Serial Old)/(Serial MSC)
Serial Old 是Serial 垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的老年代垃圾收集器。
用途:
Parallel Scavenge
收集器搭配使用。(Parallel Scavenge+Serial Old
)并行GC(Parallel Old)/(Parallel MSC)
Parallel Old
收集器是Parallel Scavenge
的老年代版本,使用多线程的标记-整理算法,Parallel Old在JDK 1.6之前,新生代使用 ParallelScavenge
收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge+Serial Old
)
Parallel Old
正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8 后可以优先考虑新生代Parallel Scavenge
和年老代 Parallel 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步:
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:+UseConcMarkSweepGC
-XX:+ParNewGC
以前垃圾收集器的特点:
Garbage-First
收集器,是一款面向服务端应用的收集器,优点如下:
G1收集器的设计目标是取代CMS收集器
Stop-The-World (STW)
更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间主要改变是Eden
,Survivor
和Tenured
等内存区域不再是连续的了,而是变成了一个个大小一样的region
,每个region
从1M
到32M
不等。一个region
有可能属于Eden
,Survivor
或者Tenured
内存区域。
STW
(1)Region区域化垃圾收集器·
区域化内存划片Region,整体编为了一下列不连续的内存区域,避免了全内存区的GC操作。
核心思想:
将整个堆内存区域分成大小相同的子区域(Region
),在JVM启动时会自动配置这些子区域的大小。
在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n
可指定分区大小(1MB~32MB
,且必须是2的幂),默认将整堆划分为2048
个分区。
大小范围在1MB~32MB
,最多能设置2048
个区域,也即能够支持的最大内存为:32MB*2048=65536MV=64G
内存
最大好处就是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可
(2)回收步骤
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
Eden
区的数据移动到新的Survivor
区,部分数据晋升到Old
区。Survivor
区的数据移动到新的Survivor
区,部分数据晋升到Old
区。(3)执行四步
初始标记:
只标记GC Roots
能直接关联到的对象
并发标记:
进行GC Roots Tracing
的过程
最终标记:
修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
筛选回收:
(4)常用配置参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=n
-XX:MaxGCPauseMillis=n
-XX:InitiatingHeapOccupancyPercent=n
-XX:ConcGCThreads=n
-XX:G1ReservePercent=n
(5)与CMS相比的优势
(6)总结
top
前五行是统计信息
第一行是任务队列信息,同uptime命令的执行结果一样
17:16:47:
当前时间
up 23:47:
系统运行时间
2 users:
当前登录用户数
load average:0.21,0.27,0.19:
系统负载,既任务队列的平均长度,三个数值分别为1分钟、5分钟、15分钟前到现在的平均值
1)vmstat
vmstat -n 2 3
第一个参数是采样的时间间隔数(单位:秒),第二个参数是采样的次数
主要参数:
procs
cpu
2)mpstat
3)pidstat
pidstat -u 1 -p 进程号
每个进程使用cpu的用量分解信息
free
应用程序中可用内存 / 系统物理内存>70%:内存充足
应用程序可用内存/系统物理内存<20% 内存不足:需要增加内存
20%<应用程序可用内存/系统物理内存<70%: 内存基本够用
df
iostat -xdk 2 3
先用top
命令找出CPU占比最高的
ps -ef
或者 jps
进一步定位,得知是一个怎样的后台程序
定位到具体线程或者代码
ps -mp 进程 ==-o== THREAD,tid,time
-o
:该参数是用户自定义格式
-p
:pid进程使用cpu的时间
-m
: 显示所有线程
将需要的线程ID转换为16进制格式(英文小写格式)
再使用:printf "%x/\n" 有问题的线程ID
jstat 进程ID | grep tid(16进制线程ID小写英文)
jps
虚拟机进程状况工具
jinfo
Java配置信息工具
jmap
内存映像工具
jstat
统计信息监控工具
今天的你多努力一点,明天的你就能少说一句求人的话!
我是小菜,一个和你一起学习的男人。