JVM的基本的模块用途?(小白篇)

一,目录?
0-1:JVM结构模型
0-2:JVM模型内存
1.Java内存分区:方法区,虚拟机栈,堆,本地方法栈
2.对象存活判断的两种方法(可达性算法,引用计数法)
3.垃圾
收集方法4.标记 - 清除,标记 - 整理,复制算法
5.JMM私有内存模型
6.java类加载过程
7.双亲委派机制,类类让父类去找,找不到让子类去找
8.回收策略


二,解析?说明?

在上个目录中,我们初步了解了JVM的结构模型和内存模型,知道了JVM的内存区扥为方法区,虚拟机栈,本地方法栈,堆四个模块。但是对于JVM的每个模块的功能我们并不知道他是用来做什么的,因此接下来按照目录进行介绍!

(1):模块作用英文?

---------方法区:常量池与类文件信息之间的联系?

我们都知道?当类文件被加载进方法区后,指令寄存器就会将目标指向主要方法之中,以执行主要方法,那么主要的方法作为程序的入口,应该如何做呢〜

java类从加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括,加载,验证,准备,解析,初始化  ,卸载,总共七个阶段。其中验证,准备,解析  统称为连接方法区做的则是加载-验证-准备的过程,而主要被调用时做的实例化则是解析过程,初始化是赋值的过程,卸载是销毁的过程!

当文件以二进制字节码类形式存放在方法区中,类里面是存在着一个静态常量池的区域用于保存字面量(字符串,基本类型的常量),以及符号引用(字符串引用,而是存储字符串在常量池里的索引)!

在JVM内存结构中,我们说到类文件里面包含的就是类的基本信息,类型信息,变量信息,字段信息,类加载器与引用,方法表中对各类方法的引用缓存等。那么这部分除却变量等无法确定的信息外,便是存放在“class file”的静态常量池子中!

 方法区是如何工作的呢?

当我们以某种方式把想要执行的类传给JVM后,JVM则会开始调用指令寄存器,激活主()方法。main()的第一条指令告知JVM为列在常量池第一项的类分配足够的内存,然后JVM根据该常量池的指令找到指向该类的指针或者引用,通过此去找到class类!此处是在类文件的静态常量池中查找,如果没有找到则说明JVM还没有将类文件加载进来,那么JVM便会开始查找类文件,找到后进行加载!

JVM通过main()方法找到JVM文件信息,进行准备验证后,开始加载到堆内存中,进行实例化的这个过程就是常量池解析。这个时候是会将静态常量池中的数据给迁移(复制)到运行时常量池中,并将静态常量池符号引用更新为直接引用,一个直接指向方法区的Java类的指针替换了常量池第一项的符号引用,以后JVM则可以通过该引用直接找到类了。

====>常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

当JVM找到这个类的时候,则会通过该直接引用找到该类,读取类的信息,然后计算类对象的大小,在堆中为自己分配一定的空间!

注意:认真读完图的人会知道,上图存在着一个矛盾点,为什么存在着在jvm还没有加载xxxxclass类的情况下,找到指向主方法的指令并执行,在发现没加载xxxclass再去弥补。其原因是JVM为了提高效率而使用了懒加载!Java的在没找到的java类的时候就已经开始执行了〜。看到此处会有一个误区,下意识地认为我们把方法区中的字节码类文件信息全部加载到常量池中,其实是错误的。我们放入常量池中的只是该类文件中的一部分可提前编译好的信息。在实例化到堆中的时候,主体依旧是以class类文件进行实例化,最终在堆中生成对象!

注意:方法区中存储的是类被编译后的信息,常量和静态变量动态常量池里面的数据是从类静态常量池里面的数据转移过去的,当转移到动态常量池中去的时候,由于在静态常量池的一部分字面量(如静态方法,实例构造方法,父类方法)是不允许重写的,所以原本在静态常量池中对字面量的是用符号引用来指向的,当转移动态到池常量会时为转变直接引用!而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。

举个实际例子:上线部署时候,如果修改到静态常量,那么在单独部署class类上去的时候,tomcat更新后仍然是失效的。原因就是静态常量使用了直接引用。

特点:

1.被所有线程共享

2. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中!

3.方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译(class)后的代码等数据。

4.比较少发生GC回收,主要是针对常量池子和类型的卸载

-------

堆内存是干嘛的?

堆内存主要是用来存放数组,集合和对象。在方法区中经过常量池解析后,分配空间之后jvm便会在堆中依据class文件生成对象!堆随着JVM的启动而启动,并且有良好的垃圾回收机制,并且是动态增长的。

 

堆内存根据对象的生命周期进行了分代划分,分为年轻代与年老代两大块,而年轻代里面又按照8:1:1划分为三个区域!

堆内存作为存储时的区域,自然有一套自己的管理机制,对失效的对象进行清除(垃圾回收),以确保数据的顺利运行,这套机制类似于我们的仓储管理,需要清理过期的货物,整理空间,登记有效的货物等! 以此类推,那么在学习堆内存的过程中首先便有两个问题存在了:一是JVM如何识别标记出已经死亡的对象,二是JVM如何对死亡的对象进行回收!

以下分问题分析:

<1>JVM如何识别对象是否已经死亡?
当前主流的JVM主要是依赖于两种基础算法,引用计数法和可达性算法。

引用计数法,顾名思义,就是给每一个对象在创建之初便设置一个引用计数器,每有一个地方引用便计数一次,引用地方减少一次则计数减一次,以此类推,当计数统计为零时,则说明该对象已经死亡可以被回收!引用计数法看起来很完美,但存在着一个问题,对于循环互相引用造成无法回收的 问题并不能很好解决。举个例子,比如A引用B,B引用A,两者的计数器便永远不会归零,当这样的引用存在越多,则会造成内存泄漏,而JVM又无法回收!

可达性分析法,望文生义,就是寻找与该对象相关的对象作为根引用(GC Root)。如果一个对象到根引用没有存在着任何引用关联,那么则说明对象是死亡的,可以被回收!  根对象并不是唯一存在的,他可以是栈(虚拟机栈,本地方法栈)里面的引用,也可以是方法区(静态常量池,运行时常量池)里面的对象。

 <2>JVM如何对死亡的对象进行回收?

       满足以上算法要求的对象并不会被马上回收,而是进入一个"等待确认"的阶段。每一个对象都需要经一次标记一次确认才可以被回收!当一个对象被可达性算法分析为不可达的时候,则会进行一次标记,并且再进行一次 “判断” ,分析是否有必要执行finalize().如果没有覆盖finalize()方法或者已经被虚拟机执行过的,则认为没必要清除。如果有必要清除则,进入等待确认状态,将该对象丢入f-quree对列中,由jvm自动发起一个线程,不定时清理已经被标记的对象!

需要注意的是:这个线程优先级是很低的,JVM不会承诺一直等待他标记确认完才执行完整地清除。因为在这个过程中如果finalize()因为代码或数据问题发生死锁或者CPU负载太高等原因导致执行缓慢,那么这个时候对列便会一直等待下去,这样的结果会导致队列阻塞!因此对于该队列,线程是采用不定时对标记的对象进行回收的方法!(GC)

那么JVM是如何进行标记的呢?

1.标记-清除?

垃圾回收算法中的基础,大多数JVM早期的用法!其基本思路就是对那些那些需要回收的对象进行标记,然后执行清除。但由于没有执行整理,容易导致堆内存碎片化,可能因此导致在分配大对象空间的时候,因为没有完整的足够空间而触发GC。另外一点就是标记和清除效率都比较低

2.复制算法?

为了减少标记-清除所带来的零碎化问题,所以采用了复制算法,其原理如下。比较早期是jvm是将堆内存划分为相等的两部分,每次只是用其中一块内存,当被使用的内存满了的时候,进行标记,将存活的对象给复制到另外一块内存,一次性清理掉满的内存,再将第二款内存的存活对象给复制回去。

这样做的结果是解决了零碎化但是,却浪费另外一半的内存,并且反复复制也压低了效率!在一段时间后边有人对算法进行了改进!

3.复制算法改进?

堆内存中,将内存安照8:1:1的比例进行划分,也就是现在的年轻代中的划分,一块比例为8的E区,两块比例为1的F区和T区。我们知道对象都是在年轻代中创建的,当E满的时候,则将E中存活的对象复制到F或者T区中其中一个,然后清除E区,如果F或T区不够用,则分配给年老代区域,据说用的是分配担保机制!如此反复。依次类推,第二次E区满的时候,则将E区和(T区和F区中的一个)中存活的放到(F区或T区中的另外一个),如果不满则放到年老代中,依次类推,F区和T区总有一个是空的,用于回转数据。当年老代也满的时候则执行FullGC,一般情况下执行全面GC的几率很低,特别是想电商一类的网站,通过优化业务逻辑等手段几乎可以控制此现象的发生!

4.标记-整理?

该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

5.分代收集?

现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。

6.JVM自我策略?

对象的创建大部分都是在年轻代中创建的,但是大对象和长期存活的对象是会被直接放入年老代的。在年轻代中因为新生和死亡的对象非常多,因此GC的次数也是比较高的,周期短,回收速度快是年轻代的特点,多是Minor GC。而在年老代一般都比较少会发生GC!一般情况下年老代中的对象是不会那么容易死亡的,大部分都是经过年轻代的“考验”留下来, 在T或F区熬过大概15次GC,可以说是老而弥新!因此FullGG几乎不会发生,只有在年老代也完全满了之后才会被触发(标记-整理,标记清除),并且每一次FullGc的时间要比年轻代的minor Gc要长得多,因此周期长,速度慢是年老代的特点!


存在这一部分人会将堆内存划分为年轻代,年老代,与持久代,持久代其实就是本文指的方法区,可以说是内存的一部分!而JVM规范中说到的持久代可以执行GC,但是对于大部分并没有实现,原因就是在于方法区的回收效率要远远低于年轻代的回收效率!故存在一部分JVM并不实现!

JVM堆内存中对于垃圾回收,除了垃圾回收算法还有垃圾回收方式野人需要关注,也就是我们常说的串性垃圾回收器和并行垃圾回收器,此处在JDK5.0后允许系统自动判断选择,但如果需要学习调优,必须对此有所深入!

PS:当你能对JVM有整体的认识并能开始调优,那么你就算是(●—●)了


虚拟机栈?

我们常说的堆栈中的栈就是虚拟机栈,她是我们系统中另外一个重点存储区域!我们常说一个对象是具有某种特定行为和属性的个体。而正是堆栈的存在,才导致了对象的完美诞生。堆是存储时单位,复制了对象数据的存储。而栈是运行时单位,复制了数据逻辑的运行!

========>栈的特点以稳定的形态向上增长,他就像一个水桶,在不考虑水的流动性的情况下,数据也是先进后出!每执行一个方法,则为其生成一个栈!

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值