JVM底层原理 内存模型+GC垃圾回收

 

java常用指令:   

javac -> 编译    java -> 运行

jps -> 查看当前java相关进程  

jinfo ->查看某一参数具体值

jinfo -flags 进程号    打出该java进程下所有JVM配置信息

jinfo  -flag  PrintGCDetails  进程号 ->  查看某个java进程 是否开启某个参数(PrintGCDetails打印GC详情)                

jinfo  -flag  MetaSpace  进程号->  查看某个Java进程 元空间大小

java -XX:+PrintCommandLineFlags  查看GC垃圾回收器

 

 

JVM堆内存空间分配:

 

方法区是一种概念,规范,元空间(永久代)是方法区的实现,包括:装载后类的信息,静态变量,常量池,即时编译后的代码

元空间/永久代内 什么情况下会发生垃圾回收?

1. 该类的所有对象都已经在堆内存中被回收

2.该类的类加载器已经被回收

3.该类的class对象都没有任何引用

满足三个条件则该类会在方法区内被回收

 

基于Hotspot虚拟机的TLAB

一般常说,栈是线程私有的,堆是共享的。 这句话并不完全正确,堆中Eden区会为每个线程预先留一小块内存空间用来创建对象 (称之为TLAB分配,即Thread Local Allocation Buffer)

1. 为什么这么做? 

防止多线程同时要去堆内存开辟空间,可能会造成多个线程争抢同一块内存地址并发问题

2. TLAB特点?

每个线程在Eden区开辟的一小块空间,

只是在“分配”这个动作上是线程独享的,至于在读取、垃圾回收等动作上都是线程共享的,

TLAB空间的内存也非常小,默认情况下仅占有整个Eden空间的1%。

3. TLAB带来的问题?

如果一个对象需要的空间大小超过TLAB中剩余的空间大小,则直接在堆内存中对该对象进行内存分配。

如果一个对象需要的空间大小超过TLAB中剩余的空间大小,则废弃当前TLAB,重新申请TLAB空间再次进行内存分配。

总结来说就是TLAB的剩余大小不满足新创建的对象大小,虚拟机定义了一个refill_waste的值,“最大浪费空间”。新创建的对象大于这个值则直接在堆内存分配,否则废弃当前TLAB 重新申请再分配。

 

System.gc()的理解

1.在默认情况下,通过System. gc()或者Runtime . getRuntime() .gc()的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。(System. gc()的底层就是Runtime . getRuntime() .gc() )
2.System. gc()只是提醒JVM的垃圾回收器执行Full GC,但是不确定是否马上执行,还有可能完全被拒绝。System. gc()与Runtime . getRuntime().gc()的作用一样。\

 

四大垃圾回收算法

1.引用计数法

2.复制算法    缺点:浪费空间   优点:不会产生内存碎片

作用于年轻代Eden区 From区和To区     复制-》清空-》交换  (复制之后有交换,谁空谁为To区)Eden区满了触发young区垃圾回收,对Eden和From区进行一次垃圾回收,将两个区剩余的对象复制一份到To区,然后清空,然后To和From交换。如果对象达到老年标准则进入老年代(交换15次)。

3.标记清除算法    先标记再清除  优点:节约内存空间    缺点:产生内存碎片

4.标记整理算法     先标记,再清除,最后整理把多余的碎片整理成整团      在标记清除算法上,不会产生内存碎片,增加了整理的耗时   

 

七大垃圾回收器

串行  并行  并发  G1  ——四种主要垃圾回收器

串行垃圾回收器: 为单线程设计,只使用一个线程进行垃圾回收,执行垃圾回收同时会暂停用户线程

并行垃圾回收器:串行垃圾回收器的加强版,有多个线程进行垃圾回收,执行垃圾回收的同时会暂停用户线程

并发垃圾回收器: 垃圾回收器线程和用户线程交叉执行,能做到尽量小的停顿用户线程(会产生内存碎片)

G1垃圾回收器: 将堆内存分割成不同区域,并发进行垃圾回收

 

并发标记清除GC——CMS: 用于老年代  (希望最小时间停顿收集的GC回收器   一般用于大型互联网应用垃圾回收)  

 1 初始标记(标记一下GC Roots能直接关联的对象,也就是静态变量和栈引用的局部变量)      2 并发标记(标记所有GC Root的间接引用对象,也就是被引用对象自己的实例变量)   

  3 再次标记(再标记第二阶段里新创建的对象,是否GC 引用到)      4 并发清除 

 只在首次标记和再次标记时是完全停顿的,并发标记和并发清除的时间都可与应用线程并发进行。

最耗时阶段在于 并发标记与并发清除阶段,好在两阶段都可以多线程执行 并且不需要STW。 

优点:保持较小停顿          缺点: 1 对CPU压力很高   2 产生内存碎片(可以设置一个参数 多少次full GC之后进行一次压缩full GC整理碎片)

老年区串行收集器作为CMS收集失败的后备收集器.

 

该参数配置为 CMS垃圾回收器多少次Full GC后会执行一次标记整理算法

 

 

G1垃圾回收器——G1:目的为了取代jdk8之前的CMS垃圾收集器

1.物理上不区分年轻代老年代,将整个堆内存分成规格大小的豆腐块Region(1-32m大小)  ,每个豆腐块属性标识了新生代,Eden,Survivor,养老代等标记,物理上不是连续的,逻辑上连续

Humongous-大对象存储  ,

2.整体采用标记整理算法,局部采用复制算法,不会产生内存碎片

2.GC能与应用程序并发执行,高吞吐量同时尽量减少停顿时间

3.预测GC停顿时间机制,用户可以指定期望停顿时间,GC垃圾收集会尽量小于期望时间(根据停顿时间去收集垃圾最多的豆腐块区域)

优点:没碎片,减少停顿,期望精准停控时间

G1回收过程

小区域内收集+连续内存块

 

GCRoots

如何判断一个对象是否可被回收:   枚举根节点做可达性分析(根搜索路径)

                  

 从GC根开始通过引用关系进行遍历,遍历不到的节点对象视为死亡。

GCRoots -> GC根的对象:1.栈中局部变量引用的对象   2.方法区中类静态属性引用的对象   3.方法区中常量引用的对象   4.本地方法中Native引用的对象

 

finalize关键字,

用作GCRoot时垂死挣扎的一次机会

发生GC时,会先判断对象是否执行过finalize 方法,如果未执行,则会先执行 finalize 方法,我们可以在此方法里将当前对象与 GC Roots 关联,这样执行 finalize 方法之后,GC 会再次判断对象是否可达,

如果不可达,则会被回收,如果可达,则不回收!

finalize 方法只会被执行一次,如果第一次执行 finalize 方法此对象变成了可达确实不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被回收!这一点切记!

 

 

常见JVM参数:

java -XX:+Print Flags Initial    查看JVM默认初始参数值

java  -XX:+Print Flags final    查看JVM修改后的参数值   =    :=

JVM参数:

1.标配参数   自打jdk娘胎出来 一直很稳定几乎没变化 如  java -version  java -help

2.-X参数    默认 -Xmixed混合模式

3.-XX参数   

  3.1. Boolean类型参数  · -XX: -PrintGCDetails  关闭打印GC收集细节             -XX:+PrintGCDetails 打开打印GC收集细节

  3.2. K-V设值类型参数   -XX: -MetaspaceSize=128m    

  

 -Xms  初始化堆内存大小   等价于 -XX:initialHeapSize    默认大小物理内存的1/64

  -Xmx 最大化堆内存大小    等价于  -XX:MaxHeapSize     默认物理内存1/4

 -Xss  单个线程初始栈空间             等价于ThreadStackSize   默认512k-1024k

其他参数包括: 设置伊甸区,from区和to区比例(默认8:1:1)   设置新生区和养老区比例 (默认1:2)  设置新生区经过多少轮GC进入老年区(默认15次)

 

 

引用问题Reference: 

Reference 强引用,  永远不会被垃圾GC回收的对象,即使死都不回收

SoftReference 软引用,  内存足够时不会回收,内存不足时会GC回收          应用场景:大量加载图片 每次从本地读取大量图片消耗性能,全部缓存到内存,可能内存不足,由此提出软引用和弱引用。

WeekReference 弱引用,  只要GC就会被回收

PhantomReference 虚引用   用于监控一个对象的回收状态  必须与引用队列联合使用  

ReferenceQueue queue = new ReferenceQueue();

Object obj1 = new Object();

PhantomReference phantom = new PhantomRefence(obj,queue);

WeekHashMap  key为 弱引用,如果Key被置空,底层Node<key,value>节点就会被回收。

 

PhantomReference维护一个队列,

PhantomReference的引用级别属于弱引用,并且调用获取对象的方法永远返回null

作用是: 如果PhantomReference引用的对象被回收,PhantomReference会加入到队列中,通过queue.poll()获取,因此可判断出他的对象是不是被回收了,

判断之后我们就可以相应的处理,可参考DirectByteBuffer的Cleaner

 

 

OOM篇

StackOverFlowError

OutOfMemoryError:  java heap space   栈溢出

OutOfMemoryError: GC overhead limit exceeded     GC回收时间过长,用了98%的时间,回收不到2%的内存。由于每次只回收2% 导致内存又快速被占满 导致再次GC

OutOfMemoryError: Direct buffer memory   Native本地内存满了。  写NIO程序时常用ByteBuffer来读写数据,一种基于通道Channel,缓冲区Buffer的I/O方式,可以通过Native函数库直接在堆外分配内存给读写数据,然后堆内对象DirectByteBuffer引用这块Native内存。

OutOfMemoryError: unable to create new native Thread     创建了过多的线程,一个进程创建这么多的线程超出了系统承载

OutOfMemoryError:  MetaSpace

 

排查实战;

top 命令 找到cpu占比最高的进程

ps -ef 找到对应的进程号

ps -mp 进程号 -o  THREAD,tid,time | sort -rn    找出进程中具体的线程号 tid

printf 将10进制的线程号转为16进制 (英文字母小写)

jstack 进程号 | grep tid(十六进制的线程号)   查看栈空间日志

 

 

posted @ 2020-05-11 18:32  六小扛把子  阅读(340)  评论(0编辑  收藏  举报