JVM

1.java运行时数据区

运行时数据区

1.1程序计数器(线程独有)

类似于计算机中的寄存器,记录当前线程执行的指令和行号。如我们所知,线程和进行都是cpu执行的时间片段,只是粒度大小不一样而已。所有在线程进行切换的过程中,需要保存线程进入等待时的指令行号,这样下一次才能找到从哪个位置继续执行。

1.2 虚拟机栈(线程独有)

当一个线程的栈深度大于虚拟机所允许的深度的时候,将会抛出StackOverflowError异常;如果当创建一个新的线程时无法申请到足够的内存,则会抛出OutOfMemeryError异常。

1.2.1栈帧结构

在执行方法时,会创建一个栈帧(一个方法对应一个栈帧),并压入虚拟机栈当中。


栈帧
1.2.2局部变量表和操作数栈
  javap -c Test.class > p.txt 反汇编字节码文件后,得到操作指令
操作数栈

上述方法执行的指令含义如下:

本地变量表: this, i, j    。顺序为0,1,2

1.加载本地变量表变量1的值,压入操作数栈中。 
2.加载本地变量表变量2的值,压入操作数栈中。
3.压入操作指令iadd,计算出结果并将之前的弹出栈外
4.返回int类型的值

更多汇编指令参考:http://bbs.gupaoedu.com/forum.php?mod=viewthread&tid=295&highlight=javap

1.2.3动态链接

多态情况下,方法动态绑定时,需要用到动态链接

1.2.4方法出口

当一个方法开始执行以后,只有两种方法可以退出当前方法:
当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion),一般来说,调用者的PC计数器可以作为返回地址。
当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion),返回地址要通过异常处理器表来确定。
当方法返回时,可能进行3个操作:
恢复上层方法的局部变量表和操作数栈
把返回值压入调用者调用者栈帧的操作数栈
调整 PC 计数器的值以指向方法调用指令后面的一条指令

1.3本地方法栈(线程独有)

调用本地方法时使用

1.4方法区

方法区

1.5heap

heap

2.java内存模型(JMM, java memory model)

2.1内存结构

内存模型

问题1:新生代的垃圾回收算法为什么用复制算法?
因为新生代的对象90%都是朝生夕死,用标记清除法容易造成内存碎片,而标记整理法代价太高
所以采用复制算法。
问题2:为什么新生代不直接设置两个区,比例为1:1,或者9:1
如果是1:1的,空间利用率只有50%。如果是9:1的话,大部分对象的分代年龄只能达到1,也就是经过一次minorGC就会进入到老年代中。
问题3:怎么控制survivor的比例和新老年代的内存比例?
-XX:SurvivorRatio=8
-XX:NewRatio=2
更多JVM参数请参考:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

2.2对象生命周期

2.2.1类加载检查

在对象创建之前,需要先判断类是否已经被加载,判断方式在当前类加载器中寻找该类的类信息,如果没有找到,则是未加载,因此多个类加载器可以加载同一个类。

2.2.2为对象分配内存

问题1:如何知道哪块是可用内存?
如果采用的垃圾收集器是Serial、ParNew等带Compact过程的收集器的时候(新生代创建对象,复制对象),内存是规整的,采用移动指针的方式进行分配。
如果采用的垃圾回收器是CMS这种采用标记清除法的时候(新生代对象移动到老年代),内存是不规整的,采用空闲列表的方式记录可用的内存区域。

问题2:多个线程同时创建对象的话,会出现并发问题,如何解决?

  • 指针碰撞 (使用CAS来同步)
  • TLAB thread local allocation buffer,为每个线程分配一个缓冲区。-XX:+UseTLAB打开此策略。

2.3初始化内存空间

内存分配完成之后,虚拟机会将分配空间内都初始化为零值(不包括对象头),如果使用TLAB分配,这一过程也可以提前至TLAB分配时进行。

2.4设置对象头

包括对象的哈希码、类元素信息、GC分代年龄等。这些信息都放置在对象头中。

2.5执行<init>方法

2.6对象回收

问题1:什么样的对象需要被回收?
无用的对象,即外部引用指向的对象。
问题2:怎么判断该对象已无引用?

  • 引用计数法,无法解决循环引用的问题
  • 可达性分析 GCroot不可达

问题3:什么对象可以成为GCRoot?
局部变量表中引用的对象,静态变量引用的对象,常量引用。
问题4:强引用,软引用,弱引用,虚引用的对象回收策略。

  • 强引用
Object obj = new Object();      // 这个obj就是对象的强引用,只要有强引用存在,就不会被回收。
  • 软引用
Object obj = new Object();                                                  // obj是强引用
SoftReference<Object> sf = new SoftReference<Object>(obj);      // sf是软引用
obj = null;                                    // 强引用置空
sf.get();//有时候会返回null    //垃圾回收时,如果内存空间不足,会回收软引用的对象。 
  • 弱引用
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//弱应用对象只能存活到下一次垃圾回收
  • 虚引用
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除

各类引用使用场景请参考:https://www.cnblogs.com/yw-ah/p/5830458.html

2.7 对象死亡拯救

finalize

3.垃圾回收

3.1垃圾回收算法

  • 标记清除算法
    最基础的算法便是标记-清除算法(Mark-Sweep)。算法分为“标记”和“清除”两个阶段:首先标记处需要收集的对象,在标记完成之后,再统一回收所有被标记的对象。
    这是最简单的一种算法,但是缺点也是很明显的:一个是效率问题,标记和清除效率都不高。二是空间问题,清除之后会产生大量的空间碎片,导致之后分配大对象找不到足够的连续对象而不得不触发另一次垃圾收集动作。


    标记清除算法
  • 复制算法
    复制算法(Copying)将可用内存按照容量大小分成相等的两份,每次只使用一半。当这一块内存用完了,就会将还存活的对象复制到另一块内存上,然后将之前的那块内存清空。
    优点是解决了空间碎片的问题,而且分配新对象的时候顺序分配,实现简单,运行高效。缺点是内存减小了一半。


    复制算法
  • 标记整理法
    复制算法在对象存活率较高的情况下,效率会变低。而且浪费了50%的空间。
    根据老年代的特点,有人提出了另外一种“标记-整理”算法(Mark-Compact)。算法的也分为标记和整理两个阶段。标记和“标记-清除”算法的标记过程一样。当标记完成之后,并不直接对可回收对象进行整理,而是所有存活的对象整理成连续的,然后清理掉剩余的空间。


    标记整理法

3.2 分代垃圾回收和垃圾收集器

垃圾收集器
  • youngGC
    当eden区空间不足时触发,采用回收算法是复制。在youngGC之前,会进行一次分配担保。
    进行youngGC时,会在from和to区来回复制,每次youngGC,对象的年龄就会加1。当新生代空间不足时,或对象达到一定年龄后会进入老年代。
    问题1:分配担保机制:
    在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
    问题2:什么对象会进入老年代?
    对象很大 -XX:PretenureSizeThreshold=3145728 3M
    长期存活的对象 -XX:MaxTenuringThreshold=15
    动态年龄判断 相同年龄所有对象的大小总和 > Suvivor空间的一般,将会重新设置晋升年龄阈值
  • oldGC
    老年代回收发生在剩余内存无法装载新生代存活的对象的时候和无法装载大对象的时候(大对象直接进入老年代。CMS的回收算法是标记清除法,其余如Serial Old, Parallel为标记整理算法。
  • fullGC
    触发机制:
    ① 永久代空间不足
    ② 分配担保检查不符合minorGC的条件
    回收整个heap和metaspace(1.8之前叫做永久代),此过程十分缓慢
垃圾收集器
  • Serial
    特点:单线程,会造成用户停顿。
    VM参数:
    -XX:+UserSerialGC 在新生代和老年代使用串行收集器
    -XX:+SurvivorRatio 设置eden和survivor区大小的比例
    -XX:+PretenureSizeThreshold 直接晋升到年老代的对象大小,设置此参数后,超过该大小的对象直接在年老代中分配内存
    -XX:+MaxTenuringThreshold 直接晋升到年老代的对象年龄,每个对象在一次Minor GC之后还存活,则年龄加1,当年龄超过该值时进入年老代
  • ParNew
    特点:serial的多线程版本
    VM参数:
    -XX:+UseParNewGC
  • Parallel Scavenge
    特点:关注吞吐量,而不是用户停顿
    VM参数:
    -XX:+UseParNewGC 打开此开关参数后,使用ParNew+Serial Old收集器组合进行垃圾收集。
    -XX:+UseParallelOldGC 打开此开关参数后,使用Parallel Scavenge+Parallel Old收集器组合进行垃圾收集。
    -XX:+ParallelGCThreads 设置并行GC时进行内存回收的线程数。
    -XX:+MaxGCPauseMillis Parallel Scavenge收集器最大GC停顿时间。
    -XX:+GCTimeRation Parallel Scavenge收集器运行时间占总时间比率。
    -XX:+UseAdaptiveSizePolicy java虚拟机动态自适应策略,动态调整年老代对象年龄和各个区域大小。
  • Serial Old
    特点:标记整理算法,单线程,CMS备用方案
  • Parallel Old
    特点:多线程,标记整理
  • CMS
    特点: 多线程,标记清除,容易出发FullGC
    CMS执行过程:
    CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。主要用于互联网或B/S系统的服务端,这类应用尤其重视服务的响应速度。
    从名字可以看出,CMS是基于“标记-清除”算法的,运作过程更加复杂一些,分为4个步骤:
    ①.初始标记(CMS initial mark) 标记GC Roots直接关联的对象
    ②.并发标记(CMS concurrenr mark) 可达性分析算法
    ③.重新标记(CMS remark) 并发变动修改
    ④.并发清除(CMS concurrent sweep)
    其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会比初始标记阶段稍长,但比并发标记阶段要短。由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。执行过程如下图。


    CMS

    CMS收集器的优点:并发收集、低停顿
    CMS收集器的缺点:
    ①CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。
    ②由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。
    ③CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。

  • G1
    特点:内存布局改变,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
    G1收集器的运作大致可划分为以下几个步骤:
    初始标记(Initial Marking)
    并发标记(Concurrent Marking)
    最终标记(Final Marking)
    筛选回收(Live Data Counting and Evacuation)

3.java监控

3.1 查看GC类型和GC日志

  • 查看GC类型

    -XX:+PrintFlagsFinal -XX:+PrintCommandLineFlags
    
image.png

GC收集器默认类型是 Parallel Scanvage + Parallel Old

  • 查看GC日志

      -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/home/administrator/james/gc.log-XX:+PrintHeapAtGC
    
youngGC日志

fullGC日志

3.2 jmap

查看内存分配情况以及设置


jmap

heap配置

heap使用情况

3.3 jstat

查看内存使用情况以及gc次数


jstat

image.png

image.png

查看更多信息请参考:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jstat.html

3.4jstack (thread dump)

jstack 4170


jstack

3.5 jvisualvm & jsoncole

4.常见问题排查

4.1cpu占用过高

https://jingyan.baidu.com/article/4f34706e3ec075e387b56df2.html

4.2 内存泄漏排查

GC回收不掉

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,999评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,102评论 1 302
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,709评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,439评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,846评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,881评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,062评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,783评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,517评论 1 248
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,762评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,241评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,236评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,145评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,941评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,965评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,802评论 2 275

推荐阅读更多精彩内容

  • 1.一些概念 1.1.数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始...
    落落落落大大方方阅读 4,456评论 4 86
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,585评论 0 7
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,882评论 2 31
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,424评论 3 83
  • 内存溢出和内存泄漏的区别 内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,...
    Aimerwhy阅读 694评论 0 1