JVM相关知识

一个类是怎么被使用的

1.首先java源文件经过词法解析被转换为token流
2.然后再语义解析中按照java的语法组装成一个语法树
3.然后在经过语义分析检查关键字的使用是否合理,类型是否匹配,作用域是否正确
4.最后就生成了字节码
5.然后JVM把字节码加载到内存中已一个byte数组存储,并初步的校验cafe babe魔法值,常量池,文件长度,是否有父类等,然后创建 java.lang.Class实例
6.然后是更详细的检验,没问题则给静态变量分配内存,并设置默认值。然后是确保类和类之间的相互引用是否正确,完成内存结构分布
7.然后是执行类的构造器方法。如果赋值运算有用到另一个类那么就会马上加载解析另一个类,
8.然后就是根据方法的使用率来进行解释执行直接给到机器去运行或者JIT编译转化为机器码再给到机器去运行

PS:
1-4 是吧源文件转换为字节码
5-7是类加载过程
8 是类的编译过程

其中类加载过程一般采用的双亲委派的方式,就是先问下加载器的父类加载器 这个类是否加载过?并且父类加载器是否有能力加载?只有这2个都是否的时候才能自己加载类

解释执行就是直接吧代码交给CPU去执行 没有了编译的时间
JIT编译执行就是会把代码编译成机器码,然后交给cpu去执行 相比解释执行他的执行效率更高

现在JVM执行字节码的方式一般是解释执行和JIT编译混合的方式来执行的,就是一开始都是用解释执行,省去了编译的时间,然后把热点代码再用JIT编译执行。因为代码也是符合28原则的。如何确实热点代码就是通过计数器的方式

内存布局

1.本地方法栈

2.虚拟机栈

    栈是一个先进后出的构造。该空间是描述JAVA方法执行的内存区域,他是线程私有的。每个方法的执行就是栈帧进栈出栈的过程,一个栈帧相当于一个方法。

在这里插入图片描述

局部变量表: 主要用于存放方法的参数和局部变量。在编译的时候就确定了大小。局部变量表的最小单位是变量槽(Slot)。一个Slot占用的内存和系统有关,可能是64位也可能是32位。虚拟机访问slot是通过索引的方式。如果是实例方法,那么默认第一个槽即索引为0他就代表对该实例的引用,相当于this。为了节省空间Slot是允许被复用的。就因为这个特性所以最好是把已经不用的变量给赋值为null。这样就会被当做垃圾给回收。如果不重新赋值,可能不会这个Slot就还是会有引用,虚拟机就不敢回收。

操作栈: 类似于栈帧,算术运算或者方法调用都是通过操作操作数栈来进行参数的传递的。

返回地址:就是记录返回地址。一般返回只有2中情况 一个是遇到异常,一个是碰到返回的命令。

动态链接:  

3.程序计数器

4.堆区 heap

几乎存放了所有的实例对象。由垃圾回收机制自动回收。是现成共享的。可是手动设置堆的大小 (-Xms 最小  -Xmx 最大)-X的意思是虚拟机  ms是memory start  mx是memory max    。   如果设置成不一样那么就会造成堆的空间不稳定所以线上一般是设置成一样的大小。以此来减少虚拟机的压力。
堆可以分成2大块。一个是新生区(由一个Eden区和2个Survivor区组成)一个是老年区。首先一个对象产生如果没有超过新生区的最大值则会直接在老年区生成对象实例。反之则会在Eden区生产。经过一段时间后Eden区满了。则会发生YGC。这时虚拟机就会把还存活的对象都放入Survivor其中一个没有使用的区中。然后把在使用的给清空。如此反复进去。每个对象在经过一次YGC之后则会被计数,当他的次数大于15(默认值可以通过-XX:MaxTenuringThreshold来设置)的时候则会直接放到老年区。当Survivor满了的时候。这时虚拟机就会把对象放入老年代中存放,并清除新生区。如果老年代也满了则会发生FGC,如果FGC之后还是放不下,则会发生OOM。这只是比较常见的垃圾回收机制。具体的可以看JVM深入了解,把各种不同虚拟机的垃圾回收机制都讲了。回收算法大致分为3中。1.标记清除。2.复制算法。 3.标记整理。4.分代收集算法。以上介绍的就是第四个。现在商用的基本都是采用第四种方式。但是这仅仅是回收算法,具体的回收还是由垃圾回收器来实施的。
垃圾收集器大致有7种


在这里插入图片描述
垃圾收集器分为以上7中,新生代的有Serial,ParNew,Parallel Scavenge3种。老年代的有CMS ,Serial Old,Parallel old 3种。G1是1.7 update14之后才正式推出的商用的全能收集器。连接了线的表示他们之间可以配合着使用。不同的收集器各有特点。

    Serial收集器: 最老的,特点是他是单线程的。这里的单线程不仅仅指的是垃圾回收只有一个线程还有指的是他在进行垃圾回收的时候要停掉所有不是垃圾回收的线程(stop the world)但是他效率很高。收集100兆以下的垃圾只需要几十毫秒。所以这个在client模式下是完全可以接受的。所以他还是client模式默认的垃圾回收器。

    ParNew收集器:他与Serial收集器的区别只是他是多线程的。仅仅是针对垃圾回收的线程,正常用户的线程还是要停掉的。提升的是在多cpu的情况下他的性能就比Serial好。单个cpu来说ParNew还是不如Serial的,甚至2个cpu都不如。他还有一个特点就是他可以和CMS合作。这个CMS就很吊,他可以在不停止用户线程的情况下进行垃圾回收。

    Parallel Scavenge收集器:他和ParNew收集器的不同是他在意的不是停顿时间上,而是吞吐量(比如你虚拟机跑了100分钟 1分钟是垃圾回收的时间 那么吞吐量就是99). 他的好处就是可以手动设置停顿时间(-XX:MaxGCPauseMillis 单位毫秒)和吞吐量 (-XX:GCTimeRatio 99以下的整数)。所以他就比较适合用在后台运算而且不需要太多交互的任务上。他还有个特点就是可以不用程序员自己设置新生代Eden区(-Xmn)和Survivor区(-XX:MaxSurvivorRatio)的比例问题和晋升老年代大小(-XX:PretenureSizeThreshold)的问题,他会根据回收的情况自己去调整。通过参数-XX:UseAdaptiveSizePolicy

    CMS收集器:已减少停顿时间为目标。采用的是标记清除算法。清除的过程大致有4步

                1.初始标记: 该阶段还需要Stop The World  。 该步骤仅仅是标记下GC Roots能直接关系到的对象,速度很快。

                2.并发标记:该阶段就是进行GC Roots Tracing的过程

                3.重新标记:该阶段还需要Stop The World  。 该步骤就是标记下在并发标记阶段用户产生新的垃圾,该阶段比初始阶段要长,但远

比并发标记阶段要短

                4.并发清除:该阶段就是清除那些被标记的对象。

    该收集器的缺点: 
    
                1.对CPU要求比较高。低于四个时,并发标记和并发清除时间就会特别长。默认的回收线程是(cpu的个数+3)/4

                2.CMS无法处理浮动垃圾(就是在并发清除阶段产生的垃圾)。所以只能等下次垃圾回收才能清理掉。因此需要预留一部分空间给清除所用。JDK1.5默认是68%,JDK1.6提升到了92%。如果预留的空间不足,此次的回收就是失败的,就会启动预备方案,临时启用Serial Old收集器来回收老年代的垃圾,这样停顿的时间就很长了。可以通过参数-XX:CMSInitiatingOccupancyFraction来设置达到百分之几时才会收集。

                3.因为采用的是标记清除算法,自然的就会有比较多的空间随便,因此会有比较多的碎片,因此会相对频繁的触发FGC。对此JVM给出了解决方案,就是通过参数-XX:+UseCMSCompactAtFullCollection来设置是否开启随便整理。如果开启了或多或少就增长GC的时间。因此还可以通过一个参数(-XX:CMSFullGCsBeforeCompaction)设置GC几次了来整理一次老年代空间。默认的是0.就是每次都是要整理碎片。

    G1收集器:
            
            1.也是并行与并发的,并不需要Stop The World ,相比CMS他对CPU的利用率更高;

            2.分代收集 : 虽然大致上也是分新生代和老年代,但是他主要的分区还是把堆内存平均的分成N个区域

            3.空间整合:他采用的是标记-整理的方法  。这样就不会有碎片的空间出现,也就不会提前触发一次FGC。

            4.可控制的回收时间。有2个参数。指定一个长度为M毫秒的时间长度中不超过N毫秒的时间用来垃圾收集

PS:理解GC日志

        有2个日志例子: 

                1.  33.125:  [GC  [DefNew: 3324K -> 152K(3217K), 0.0025925 secs]    3324L->152K(11904K),  0.0031680  secs]

                2.  100.667: [Full   GC   [Tenured:  0K - >210K(10240)K,  0.0149142  secs]  4603K -> 210K(19456K),  [Perm : 2999K -> 2999K (21248K)], 0.0150007  secs]   [Times: user=0.01  sys=0.00 ,  real=0.02 secs ]


                前面的 “ 33.125” 和 “ 100.667” 表示的是GC的时间。 
                GC日志开头的 ”[ GC “ 和 “ [Full GC” 是用来标识垃圾收集的停顿类型 不是用来区分是新生代的GC 还是 老年代的GC 。 如果有Full 说明这次的停顿类型是Stop The World 的。
                接下来的 “[DefNew “ 和 “[Tenured” 则表示的是GC发生的区域,并且和使用的收集器有关。DefNew 表示的 default new Generation则标识的是Serial
                接下来的 “: 3324K -> 152K(3217K),” 标识的“GC前该内存区域已使用的容量 -> GC后该内存区域已使用的容量(该内存区域的总容量)  括号外面指的是堆容量相关的信息,  

5.元空间区

    替代了JDK8之前Perm区(永久代)。永久代之所以会被淘汰是因为他的大小是固定的(通过-XX:MaxPermSize来设置 单位是M),而且很难优化。容易OOM。

    元空间区:原先在Perm区中的字符串常量移至堆内存。其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间中

6.codeCache(JIT编译产物)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值