精华内容
下载资源
问答
  • JVM的运行原理

    2020-02-22 23:52:47
    JVM的运行原理 1、必要性 ​ 2、JVM是源文件(.java)运行在操作系统(语言,windows操作系统语言是c++)的媒介,从main()开始 ​ 3、运行,编译(由jdk/eclipse完成),把.java转成.class文件 ​ 4、类加载器...

    转载自https://blog.csdn.net/csdnliuxin123524/article/details/81303711

     


     

    JVM的运行原理

    目录

    JVM的运行原理

    类加载器

    1、分类

    2、加载机制

    3、一个类加载器的理想化组成

    4、类加载器特性

    5、类加载器的最终成果

    方法区

    1、方法区的内容是每个装载进来的类信息,根据内外部环境划分

    2、类型的常量池。

    3、方法区在虚拟机启动的时候创建。

    4、方法区同样存在垃圾收集

    5、方法区的大小不必是因定的

    虚拟机栈

    本地方法栈

    执行引擎

    JAVA的内存模型

    问题总结


    1、必要性
    ​
    2、JVM是源文件(.java)运行在操作系统(语言,windows操作系统语言是c++)的媒介,从main()开始
    ​
    3、运行,编译(由jdk/eclipse完成),把.java转成.class文件 
    ​
    4、类加载器(classLoader是类加载器,一个是ClassLoader实体类),什么是类加载器,怎么加载的,分类,继承关系,双亲委派模型,最终产物,new()A 的过程
    ​
    5、运行时数据区划分
    ​
    6、方法区,作用,组成,(进程与线程的区别)
    ​
    7、虚拟机栈,特性,组成,入栈出栈过程,栈帧组成,动态链接特性
    ​
    8、堆,特性,GC回收过程
    ​
    9、程序计数器,特性
    ​
    10、本地方法栈,特性
    ​
    11、执行引擎,功能,运行方式(解释器,即时编译器)
    ​
    12、整合实例
    ​
    13、计算机硬件与JVM间的线程关系,线程安全发送原因,注意事项。
    
    
    
    

     

    类加载器

    1、分类

     

    2、加载机制

    双亲委派类加载机制

    1、启动程序,首先做初始化(init)是加载jdk自带的jar包,这部分由bootstarp类加载器完成。然后程序找到main(),创建A对象。创建A类的过程,A.class文件进入到类加载器,首先由默认的类加载器去查看此类加载器有没有已经加载过A类(保证一个类文件只加载一次),这个默认的类加载器就是Application ClassLoader。如果Application 没有加载过A类,那就到它的上级类加载器查看有没有加载过,也就是ExtClassLoader.如果没有就到bootStrap类加载查看有没有加载过。如果还没有 ,就到bootStrap负责加载的jar包中找没有此A类,如果找不到,就到下级ExtClassLoader查找有没有此A类.如果没有就到AppClassLoader查找有没有A类。如果还没有就到user类加载器中找A类。如果还没有就报classNotFoundException。
    (总结,两个过程,一是自下而上的找没有加载过,二是自上而下的去加载。)

    3、一个类加载器的理想化组成

     

    4、类加载器特性

    1、不止一个种类,各种类完成各自的分内工作,也就是各加载各自的jar包。
    2、不止一个类加载器,好多个
    3、可以自定义类加载器

    5、类加载器的最终成果

    把加载的类放到方法区里面,并在堆里面生成相应的实体。

     

    方法区

    1、方法区的内容是每个装载进来的类信息,根据内外部环境划分

    外部信息:
    1、类型全名(包名+类名)
    2、类型的父类型的全名
    3、给类型是一个类还是接口
    4、类型的修饰符
    5、所有父接口全名的列表
    ​
    内部信息:
    1、类型的常量池
    2、类型字段信息
    3、类型的方法信息
    4、所有的静态类变量(非常量)信息
    5、一个指向类加载的引用
    6、一个指向class类的引用
    ​

    从上面的分类可以看出方法区内存放的类信息进行了分类统一处理

    2、类型的常量池。

     

    在工作学习中我们会听到过很多常量池,字符串常量池,运行时常量池,这里又来个类型的常量池。其实这些常量池的作用和使用原理都是一样的, 不用太过区分。
    ​
    其使用目的:
    1、为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
    ​
    2、类型的常量池(也叫class 常虽池)用于存放编译器生成的各种字面量(uteral)和符号引用(Symbolic References);

    什么是字面量和符号引用?

    字面量包括: 
    1.文本字符串
    2.八种基本类型杂值
    3.被声明为final的常量等;
    ​
    符号引用包括: 
    1.类和方法的全限定名
    2.字段的名称和描述符
    3.方法的名称和描述符。
    常量池就是这个类型用到的常量的一个有序集合, 池中的数据像数组项- 样,是通过索引访问的。 因为常量池存储了一个类型所使用到的所有类型,域和方法的符号引用,所以它在java程序 的动态链接中起了核心的作用。其实存储和访问方式同string常量池。每个class文件都有一个class常量池。

    3、方法区在虚拟机启动的时候创建。

    所有jvm线程共享,也就是不随启动的线程数的变化而变化,而且必须考虑数据的线程安全。假如两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只应该有一个线程去加载, 而另一个线程等待。

     

    4、方法区同样存在垃圾收集

    因为通过用户定义的类加载器可以动态扩展java程序,一 些类也会成为垃圾。jvm可以回收一个未被引用类所占的空间,以使方法区的空间最小。

    5、方法区的大小不必是因定的

    jvm可 以根据应用的需要动态调整。如果超出方法区最大内存,会报出java.lang.OutofMemoryError.

    (1)是Java虚拟机所管理的内存中最大的一块。
    (2)堆是jvm所有线程共享的。
    (3)在虚拟机启动的时候创建。
    (4)唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。
    (5) Java堆 是垃圾收集器管理的主要区域。
    (6) java堆是计算机物理存储.上不连续的、逻辑上是连续的,也是大小可调节的(通过Xms和Xmx控制)。
    (7)如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
    (8)因此很多时候java堆也被称为“GC堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代又可以分为: Eden 空间、From Survivor空间、To Survivor空间。见下图:

     

     

    ① 新生区

    新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
    ​
    新生区又分为两部分:
    伊甸区 (Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。
    幸存区有两个:
        0区(Survivor 0 space)和1区(Survivor 1 space)。
        当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园进行垃圾回收(Minor GC),将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1去也满了呢?再移动到养老区。若养老区也满了,那么这个时候 将产生Major GC(FullGCC),进行养老区的内存清理。若养老区执行Full GC 之后发现依然无法进行对象的保存,就会产生OOM异常 “OutOfMemoryError”。

    如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

     a.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
     b.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

    ② 养老区

    养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象(需要经常创建的对象,不用创建和销毁,省去时间,并不产生内存碎片)都在这个区域活跃。

    ③ 永久区

    永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存

    如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。原因有二:

     a. 程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。
     b. 大量动态反射生成的类不断被加载,最终导致Perm区被占满。

    说明:

    Jdk1.6及之前:常量池分配在永久代 。
    Jdk1.7:有,但已经逐步“去永久代” 。
    Jdk1.8及之后:无(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。
    (9)GC具体什么时候执行,这个是由系统来进行决定的,是无法预测的。

    虚拟机栈

    作用:主要用于方法的执行。

    ​
    (1)线程私有的,它的生命周期与线程相同,每个线程都有一个。 
    (2)每个线程创建的同时会创建一个JVM栈,JVM栈中每个栈帧存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型: boolean、char、byte、short、int、long、float、double;和对象引用(reference 32 位以内的数据类型,具体根据JVM位数(64为还是32位)有 关,因为一个solt(槽)占用32位的内存空间 ,64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的基本类型只占用1 个)、部分的返回结果,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址; 
    (3)每一个方法从被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 
    (4)虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。 
    (5)虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作 栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程 
    (5)栈运行原理:栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据 的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,B方法又调用了 C方法,于是产生栈帧F3也被压入栈…… 依次执行完毕后,先弹出后进......F3栈帧,再弹出F2栈帧,再弹出F1栈帧。见下图

     

     

    动态链接根据方法在方法区中存放的位置去找到对应的方法,再将其压入栈中
    上图表示的就是一个线程的虚拟机栈的组成结构,由很多方法栈帧组成,每个栈帧又分为局部变量,操作数帧等几部分组成。执行过程就是先执行main方法,main方法被压入栈底,main方法又调用method1()方法,method1由调用method2,以次类推,当最上面的方法执 行完了就弹出栈。这也就是我们常说的栈是先进后出的方式,也就是桶状模型。 
    (6)对上图中的动态链接解释下,比如当出现main方法需要调用method1()方法的时候,操作指令就会触动这个动态链接就会到方法区中找 method1(),然后把method1()方法压入虚拟机栈中,执行method1栈帧的指令;此外如果指令表示的代码是个常量,这也是个动态链接,也会到 方法区中的运行时常量池找到类加载时就专门存放变量的运行时常量池的数据

    本地方法栈

    (1)先解释什么是本地方法:jvm中的本地方法是指方法的修饰符是带有native的但是方法体不是用java代码写的一类方法,这类方法存在的意义当然是填补java代码不方便实现的缺陷而提出的。 
    (2)作用同java虚拟机栈类似,区别是:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。 
    (3)是线程私有的,它的生命周期与线程相同,每个线程都有一个。
    (4)会报出OutOfMemoryError异常。 
    (5)优势:不用经过执行引擎解释器,可以直接与操作系统交互,在一定程度上加快运行速度。再者是有的任务在java层次不易实现,需要直 接与操作系统交互。 
    (6)缺点:本身java是很灵活的,一次编译,到处运行。但是如果在不同语言编写的操作系统,因此使用了native的程序可移植性不太高。 
    (7)图解与虚拟机栈的交互,如下图

     

     

    native方法是没有方法实体的,形式如下:
        public class IHaveNatives
        {
          native public void Native1( int x ) ;
        } 
    它的方法体是通过动态链接到内存或硬盘中获取的,然后直接在操作系统上运行。
    以windows平台为例,方法体是用c语言编写的,一般以.dll文件格式。
    ​
    本地方法也可以调用java方法。 
    因为其实现体是由非java代码在在外部实现的,不能与abstract连用,他们都是方法的声明,如果同时出现,就相当于即把实现移交给子类,又把实现移交给本地操作系统,那到底谁来实现具体方法呢? 
    (8)为什么native方法修饰的修饰的方法PC程序计数器为undefined。读懂上面的所有知识点可以就很容易自己理解了。在一开始类加载时,native修饰的方法就被保存在了本地方法栈中,当需要调用native方法时,调用的是一个指向本地方法栈中某方法的地址,然后执行方法直接与 操作系统交互,返回运行结果。整个过程并没有经过执行引擎的解释器把字节码解释成操作系统语言,PC计数器也就没有起作用。 
    (9)本地方法一般很少用到,不用做太多了解。

    程序计数器(Program Counter Register)

    也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    (1)计算机硬件中也有个pc寄存器,跟这个功能大致一样,只是所处的位置不同。 
    (2)当虚拟机正在执行的方法是一个本地(native)方法的时候,jvm的pc寄存器存储的值是undefined。 
    (3)程序计数器是线程私有的,它的生命周期与线程相同,每个线程都有一个。 
    (4)此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

    执行引擎

    (1)功能:

    方法区以及栈中的指令还是人能够看懂的,这里执行引擎的工作就是要把指令转成JVM执行的语言(也可以理解成操作系统的语言),最后操作系统语言再转成计算机机器码。

    (2)有两种运行方式

    1、解释器:
    一条一条地读取,解释并且执行字节码指令。因为它一条一条地解释和执行指令,所以它可以很快地解释字节码,但是执行起来会比较慢。这是解释执行的语言的一个缺点。字节码这种“语言”基本来说是解释执行的。
    ​
    2、即时(Just-In-Time)编译器:
    即时编译器被引入用来弥补解释器的缺点。执行引擎首先按照解释执行的方式来执行,然后在合适的时候,即时编译器把整段字节码编译成本地代码。然后,执行引擎就没有必要再去解释执行方法了,它可以直接通过本地代码去执行它。执行本地代码比一条一条进行解释执行的速度快很多。 编译后的代码可以执行的很快,因为本地代码是保存在缓存里的。热点代码,执行次数比较多的代码

     

    (3)程序在JVM主要执行的过程是执行引擎与运行时数据区不断交互的过程,执行引擎拿到的pc寄存器的指令(这里是单个指令,一条一条地执行)地址,到栈 帧中获取指令,计算结果,返回到栈。下图是个经典的交互过程。

     

    首先位于.class文件中,被类加载器加载到方法区,然后main方法启动,运行到foo方法,把foo方法压入到栈帧中也就是上图左侧是foo方法指令集;然后,常数1入栈,栈顶元素就是1,然后栈顶元素移入局部变量区存储,常数2入栈,栈顶元素变为2,然后 栈顶元素移入局部变量区存储;接着1,2依次再次入栈,弹出栈顶两个元素相加后结果入栈,将5入栈,栈顶两个元素弹出并相乘后结果入栈,然后栈顶变为15,最后移入局部变量。执行return命令如果当前线程对应的栈中没有了栈帧,这个Java栈也将会 被JVM撤销。
    ​
    就是在方法区,程序计数器就不用说了,局部变量区位于虚拟机栈中,右侧最下方的求值栈(也就是操作数栈)我们从动图中 明显可以看出存在栈顶这个关键词因此也是位于java虚拟机栈的。
    ​
    另外,图中,指令是Java代码经过javac编译后得到的JVM指令,PC寄存器指向下一条该执行的指令地址,局部变量区存储函数运 行中产生的局部变量,栈存储计算的中间结果和最后结果。

    JAVA的内存模型

    这里主要思考的是线程和线程安全问题

     

    在运行时数据内存区中虚拟机栈、pc寄存器、本地方法栈是每个线程都有的,很明显这些都是独立的不会发生线程不安全的问题,但是我们平时讨论的线程不安全、要加锁等等情况是怎么回事呢?

    答:其实,发生线程不安全问题的原因在于cpu,看下图,简单理解Cpu。见下图

      

    在CPU内部有一组CPU寄存器,也就是CPU的储存器。CPU操作寄存器的速度要比操作计算机主存快的多。在主存和CPU寄存器之间还存在一个CPU缓存,CPU操作CPU缓存的速度快于主存但慢于CPU寄存器。某些CPU可能有多个缓存层(一级缓存和二级缓存)。计算机的主存也称作RAM,所有的CPU都能够访问主存,而且主存比上面提到的缓存和寄存器大很多。 
    ​
    当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某些节点把缓存数据flush到主存。

    Java内存模型和硬件架构之间的桥接

    正如上面讲到的,Java内存模型和硬件内存架构并不一致。硬件内存架构中并没有区分栈和堆,从硬件上看,不管是栈还是堆,大部分数据都 会存到主存中,当然一部分栈和堆的数据也有可能会存到CPU寄存器中,如下图所示,Java内存模型和计算机硬件内存架构是一个交叉关系:

     

     

    当对象和变量存储到计算机的各个内存区域时,必然会面临一些问题,其中最主要的两个问题是:

    1、共享对象对各个线程的可见性
    2、 共享对象的竞争现象 

    问题1:共享对象的可见性

    当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronization关键字,一个线程对共享对象的更新有可能导致其它线 程不可见。
    想象一下我们的共享对象存储在主存,一个CPU中的线程读取主存数据到CPU缓存,然后对共享对象做了更改,但CPU缓存中的更改后的对象还 没有flush到主存,此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象,而且拷贝的对象位 于不同的CPU缓存中。
    ​
    下图展示了上面描述的过程。左边CPU中运行的线程从主存中拷贝共享对象obj到它的CPU缓存,把对象obj的count变量改为2。但这个变更对运 行在右边CPU中的线程不可见,因为这个更改还没有flush到主存中

     

     

    要解决共享对象可见性这个问题,我们可以使用java volatile关键字。 Java’s volatile keyword. volatile 关键字可以保证变量会直接从主存读取,而对变量的更新也会直接写到主存。volatile原理是基于CPU内存屏障指令实现的。

    问题2:竞争现象

    如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。 
    如下图所示,线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它 的CPU缓存,并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。
    ​
    如果这两个加1操作是串行执行的,那么Obj.count变量便会在原始值上加2,最终主存中的Obj.count的值会是3。然而下图中两个加1操作是并行 的,不管是线程A还是线程B先flush计算结果到主存,最终主存中的bj.count只会增加1次变成2,尽管一共有两次加1操作。

     

    要解决上面的问题我们可以使用java synchronized代码块。synchronized代码块可以保证同一个时刻只能有一个线程进入代码竞争区, synchronized代码块也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是 不是volatile类型的。

    volatile和 synchronized区别

    volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线 程可以访问该变量,其他线程被阻塞住。
    ​
    volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的 
    volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
    volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。 
    volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

    cpu内部线程处理方式:

    一个核心处理器可以处理两个线程。最重要的是,一个核心的一个线程对jvm的多线程的操作不是一对一,cpu的一个 线程不是只处理一个jvm线程,每隔一段时间(毫秒级甚至微秒级)就会换一个jvm线程去执行,当前线程等待。优点,就是如果一个线程中存 在sleep,就省去了一段等待时间。当然,jvm可以设置线程执行优先级的。

    问题总结

    1,怎么查看一个类的.class文件呢?

    答:写好你的.Java文件后,file---open file,打开所在工程的.bin目录(不同工程有所不同),就是放工程的.class文件,然后打开就行了。 也可以直接ctrl+shift+R搜索文件名,不过右上角要设置成show derived Resources,就能显示,class文件了。最后要注意的是你的eclipse不 能安装了反编译插件哦,不然打开后跟.java文件内容一样了。

    2,方法区存放的是类文件

    (静态文件,比如变量值是不随程序的运行而改变的,一个类只有一个。)在栈帧中运行方法时,需要new A对象,就会在堆中为A开辟空间,创建A对象。栈中是指向堆中A对象的地址。当又new A对象,堆中会再开辟一个空间,创建一个A对象。但是方法区始终只有一个A类文件。这是一对多的关系。

    3,jdk,jre,JVM的关系:

    JDK(Java Development Kit)是Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两 个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和Lib(初始化的jar包)合起来就称为jre。

    4,GC垃圾回收机制

    不是创建的变量为空是就被立刻回收,而是超出变量的作用域后就被自动回收。

    5,java虚拟机的生命周期:

    声明周期起点是当一个java应用main函数启动时虚拟机也同时被启动,而只有当在虚拟机实例中的所有非守护 进程都结束时,java虚拟机实例才结束生命。 

    6,java虚拟机与main方法的关系:

    main函数就是一个java应用的入口,main函数被执行时,java虚拟机就启动了。启动了几个main函数就 启动了几个java应用,同时也启动了几个java的虚拟机。

    7,java的虚拟机种有两种线程

    一种叫叫守护线程(服务员),一种叫非守护线程(也叫普通线程,比作厨师),main函数就是个非守护线程,虚拟机的gc就是一个守护线程。java的虚拟机中,只要有任何非守护线程还没有结束,java虚拟机的实例都不会退出,所以即使main函数这个非守护线程退出,但是由于在main函数中启动的匿名线程也是非守护线程,它还没有结束,所以jvm没办法退出

    8,堆内存大小-Xms -Xmx设置相同,可以避免每次垃圾回收完成后JVM重新分配内存。

    9,我们平时所说的八大基本类型的在栈中的存放位置是:

    运行时数据区-->虚拟机栈-->虚拟机栈的一个栈帧-->栈帧中的局部变量表; 
    ​
    局部变量表存放的数据除了八大基本类型外,还可以存放一个局部变量表的容量的最小单位变量槽(slot)的大小,通常表示为reference; 所以是可以放字符串类型的,但是要以 String a="aa";的形式出现,如果是new Object()那就只能实在堆中了,栈里面存的是栈执行堆的地 址。在执行引擎的图中,只涉及到int a=1;int b=2;并没有String或对象类型的使用,所以也就没有关联到堆。

    10,字符串常量池:

    在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中; 
    在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。 
    JDK8以后也还是放在了Heap空间中,并没有已到元空间。
    常量池的特点是:字符串常量池中的字符串只存在一份!
    String s1 = "hello,world!";
    String s2 = "hello,world!";
    即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返 回给s2。既然在堆地址和值都一样,那么返回给栈去判断s1==s2的结果自然也是相等的了。
    展开全文
  • 你对 JVM 的运行原理了解多少? 我们写的 Java 代码到底是如何运行起来的? 视频参考JVM合集/零基础学习JVM、GC基础知识、GC算法、JVM的垃圾回收器、面试JVM实战、 实战调优​​​​​​​ 想要弄...

      前言 

     

    想要弄清楚这些问题,我们接下来从这三个维度去探讨:

     

    • JVM 和操作系统的关系?

    • JVM、JRE 、JDK的关系?

    • JVM 虚拟机规范和 Java 语言规范的关系?

     

     

    JVM 和操作系统的关系

           

    我们用一句话概括 JVM 与操作系统之间的关系:JVM 上承开发语言,下接操作系统,它的中间接口就是字节码。

     

    而 Java 程序和我们通常使用的 C++ 程序有什么不同呢?这里用两张图进行说明。

                                

    对比这两张图可以看到 C++ 程序是编译成操作系统能够识别的 .exe 文件,而 Java 程序是编译成 JVM 能够识别的 .class 文件,然后由 JVM 负责调用系统函数执行程序。

     

     

    JVM、JRE、JDK的关系

                 

    通过上面的学习我们了解到 JVM 是 Java 程序能够运行的核心。但是需要注意,JVM 自己什么也干不了,你需要给它提供生产原料(.class 文件)。俗语说的好,巧妇难为无米之炊。它虽然功能强大,但仍需要为它提供 .class 文件。

     

    仅仅是 JVM,是无法完成一次编译,处处运行的。它需要一个基本的类库,比如怎么操作文件、怎么连接网络等。而 Java 体系很慷慨,会一次性将 JVM 运行所需的类库都传递给它。JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境,也就是我们常说的 JRE(Java Runtime Environment)。

     

    有了 JRE 之后,我们的 Java 程序便可以在浏览器中运行了。大家可以看一下自己安装的 Java 目录,如果是只需要执行一些 Java 程序,只需要一个 JRE 就足够了。

     

    对于 JDK 来说,就更庞大了一些。除了 JRE,JDK 还提供了一些非常好用的小工具,比如 javac、java、jar 等。它是 Java 开发的核心,让外行也可以炼剑!

     

    我们也可以看下 JDK 的全拼,Java Development Kit 。我非常怕 kit(装备)这个单词,它就像一个无底洞,预示着你永无休止的对它进行研究。JVM、JRE、JDK 它们三者之间的关系,可以用一个包含关系表示。

     

    JDK>JRE>JVM

     

                 

     

     

    JVM 虚拟机规范和 Java 语言规范的关系

     

    我们通常谈到 JVM ,首先会想到它的垃圾回收器,其实它还有很多部分,比如对字节码进行解析的执行引擎等。广义上来讲,JVM 是一种规范,它是最为官方、最为准确的文档;狭义上来讲,由于我们使用 Hotspot 更多一些,我们一般在谈到这个概念时,会将它们等同起来

     

    如果再加上我们平常使用的 Java 语言的话,可以得出下面这样一张图。这是 Java 开发人员必须要搞懂的两个规范。

                  

     

    左半部分是 Java 虚拟机规范,其实就是为输入和执行字节码提供一个运行环境。右半部分是我们常说的 Java 语法规范,比如 switch、for、泛型、lambda 等相关的程序,最终都会编译成字节码。而连接左右两部分的桥梁依然是 Java 的字节码。

     

    如果 .class 文件的规格是不变的,这两部分是可以独立进行优化的。但 Java 也会偶尔扩充一下 .class 文件的格式,增加一些字节码指令,以便支持更多的特性。

     

    我们可以把 Java 虚拟机可以看作是一台抽象的计算机,它有自己的指令集以及各种运行时内存区域,学过《计算机组成结构》的同学会在课程的后面看到非常多的相似性。

     

    那有的同学就说了,我不学习 JVM ,会影响我写 Java 代码么?理论上,这两者没有什么必然的联系。它们之间通过 .class 文件进行交互,即使你不了解 JVM,也能够写大多数的 Java 代码。就像是你写 C++ 代码一样,并不需要特别深入的了解操作系统的底层是如何实现的。

     

    但是,如果你想要写一些比较精巧、效率比较高的代码,就需要了解一些执行层面的知识了。了解 JVM,主要用在调优以及故障排查上面,你会对运行中的各种资源分配,有一个比较全面的掌控。

     

     

    小结

     

     

    搞懂这些,我们再回头看上面的三个问题,自然迎刃而解了。

     

    • 为什么 Java 研发系统需要 JVM?

     

    JVM 解释的是类似于汇编语言的字节码,需要一个抽象的运行时环境。同时,这个虚拟环境也需要解决字节码加载、自动垃圾回收、并发等一系列问题。JVM 其实是一个规范,定义了 .class 文件的结构、加载机制、数据存储、运行时栈等诸多内容,最常用的 JVM 实现就是Hotspot。

     

    • 对你 JVM 的运行原理了解多少?

     

    JVM 的生命周期是和 Java 程序的运行一样的,当程序运行结束,JVM 实例也跟着消失了。JVM 处于整个体系中的核心位置,关于其具体运行原理,我们在下面的分享中详细介绍。

     

    • 我们写的 Java 代码到底是如何运行起来的? 

     

    一个 Java 程序,首先经过 javac 编译成 .class 文件,然后 JVM 将其加载到`元数据`区,执行引擎将会通过`混合模式`执行这些字节码。执行时,会翻译成操作系统相关的函数。JVM 作为 .class 文件的黑盒存在,输入字节码,调用操作系统函数。过程如下:Java 文件->编译器>字节码->JVM->机器码

     

     

    总结

     

    到这里本次分享的内容就全部讲完了,今天我们分别从三个角度,了解了 JVM 在 Java 研发体系中的位置

     

    下一次分享我将分享关于 JVM 内存管理相关的知识,记得持续关注 IT 技术思维哦

    展开全文
  • JVM的运行原理及优化配置运行原理: 当一个URL被访问时,内存申请过程如下: 1. JVM会试图为相关Java对象在Eden中初始化一块内存区域 2. 当Eden空间足够时,内存申请结束。否则到下一步 3. JVM试图释放在Eden中...

    JVM的运行原理及优化配置

    运行原理:
    当一个URL被访问时,内存申请过程如下:
    1. JVM会试图为相关Java对象在Eden中初始化一块内存区域
    2. 当Eden空间足够时,内存申请结束。否则到下一步
    3. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区
    4. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区
    5. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)
    6. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”


    jvm的功能模块:

    • 持久区(Perm)
    • 新生代区(YOUNG) 由1个Eden和2个Survivor组成
    • 旧生代区(OLD)
      结构如下图:
      这里写图片描述

    Java堆相关参数:

    参数名 描述
    ms/mx 定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销
    NewSize/MaxNewSize 定义YOUNG段的尺寸,NewSize为JVM启动时YOUNG的内存大小;MaxNewSize为最大可占用的YOUNG内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销
    PermSize/MaxPermSize 定义Perm段的尺寸,PermSize为JVM启动时Perm的内存大小;MaxPermSize为最大可占用的Perm内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。
    SurvivorRatio 设置Survivor空间和Eden空间的比例,默认1:6

    内存溢出的可能性:
    OLD段溢出
    1) 设置的内存参数过小(ms/mx, NewSize/MaxNewSize)
    2) 程序问题
    程序无限循环或循环次数过多的创建对象,也有可能单个程序所申请内存过大,在创建对象使用完后没有及时释放对象的空间。
    Perm段溢出
    通常由于Perm段装载了大量的Servlet类而导致溢出
    将PermSize扩大,一般256M能够满足要求 ; 若别无选择,则只能将servlet的路径加到CLASSPATH中,但一般不建议这么处理

    Java中四种垃圾回收算法:

    • –XX:+UseSerialGC
    • –XX:+UseParallelGC
    • –XX:+UseParallelOldGC
    • –XX:+UseConcMarkSweepGC

    第一种为单线程GC,也是默认的GC。,该GC适用于单CPU机器。
    第二种为Throughput GC,是多线程的GC,适用于多CPU,使用大量线程的程序。第二种GC与第一种GC相似,不同在于GC在收集Young区是多线程的,但在Old区和第一种一样,仍然采用单线程。-XX:+UseParallelGC参数启动该GC。
    第三种为Concurrent Low Pause GC,类似于第一种,适用于多CPU,并要求缩短因GC造成程序停滞的时间。这种GC可以在Old区的回收同时,运行应用程序。-XX:+UseConcMarkSweepGC参数启动该GC。
    第四种为Incremental Low Pause GC,适用于要求缩短因GC造成程序停滞的时间。这种GC可以在Young区回收的同时,回收一部分Old区对象。-Xincgc参数启动该GC。


    Heap设定与垃圾回收:

    JVM有2个GC线程。第一个线程负责回收Heap的Young区。第二个线程在Heap不足时,遍历Heap,将Young 区升级为Older区。Older区的大小等于-Xmx减去-Xmn,不能将-Xms的值设的过大,因为第二个线程被迫运行会降低JVM的性能。

    为什么一些程序频繁发生GC?有如下原因:
    A 程序内调用了System.gc()或Runtime.gc()。
    B 一些中间件软件调用自己的GC方法,此时需要设置参数禁止这些GC。
    C Java的Heap太小,一般默认的Heap值都很小。
    D 频繁实例化对象,Release对象。此时尽量保存并重用对象,例如使用StringBuffer()和String()。

    如果你发现每次GC后,Heap的剩余空间会是总空间的50%,这表示你的Heap处于健康状态。许多Server端的Java程序每次GC后最好能有65%的剩余空间。
    经验之谈:
    1.Server端JVM最好将-Xms和-Xmx设为相同值。为了优化GC,最好让-Xmn值约等于-Xmx的1/3[2]。
    2.一个GUI程序最好是每10到20秒间运行一次GC,每次在半秒之内完成[2]。
    注意:
    1.增加Heap的大小虽然会降低GC的频率,但也增加了每次GC的时间。并且GC运行时,所有的用户线程将暂停,也就是GC期间,Java应用程序不做任何工作。
    2.Heap大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx定义的值,因为Java为其他任务分配内存,例如每个线程的Stack等。
    3.Stack的设定 每个线程都有他自己的Stack。-Xss用来设置 每个线程的Stack大小。 Stack的大小限制着线程的数量。如果Stack过大就好导致内存溢漏。-Xss参数决定Stack大小,例如-Xss1024K。如果Stack太小,也会导致Stack溢漏。

    展开全文
  • JVM 的运行原理和优化

    2018-03-02 11:04:57
    一、什么是JVM JVM是Java Virtual Machine(Java虚拟机)缩写,JVM是一种用于计算设备规范,它是一个虚构出来计算机,是通过在实际计算机上仿真模拟各种计算机功能来实现。 Java语言一个非常重要...

    一、什么是JVM

        JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

        Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

       从Java平台的逻辑结构上来看,我们可以从下图来了解JVM:

    技术分享

        从上图能清晰看到Java平台包含的各个逻辑模块,也能了解到JDK与JRE的区别,对于JVM自身的物理结构,我们可以从下图鸟瞰一下:

    技术分享


    二、JAVA代码编译和执行过程

    Java代码编译是由Java源码编译器来完成,流程图如下所示:

    技术分享

    Java字节码的执行是由JVM执行引擎来完成,流程图如下所示:

    技术分享

    ava代码编译和执行的整个过程包含了以下三个重要的机制:

    • Java源码编译机制

    • 类加载机制

    • 类执行机制

    Java源码编译机制

    Java 源码编译由以下三个过程组成:

    • 分析和输入到符号表

    • 注解处理

    • 语义分析和生成class文件

    流程图如下所示:

    技术分享


    最后生成的class文件由以下部分组成:

    • 结构信息。包括class文件格式版本号及各部分的数量与大小的信息

    • 元数据。对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池

    • 方法信息。对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息

    类加载机制

    JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

    技术分享

    1)Bootstrap ClassLoader

    负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

    2)Extension ClassLoader

    负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

    3)App ClassLoader

    负责记载classpath中指定的jar包及目录中class

    4)Custom ClassLoader

        属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。


    类执行机制

    JVM是基于栈的体系结构来执行class字节码的。线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。栈的结构如下图所示:

    技术分享


    三、JVM内存管理和垃圾回收

    JVM内存组成结构

    JVM栈由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:

    技术分享

    1)堆

    所有通过new创建的对象的内存都在堆中分配,堆的大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,结构图如下所示:

    技术分享

    • 新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例

    • 旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象

    • 持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,常量池等等。可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其他机制来实现方法区。

    技术分享

    -Xmx:最大堆内存,如:-Xmx512m

    -Xms:初始时堆内存,如:-Xms256m

    -XX:MaxNewSize:最大年轻区内存

    -XX:NewSize:初始时年轻区内存.通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%

    -XX:MaxPermSize:最大持久带内存

    -XX:PermSize:初始时持久带内存

    -XX:+PrintGCDetails。打印 GC 信息

     -XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

     -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10


    2)栈

        每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

       -xss:设置每个线程的堆栈大小. JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

    3)本地方法栈

    用于支持native方法的执行,存储了每个native方法调用的状态

    4)方法区

    存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(Permanet Generation)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值


    垃圾回收按照基本回收策略分

    引用计数(Reference Counting):

    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

     

    标记-清除(Mark-Sweep):

    技术分享

        此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。


    复制(Copying):

    技术分享

        此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。


    标记-整理(Mark-Compact):

    技术分享


        此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。


    JVM分别对新生代和旧生代采用不同的垃圾回收机制

           新生代的GC:

           新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和From Space或To Space之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代。

    在执行机制上JVM提供了串行GC(Serial GC)、并行回收GC(Parallel Scavenge)和并行GC(ParNew)

    1)串行GC

        在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定

    2)并行回收GC

        在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数

    3)并行GC

    与旧生代的并发GC配合使用

    旧生代的GC:

        旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行GC(Serial MSC)、并行GC(parallel MSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。

    以上各种GC机制是需要组合使用的,指定方式由下表所示:

    指定方式

    新生代GC方式

    旧生代GC方式

    -XX:+UseSerialGC

    串行GC

    串行GC

    -XX:+UseParallelGC

    并行回收GC

    并行GC

    -XX:+UseConeMarkSweepGC

    并行GC

    并发GC

    -XX:+UseParNewGC

    并行GC

    串行GC

    -XX:+UseParallelOldGC

    并行回收GC

    并行GC

    -XX:+ UseConeMarkSweepGC

    -XX:+UseParNewGC

    串行GC

    并发GC

    不支持的组合

    1、-XX:+UseParNewGC -XX:+UseParallelOldGC

    2、-XX:+UseParNewGC -XX:+UseSerialGC



    四、JVM内存调优


        首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。

        对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:

    旧生代空间不足
        调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象 

    Pemanet Generation空间不足
        增大Perm Gen空间,避免太多静态对象 

        统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间
        控制好新生代和旧生代的比例 

    System.gc()被显示调用
        垃圾回收不要手动触发,尽量依靠JVM自身的机制 

        调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果

    1)新生代设置过小

        一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC

    2)新生代设置过大

        一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加

        一般说来新生代占整个堆1/3比较合适

    3)Survivor设置过小

        导致对象从eden直接到达旧生代,降低了在新生代的存活时间

    4)Survivor设置过大

        导致eden过小,增加了GC频率

        另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

        由内存管理和垃圾回收可知新生代和旧生代都有多种GC策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM提供两种较为简单的GC策略的设置方式

    1)吞吐量优先

        JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

    2)暂停时间优先

        JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

     

    最后汇总一下JVM常见配置

    堆设置

    -Xms:初始堆大小

    -Xmx:最大堆大小

    -XX:NewSize=n:设置年轻代大小

    -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

    -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

    -XX:MaxPermSize=n:设置持久代大小

    收集器设置

    -XX:+UseSerialGC:设置串行收集器

    -XX:+UseParallelGC:设置并行收集器

    -XX:+UseParalledlOldGC:设置并行年老代收集器

    -XX:+UseConcMarkSweepGC:设置并发收集器

    垃圾回收统计信息

    -XX:+PrintGC

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps

    -Xloggc:filename

    并行收集器设置

    -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

    -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

    -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

    并发收集器设置

    -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

    -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

    展开全文
  • 这里,我们特意来深刻认识一下JVM的工作和运行原理JVM的生命周期产生:当启动一个Java程序时,一个JVM实例就产生了; 运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:...
  • JVM的运行原理以及JDK 7增加的新特性(二) JVM结构 Java编写的代码会按照下图的流程来执行 类装载器装载负责装载编译后的字节码,并加载到运行时数据区(Runtime Data Area),然后执行引擎...
  • 一.Java虚拟机的工作原理: 1.首先Java源文件经过前端编译器(javac或ECJ)将.java文件编译为...然后JRE(类加载器)加载Java字节码文件,载入系统分配给JVM的内存区, 3.虚拟机中的执行引擎用来执行class文...
  • Java,编程语言,被创造于90年代初,在经历了这么...有些工作年限的Java程序员已经是对Java的运行原理了解的很透彻了,本文只为新人带来详细的解析。JVM是Java的核心和基础,是Java编译器和平台之间的虚拟处理器,利...
  • 了解程序的运行原理 要先学会看字节码文件 然后这样才能真正的去看程序是怎么运行的 ,不是说在开发工具 里debug一下就是了解程序运行了 debug不会直接告诉你 两个字符串变量相加 其实底层是new了一个StringBuilder...
  • Java,编程语言,被创造于90年代初,在经历了这么...有些工作年限的Java程序员已经是对Java的运行原理了解的很透彻了,本文只为新人带来详细的解析。JVM是Java的核心和基础,是Java编译器和平台之间的虚拟处理器,利...
  • Java,编程语言,被创造于90年代初,在经历了这么...有些工作年限的Java程序员已经是对Java的运行原理了解的很透彻了,本文只为新人带来详细的解析。JVM是Java的核心和基础,是Java编译器和平台之间的虚拟处理器,利...
  • JVM运行原理详解

    万次阅读 多人点赞 2017-05-31 15:01:45
    作为一名Java使用者,掌握JVM的体系结构也是很有必要的。 说起Java,我们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口...
  • JVM 运行机制及其原理发布时间:2018-05-22 22:15,浏览次数:1074, 标签:JVM最近出去面试,总被问到JavaJVM相关的东西,什么JVM的内存模型、JVM的内存分配、内存回收、内存回收算法…搞得我一头雾水,早些年还看过...
  • 上一节中,我们学习了JVM的基本结构个内存结构(特指运行时数据区结构),本节我们讲学习一下JVM的运行流程,并通过一个实际例子来剖析一下在运行时JVM是如何分配内存结构中各个组成部分工作的。 1、JVM运行原理 ...
  • JVM上执行java字节码,执行时这些字节码可以解释成具体平台机器码,学习JVM运行机制以及原理,会懂得为什么java语言拥有“一次编译,处处运行”这一跨平台能力。什么是JVM呢?JVM是Java VirtualMachine(Java虚拟机)...
  • JVM运行原理

    千次阅读 多人点赞 2016-08-02 21:34:46
    JVM运行原理,Class文件介绍,虚拟机类加载机制,类加载全过程,虚拟机执行引擎
  • jvm运行的原理

    2018-04-13 11:58:22
    https://www.cnblogs.com/zhanglei93/p/6590609.html
  • JVM底层运行原理(上)

    2018-12-31 13:51:16
    jvm是java程序的运行环境,作为一名java程序员,是肯定要理解的。 1.JVM整体介绍 JVM 运行流程 图中的源程序,不仅仅只有java,比如scale等,也可以作为源程序。 java语音之所以是跨平台的,就是因为有JVM。 ...
  • 和电脑运行之间 的关系 Java虚拟机也是面向对象的运行方式 面向对象编程思维 : 用编程解决的实际问题 在解决问题时需要用到工具 如何使用自己创造的工具去一一解决大问题中的小问题. 然后梳理解决问题的逻辑....
  • JAVA和JVM运行的原理

    2014-02-14 20:54:44
    这里和大家简单分享一下JAVA和JVM运行的原理,Java语言写源程序通过Java编译器,编译成与平台无关‘字节码程序’(.class文件,也就是0,1二进制程序),然后在OS之上Java解释器中解释执行,而JVM是java核心和...
  • Jvm运行原理

    2018-09-10 13:12:17
    但是由于JVM对实际的简单开发的来说关联的还是不多,一般工作个一两年(当然不包括爱学习的及专门做性能优化的什么的),很少有人能很好的去学习及理解什么是JVM,以及弄清楚JVM的工作原理,个人认为这块还是非常有...
  • 原标题:浅谈JVM原理概念虚拟机:指以软件方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中完整计算机系统,是物理机软件实现。JVM分类:VMWare ,Visual Box,JVM(其中VMWare和Visual Box都是使用软件...
  • 一:简介在学习Java虚拟机之前,也就是Jvm之前,我想大家能够带着问题去学习,这样话,大家学习起来也会比较有所获!...二:Jvm基础概念Java虚拟机(Jvm)是可运行Java代码假想计算机Java虚拟机包括一...
  • JVM的运行机制和原理

    2018-04-03 15:25:10
    这里和大家简单分享一下JAVA和JVM运行的原理,Java语言写源程序通过Java编译器,编译成与平台无关‘字节码程序’(.class文件,也就是0,1二进制程序),然后在OS之上Java解释器中解释执行,而JVM是java核心和...
  • 文章目录PreJVM和GC的运行原理 Pre 我们已经把完整的JVM运行原理、GC原理以及GC优化的原理,还有线上发生GC问题的各种优化案例分析完了,所以到这里务必停一停脚步,整理一下学习过的知识脉络 。 JVM和GC的运行...
  • Java基本知识 Java方法是如何在JVM里面执行 方法执行原理: 1.方法只有在调用时候,才会在内存中为其开辟空间 2.方法之定义而没有调用话,不会为其开辟内存空间 3.方法在调用时候在“”栈内存“”中为其开辟...
  • JVM 的知识点太散,不系统,今天带大家详细的了解一下jvm的运行原理。 正文 1 什么是JVM? JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码...
  • jvm运行机制原理

    2017-11-04 15:03:34
    运行时数据区(方法区、堆、jvm栈、PC寄存器、运行时常量池、本地方法堆栈 GC就是回收不用数据,清理内存。 工作原理JVM是java核心和基础,在java编译器和os平台之间虚拟处理器。它是一种基于下层操作...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,246
精华内容 2,498
关键字:

jvm的运行原理