黑马程序员技术交流社区

标题: 【成都校区】jvm调优 [打印本页]

作者: 一半是快乐    时间: 2019-7-11 17:58
标题: 【成都校区】jvm调优
一、JDK,JRE,JVM区别与联系
        JDK是 Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。





        jdk(JDK是用于java程序的开发,而jre则是只能运行class而没有编译的功能。)

                bin:最主要的是编译器(javac.exe)

                include:java和JVM交互用的头文件

                lib:类库

                jre:java运行环境

        jre= bin+jvm

                JVM并不代表就可以执行class了,JVM执行.class还需要JRE下的lib类库的支持,尤其是rt.jar。

        jvm(Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。)

                java虚拟机(java运行时的环境),通过JDK中的编译程序(javac)将java文件编译成.class字节码文件,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。里面主要包含了jvm和java运行时基本类库(rt.jar)。



二、JVM虚拟机详解
        执行引擎是Java虚拟机最核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。





程序计数器(线程专属)
        指向当前线程正在执行的字节码指令集地址(行号)。

        线程执行指令在操作系统上运行,操作系统在CPU上运行,CPU上的调度策略,时间片。

        一个线程正在执行,当另一个线程同时运行时吗,在CPU层面抢夺时间片,抢夺成功,前一个线程会被挂起。

        当第二个线程执行完毕,则有程序计数器去找到挂起的线程唤醒执行。



虚拟机栈(FILO)(线程专属)->栈->数据结构->存储数据
        支撑线程执行方法的数据结构(一个java方法中运行时需要的数据存放地址)

        基本单位:栈帧(Stack Frame)(局部变量表,操作数栈,动态链接,方法返回地址.....)

        一个方法一个栈帧,一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。



         局部变量表:用于存放方法参数和方法内部定义的局部变量。在编译期由Code属性中的 max_locals确定局部变量表的大小。 局部变量表的容量以变量槽(Variable Slot)为最小单位。

        当前栈帧(Current Stack Frame):一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,与这个栈帧相关联的方法称为当前方法(Current Method)。

        操作栈:操作数栈的最大深度也在编译的时候写入到Code属性max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。

        动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

        将入参转化为指令,通过对应的指令将数据从操作数栈压入局部变量表。(如下图所示)









本地方法栈(线程专属)
        类似虚拟机栈,给java调用本地方法使用的内存。

方法区(线程共享)(永久代)
        存储已被虚拟机加载的类信息、常量、静态变量、及编译器编译后的代码数据。

java堆(heap)(线程共享)
        用来储存java对象,是垃圾回收机制的主要管理区域。

运行时常量池(线程共享)
        Class文件中除了有类的版本、字段、方法、接口的描述信息以外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

直接内存(线程共享)
        本机内存,Java的NIO中用到的内存映射部分用到直接内存,这部分内存不受Jvm的限制,只受机器的内存的限制。



三、JVM虚拟机垃圾回收机制
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

判断对象生死的算法

        引用计数器算法:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失败时,引用计数器就减1。当引用计数器为0时,对象就是不可能被使用的。

        可达性算法分析:引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,当一个对象到GC Roots没有任何引用相连。则证明此对象是不可用的。





        OopMap:保存类型的映射表。(通过OopMap,HotSpot可以快速准确完成GC Roots枚举)

        Safe Point:记录OopMap信息的位置。

                抢先式中断:GC发生时,中断所有线程,如果发现有线程不再安全点上,就恢复线程让它运行到安全点上。现在几乎不用这种方案。

                主动式中断:设置一个标志,和安全点重合,再加上创建对象分配内存的地方。各个线程主动轮询这个标志,发现中断标志为真就挂起自己。HotSpot使用主动式中断。

        Safe Region:在一段代码片段中,引用关系不会发生变化,在该区域的任何地方发生GC都是安全的。



垃圾收集算法

        标记-清除算法:标记出所有需要清理的内存,编辑完成后统一清理。缺点:有可能将内存碎片化,导致内存不那么连续,如果遇到大对象需要分配连续的内存空间,将提前触发垃圾回收。效率较低。

        复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。缺点:内存缩小一半。

        标记-整理算法:标记后以不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。



JVM讨论的需要垃圾回收的区域主要是指堆内存和方法区。

        程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知。因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2