写一篇自己总结的JVM

JVM是什么?为什么要学?怎么学最好?

  •  java虚拟机,java代码是我们写的,能被机器识别,是虚拟机帮我们来完成的。
  • 就像人或者就要依赖地球一样,java代码想活着就要依赖JVM虚拟机。至于为什么要学,就像是我生活在地球上有科学家去研究气候,去研究地质,去研究生物一样。人研究地球是为了更好的存活,提高生活质量。那么我们研究JVM的意义就是让我们的代码更高效的有质量的运行。
  •  怎么学这个问题,我的建议是这部分看阿里巴巴出版的《码出高效》这本书的第四章就好了。无疑的是阿里巴巴是中国java阵营的大哥,看看那些大牛是如何来聊JVM虚拟机的。

 如果你之前没有JVM虚拟机的知识,我感觉这篇文章读起来会吃力,就像我没有JVM基础知识的时候,读《实战JVM虚拟机》的时候一样痛苦。坚持下来,痛在前边,收获在后边。《实战JVM虚拟机》这本书也真的不错。如果想系统学习,我推荐这本书。

  不是一定要推荐这本书,我觉得站在巨人的肩膀上是有必要的,我觉得最害怕的就是不如别人,还不向别人学习。

  在《码出高效》这本书中,一共有五部分,想必是他们觉得最重要的部分。我也围绕这几部分展开学习。分别是:字节码;类加载机制;内存布局;对象实例化;垃圾回收。

第一部分:字节码

  这部分我的理解不够充分,但是我可以把我理解的讲出来。上边提到过,代码是我们人写的,那么让机器(硬件)能够认识,就是字节码。也就是说,源文件经历了一个过程到了机器能识别到字节码,这个过程设计编译原理。我就不细说了。

 这整个过程如下:

  感兴趣的自己学习编译原理这门课。不感兴趣的就理解到:我们写的代码,经历了一个过程到了字节码,字节码才是JVM认识的。这个过程成为编译(javac完成的)。为什么学习JVM要说上字节码这个问题呢?简单可以这样理解,我们吃馒头,馒头是由小麦加工过来的,虽然我们吃的是小麦,却是小麦的加工品。从小麦原料到馒头这个加工过程就是编译。馒头相当于是字节码。JVM相当于是人。

第二部分:类加载机制 

 那什么又是类加载机制,为什么要学呢?

 学过操作系统的应该知道,任何处理都是经过CPU来进行的。程序(翻译过的字节码文件 .class)只有加载到内存中才能然CPU处理。字节码文件到内存中去这个过程就叫做类加载。

  学习类加载之前先了解类加载器:类加载过程是有类加载器完成的,整体分为Load,Link,Init 。Load(加载)过程是读取字节码文件,初步校验魔法数,常量池,文件长度,是否有父类,然后创建java.lang.Class的实例。Link(链接)这个过程会检查比如final是否合规,类型是否正确,静态变量是否合理,为静态变量分配内存,设定默认值。并检查类的引用是否正确。最终完成内存结构布局。 Init(初始化)进行赋值,如果这个过程用其他类的静态方法就立刻加载另外一个类。 

  类加载过程图

  

  在类加载过程中会加载静态代码块,执行静态变量赋值语句。

 总结: 我对类加载机制理解到这里,书中还有部分内容没能懂。(会回头再读的)我觉得理解到这里,我们需要知道的是:在类加载过程,分别完成了校验魔法数,常量池,父类,检查一些关键字是否正确,类型是否正确,静态变量是否正确,为静态变量分配内存,赋初始化值。

第三部分:内存布局(别的地方叫做内存模型)

  学过操作系统以后我们知道:我们的资源一般存放在硬盘上,硬盘适合大量的存储,但是慢。CPU虽快,但是造价昂贵,也不能把数据放在CPU上,我们解决方案就是缓冲区的概念。就是加一层缓冲区。这层缓冲区是内存。也就是说,我们CPU要操作的数据先从磁盘上放到内存(缓冲区)中来。

  在JVM中主要就是针对内存的操作,包括内存申请,分配管理,从而保证了JVM的正常运行。

  下边是书中给出的内存模型图,这是我见过的众多中比较好的图

  针对上图一块一块的讲:

  • 1.  堆(Heap):这块是内存中占比最大的,存放着绝大多数的对象的实例。它是OOM的主要发源地。同时它是共享的区域,我们所谓的JVM调优,JVM优化就是优化堆内存。顺便说下这块可以调优的地方,就是根据我们的需要来适当的设置堆内存的初始值和最大值。《码出高效》中有提到:如果初始值和最大值差距很大的话,会出现堆内存持续缩小又扩容的问题,这会带来不必要的服务器压力,所以建议将初始值和最大值设置成一致的。 -Xms:1024M   -Xmx:1024M  前边的是初始值,后边的是最大值。
    • 上图可以看到的是:堆又分为 新生代和老年代,新生代分为伊甸去和两个幸存区(两个幸存区又分别是to和from,他to和from是来回颠倒的),默认比例是eden: to :from = 8:1:1 。这么划分有什么意义:首先创建的对象是要放在eden区的,如果伊甸区达到一定水准:就发生YGC(垃圾回收),回收的才能放在幸存区,这时存放幸存的就叫做from,因为它存放了对象,下次再GC就要将幸存的对象放到另外一个幸存区了,也就是to区(要去的地方,空的地方叫to)。幸存一次就有一个年龄,到达一定年龄就要被安排到养老区了。这个年龄叫做阈值,可以设置。为什么要送到养老区呢:是这样的,这个没有被杀死的是因为一直在被用着,那都杀了好多次了,你看反正杀不死我,我还有用,就把我放到养老区把。 这有引出了养老区,养老区是个比较平静的区域,一直被用的存放在这里。还有特别的对象也放在这里。所谓老年代,就是因为GC不频繁,但是不意味这不发生。
    • 如果觉得我讲的不清楚:看原文
  • 2. 元空间  JDK1.8之前,是固定大小的,叫做永久带,不利于调优被替换掉了。取而代之的是元空间,至于为什么会别替代,类的调用这些信息本来是放在永久带的,如果深度过大,就以为的信息过多,然后会导致OOM错误。此外在永久带的垃圾回收也有很多问题无法解决,然后就废弃掉了,换成了元空间,元空间只直接存放在本地内存中,字符串常量存放在元空间,类的元信息,字段,静态属性,方法,常量都存放在元空间。
  • 3. 虚拟机栈 ,堆管存储,栈管运行。这部分内容是线程私有的,没得优化。会报的错误是stackOverFlow。利用栈这一个特殊的数据结构来完成特殊的任务。每个方法的调用成为一个栈帧,栈帧中存放了一些方法相关的信息。至于都存放了什么信息:
    • 方法调用叫做入栈,方法执行完了叫出栈。
    • 局部变量表又是什么呢:存放这方法参数,以及局部变量。
  •  4.本地方法栈:负责本地方法调用。可以调用别的语言写的方法。不做过多的介绍了
  •  5.程序计数寄存器:就是执行到哪里了
    •   
  • 小总结

~待续

猜你喜欢

转载自blog.csdn.net/star1210644725/article/details/93225701