zoukankan      html  css  js  c++  java
  • java虚拟机总结

    java虚拟机总结

      世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本身就是一件不断追求完美的事情。

    1、java内存区域与内存溢出异常

      运行时数据区域包括方法区(method area),虚拟机栈(VM stack),本地方法栈(native method stack),(heap),程序计数器(program counter register)

      1.程序计数器

        1.是一块较小的内存空间,可以看成是当前线程执行的字节码的行号。在虚拟机的概念模型中,字节码解释器工作就是通过改变这个计数器的值去选取下一条要执行的字节码指令的,分支,循环,跳转,异常处理,线程恢复等功能都依赖这个计数器。

        2.为了线程能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,这类内存称之为线程私有内存。 

        3.如果线程执行的是一个java方法,计数器记录的是执行的虚拟机字节码的地址;如果是native方法,计数器的值为空(undefined)。此内存区也是java虚拟机中唯一一个没有规定任何OutOfMemoryError情况的区域

      2.Java虚拟机栈

        1.线程私有,生命周期与线程同步,虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧(stack frame)用于存储局部变量,操作栈,动态链接,方法出口等信息。每一个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机中入栈出栈的过程。

        2.局部变量表存放了编译期可知的各种基本数据类型,(Boolean ,byte,char,short,int,double,long,float),对象引用returnaddress类型。其中64位长度的long和double类型会占用2个局部变量空间(slot),其余只占一个,局部变量所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量的大小。

        3.java虚拟机中,对该区域定义了两种异常:

          1.如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError

          2.如果虚拟机可以动态拓展,当拓展无法申请到足够的内存会抛出OutOfMemoryError

      3.本地方法栈

        1.与虚拟机栈非常相似,区别是虚拟机栈执行的是java方法(字节码)服务,而本地方法栈则是虚拟机使用到的native方法

      4.java堆:

        1.java堆是Java虚拟机管理的内存中最大的一块。java堆是被线程共享的,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例几乎所有的对象实例都是在这里分配内存

        2.Java堆是垃圾回收器管理的主要区域,因此也被称为GC堆。从内存回收的角度,由于现在的收集器基本上都采用分代收集算法,所以java堆还可以细分为:新生代和老年代;再细致点有Eden区,from survivor区,to survivor区。如果从内存分配的角度来说,线程共享的java堆中可能分出多个线程私有的分配缓冲区不管如何划分,都与存放内存无关,无论哪个区域,存放的都是对象实例,进一步划分是为了更好的回收内存,或者更快的分配内存

        3.java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。主流虚拟机都是按照可扩展实现的(通过-Xmx和-Xms控制)。如果堆中内存没有内存完成实例分配,并且再也无法拓展,将会抛出OutOfMemoryError。

      5.方法区

        1.方法区与java堆一样,是线程共享区,用于存储已被虚拟机加载的类信息,常量,静态变量,及时编译器变异后的代码等数据。别名non-heap,目的是与堆区分开来。

        2.习惯HotSpot虚拟机的开发人员把方法区称之为永久代,本质上两者并不等价,仅仅是因为Hotspot设计团队选择把gc分代收集扩展至方法区,或者说使用永久代实现方法区而已。对于其他虚拟机来书是不存在永久代的概念。

        3.java虚拟机规范对这个区域的限制非常宽松,除了和java堆中一样不需要连续内存和可以选择固定的大小和可扩展外,还可以选择不实现垃圾回收。相对而言,垃圾回收在这个区域比较少见,但并非可以永久存在。这个区域中内存回收的主要目标是针对常量池的回收和针对类型的卸载,一般比较困难,特别是类型的卸载,条件苛刻,但是确实有必要。

        4.当方法区无法满足内存分配需求时,将抛出OutOfMemoryError

      6.运行时常量池

        1.是方法区的一部分。Class文件中除了类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分将在类加载后存放到方法区的运行时常量池

        2.对于运行时常量池,java虚拟机规范中没做任何细节的要求。一般除了保存class文件中描述的符号引用,还会把翻译出来的直接引用也存储在运行时常量池中。

        3.相对于class文件常量池的另外一重要特征是具备动态性。java并不要求常量一定只能在编译期产生,运行期间也可能将新的常量放入池中,这种特性被开发用的较多的就是string类的intern方法

        4.当常量池无法申请到内存时会抛出OutOfMemoryError

      7.直接内存:

        1.并不是虚拟机运行数据区的一部分,也不是java虚拟机规范中定义的内存,但是这块内存被频繁使用,也会导致OutOfMemoryError

        2.jdk1.4新加入NIO类,引入一种基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后存在一个java堆里的DirectByteBuffer对象作为这块内存引用来进行操作。在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据

        3.显然,本机直接内存分配不会受到java堆的大小限制,但是既然是内存,则肯定会受到本机总内存大小和处理器寻址空间的限制服务器管理员配置虚拟机参数时,往往会忽略掉直接内存,使得整个内存区域大小大于物理内存限制,导致动态扩展时出现OutOfMemoryError。

    2、对象访问

      1.Object obj = new Object();Object obj 这部分语义将会反映到java栈的本地变量表,作为一个引用类型数据出现,而new Object()部分语义将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存长度不固定。另外在java堆中还必须包含能够查找到此类对象类型数据的地址信息,这些类型数据则存放在方法区

      2.由于引用类型在java虚拟机规范中只规定了指定对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到java堆中的对象的具体位置,主流 访问方式有两种:使用句柄和直接指针

        1.句柄:java堆会划分一块内存作为句柄池,引用中存储的对象就是句柄地址,而句柄包含了对象实例数据和类型各自的地址信息,如图

         2.直接指针访问:java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,引用中直接存储的就是对象地址

    如图

        3.这两种访问方式各有优势,使用句柄的最大好处就是引用中存储的是稳健的句柄地址,在对象被移动时会改变句柄中的实例数据指针,而引用本省不需要被修改

        4.使用直接指针的最大好处是,节省了一次指针的时间开销,由于对象的访问在java中非常频繁,因此此类开销积少成多也是非常可观的执行成本。就HotSpot而言,它使用的是第二种方式进行对象访问的,就整个开发环境而言,各种语言都有使用。

      

    3、OutOfMemoryError

      1.Java堆溢出:只要不断的创建对象,并且保证GC root到对象之间有可达的路径来避免垃圾回收机制来清除这些对象,就会在对象数量达到堆的最大容量的时候产生内存溢出。

      2.将堆的最小值-Xms和最大值-Xmx设置为一样即可避免堆的自动拓展,通过-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出时Dump出当前内存堆转储存快照以便事后分析。

      3.要解决这个区域的异常,一般是通过内存映射分析工具对dump出来的堆快照进行分析,确认内存中的对象是否有必要存在,也就是先分清楚是到底出现的是内存泄露还是内存溢出。

        1.内存泄漏:通过工具查看泄漏对象到GC roots引用链。就能找到泄漏的对象是通过怎样的路径与GC Roots相关联并导致垃圾回收器无法自动回收的,掌握了泄漏对象的类型信息,以及GCroots引用链信息,就可以比较准确地定位泄漏代码的位置

        2.如果不存在泄漏,换句话说就是内存中的对象都还活着,那就应该检查虚拟机的堆参数设置(-Xms和-Xmx),与物理内存相比看是否还能继续调大,在代码上检查是否有某些对象生命周期过长,持续时间过长的情况,尝试减少程序运行期的内存消耗。

    4、虚拟机栈和本地方法栈溢出

      1.由于hotspot虚拟机不区分虚拟机栈和本地方法栈,所以-Xoss参数无效,栈容量只有-Xss参数设定。

      2.在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,都会抛出StackOverflowError

      3.但是,建立过多的线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆内存和减少栈容量来换取更多的线程。

    5、运行时常量池溢出

      1.如果要向运行时常量池添加内容,最简单的方法就是使用String.intern()方法。作用是:如果池中已经包含等于此String对象的字符串,则返回代表池中的这个String对象;否则将此String对象包含的字符串添加到常量池,并返回这个String的引用。由于常量池在方法区内分配,可以 通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而限制常量池的容量。

    6、方法区溢出

      1.方法区存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。

      2.方法区溢出也是常见的内存溢出异常,一个类如果要被垃圾回收器回收掉,判定条件是非常苛刻的。

    7、本机直接内存溢出

      1.DirectMemory容量可以通过-XX:MaxDirectMemory指定,如果不指定,则默认与java堆的最大值一样(-Xmx)。

      2.虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但他抛出异常时并没有向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常。

    8、垃圾回收器与内存分配策略

      1.对象已死:堆中几乎存放所有的对象实例,垃圾回收器在对堆进行回收前,第一件事就是要确定这些对象那些还活着,那些已经死去

      2.如何判断对象是否存活:

        1.引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器+1,引用失效时,计数器-1;任何时刻计数器为0的对象就是不可能再被使用。但是java语言中没有选择引用计数法来管理内存,主要原因是它很难解决对象之间相互循环引用的问题

        2.根搜索算法:商用程序语言中都是使用根搜索算法来判断对象是否存活的。基本思路就是,通过一系列名为GC roots的对象作为起始点,从这些节点向下搜索,搜索所走过的路径叫做引用链,当一个对象到GCroots没有任何引用链相连时(即从GCroots到这个对象不可达),则证明对象是不可用的。

      3.在java语言中,可以作为GCroots对象的包括以下:

        1.java虚拟机栈中的引用的对象

        2.方法区中的类静态属性引用的对象

        3.方法区中常量引用的对象

        4.本地方法栈中JNI的引用的对象

    9、引用

      1.引用的定义:jdk1.2之前对引用的定义,如果reference类型的数据存储的数值代表的是另外一块内存的起始地址,就成这块内存代表着一个引用。

      2.jdk1.2以后java对引用概念做了扩充。将引用分为强引用,软引用,弱引用,虚引用四种。

        1.强引用:类似于Object obj = new Object()。只要强引用还存在,垃圾回收器永远不会回收掉引用的对象

        2.软引用:描述一些还有用,但非必须的对象。对于软引用关联的对象,在系统将要发生内存溢出之前,将会把这些对象列入回收范围,并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出OOM。

        3.弱引用:描述一些非必须对象的,但是强度比软引用更弱,被弱引用关联的对象只能生存岛下一次垃圾回收前。当垃圾回收器工作时,无论内存是否充足都会被回收掉引用得对象。

        4.虚引用:也称幽灵引用或者幻影引用。是最弱的一种引用。一个对象是否有虚引用完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例。设立虚引用的唯一目的就是希望在这个对象被回收时得到一个系统通知

    10、判断一个对象真的死亡过程

      1.判断一个对象死亡至少要经过两次标记过程

        1.第一次:对象在进行根搜算法后发现,没有与GCRoots相连接的引用链,那这个对象将会被第一次标记,并进行一次筛选。筛选的条件是该对象是否有必要执行finalize()方法。当对象没有覆盖finalize方法,或则finalize方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

        2.如果这个对象被判断有必要执行finalize方法,那么这个对象将会被放入一个名为F-queue的队列中。并稍后由一条由虚拟机自动创建的低优先级finalizer线程去执行。这里的执行是指虚拟机会触发这个方法,但不承诺等待它运行结束。原因是,对象的finalize方法执行缓慢或者发生死循环将导致F-queue队列的其他对象永久处于等待状态,导致整个内存奔崩溃

        3.第二次:finalize方法是对象逃脱死亡的最后一次机会,稍后GC将对F-queue中的对象进行小规模的第二次标记,如果对象要在finalize方法中成功拯救自己,只要重新与引用链的任何对象建立连接即可,例如把自己复赋值给某个类变量或者是对象的成员变量,那么第二次标记时他将会被移除即将回收的集合。

      2.finalize能做的所有工作,使用try-catch或者其他方式都能做的更好更及时,完全可以忘掉finalize方法。

    11、回收方法区

      1.方法区进行垃圾回收的性价比一般比较低在堆中,尤其是新生代,常规应用一次垃圾回收一般可以回收70%~95%的空间,而永久代的垃圾回收效率远低于此

      2.永久代的垃圾回收主要回收两部分内容:废弃常量和无用的类

        1.废弃常量:假如一个字符串“ABC”进入常量池,但是当前系统中没有任何string对象叫做“ABC”,如果发生垃圾回收,且必要的话,这个字符串将会被回收。

        2.废弃的类:类需要满足以下三个条件才能算是无用的类

          1.该类的所有实例都已经被回收,即java堆中不存在任何该类的实例

          2.该类的ClassLoader已经被回收

          3.该类对应的Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

        3.满足以上三个条件仅仅是可以回收,而不是和对象一样,不使用了就必然被回收。是否回收类,HotSpot虚拟机提供-Xnoclassgc参数进行控制。

        4.在大量使用反射,动态代理,CGLib等框架的场景,都需要虚拟机具备卸载功能,保证永久代不会溢出

    12、垃圾回收算法

      1.标记-清除算法:算法分为两个阶段,标记和清除

        1.标记阶段:首先标记所需要回收的对象

        2.清除阶段:标记完成后统一回收所有被标记的对象

        3.标记清除算法的缺点:

          1.效率问题:标记和清除过程的效率都不高

          2.空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能导致程序在后面需要分配较大内存时无法找到足够的连续内存从而触发GC

      2.复制算法:将可用内存分为容量相等的两块AB,每次只使用A。当A用完了,就将其中存活的对象B,再把A的内存空间一次清理掉。优点是不用考虑内存碎片的问题,简单高效,缺点是代价昂贵,只能使用一半的内存。现代很多商业虚拟机采用这种办法回收新生代,研究表明,新生代中对象98%都是朝生夕死,所以不需要按照1:1划分内存,而是将内存分为较大的一块Eden区和两块较小的survivor区,比例为8:1:1。当回收时,将Eden和survivorFrom区还存活的对象一次拷贝到survivorTo区,最后一次清理掉survivorFrom的空间。当survivorTo空间不够时,需要依赖其他内存进行分配担保

      3.标记整理算法:根据老年代的特点,有人提出标记整理算法,标记过程同标记清除算法,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

      4.分代收集思想:根据对象的存活周期的不同将内存划分为几块。一般把java堆分为新生代和老年代。

        1.新生代:每次GC都会有大批对象死去,就是用复制算法。只需要付出少量存活对象的复制成本就可以完成收集

        2.老年代:对象存活率高,没有额外的空间进行分配担保,必须使用标记清除或者标记整理算法。

    13、垃圾回收器:内存回收的具体实现(基于sunhotpot虚拟机),只有最适合的收集器没有万能的收集器

      1.Serial收集器单线程新生代收集器,在GC时,必须暂停其他所有的工作线程(stop the world)。优点是,简单而高效(和其他收集器的单线程比),对于限定单个CPU来说,由于没有线程的互相开销,专心做垃圾回收自然可以获得最高的单线程收集效率

      2.ParNew收集器Serial收集器的多线程版,除了使用多线程进行垃圾回收。目前只有他能与CMS收集器配合使用

      3.Parallel Scavenge收集器:也是一个新生代收集器,也是使用复制算法,并行多线程收集。Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。即CPU用于运行用户代码的时间与CPU总消耗的时间的比值,即吞吐量=运行代码时间/(运行代码时间+GC时间)。停顿时间越短越适合需要与用户交互的程序,良好的响应速度能提升用户的体验。Parallel Scavenge收集器提供了两个参数用于精准控制吞吐量:

        1.-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间:允许一个大于0的毫秒数,收集器将尽力保证内存回收的时间不超过设定值。GC停顿时间的缩短是以牺牲吞吐量和新声代空间来换取的,意思是收集300M肯定比收集500M快,但收集的频率变多了

        2.-XX:GCTimeRatio:直接设置吞吐量的大小

      4.Serial Old 收集器

        1.Serial Old收集器是Serial收集器的老年代版本,同样是单线程收集器,使用标记整理算法。在server模式下,主要有两大用途:一是在jdk1.5之前的版本与Parallel Scavenge收集器搭配使用,另外一个作用就是作为CMS收集器的预备方案。

      5.Parallel Old收集器:

        1.Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。

        2.在Parallel Old收集器出现前,新生代的Parallel Scavenge收集器一直比较尴尬,因为,Parallel Scavenge收集器无法与CMS收集器配合工作,只能选择Serial Old收集器,而由于Serial Old收集器在服务器端性能上的拖累,即便使用了Parallel Scavenge收集器,也未必能在整体性能上获得吞吐量最大化的效果,这种组合甚至还不如ParNew 加CMS组合给力

        3.直到Parallel Old收集器出现后,吞吐量优先的收集器才有了名副其实的组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器加Parallel Old收集器。

      6.CMS收集器(Concurrent Mark Sweep):

        1.收集器是一种已获得最短回收停顿时间为目标的收集器。尤其是互联网网站希望获得系统停顿最短时间,以便提供给用户较好的体验。CMS非常符合这种需求。

        2.基于标记清除算法实现,他的运作分为四个步骤:

          1.初始标记

          2.并发标记

          3.重新标记

          4.并发清除

        3.初始标记,重新标记需要stop the world。初始阶段仅仅只是标记一下GCroots能关联的对象,速度很快,并发标记阶段就是进行GCroots能直接关联到的对象,速度很快,并发标记阶段就是进行GCroots tracing过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致的标记变化的那一部分对象的标记记录。

        4.由于整个过程耗时最长的并发标记和并发清除过程中,收集线程都可以与用户线程一起工作,所以整体上CMS收集器是与用户线程一起并发执行的

        5.CMS最主要优点是并发收集,低停顿

        6.CMS有三个显著缺点

          1.CMS收集器对CPU资源非常敏感。其实面向并发设计的程序都对CPU资源比较敏感。并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程或者说CPU资源而导致程序变慢,总吞吐量降低。CMS默认启动回收线程数(CPUCore+3)/4,即当CPU在4个以上时,并发回收时垃圾收集线程最多不超过25%的CPU资源。当CPU不足4个时,那个CMS对用户程序的影响就可能很大。为了解决这个情况,虚拟机提供一种叫做增量式并发收集器的CMS收集器的变种,就是在并发标记和并发清理的时候让GC线程,用户线程交替运行,尽量减少GC线程的独占时间,这样整个垃圾回收过程会更长,但对用户的影响就会显得少一点。

          2.CMS收集器无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而导致另外一次Full GC 。由于CMS并发清理阶段用户线程还在运行,就会产生新的垃圾。这些垃圾出现在标记过程之后,CMS无法在本次收集处理,只好留到下次在处理。这一部分垃圾就叫做浮动垃圾。要是CMS运行期间的预留内存无法满足程序需要,就会出现Concurrent Mode Failure,这时虚拟机会启动预备方案,临时启动Serial Old收集器进行老年代的垃圾收集,这样停顿的时间就很长了

          3.CMS是基于标记清除算法的收集器,所以收集结束时会产生大量的空间碎片。

    CMS提供一个-XX:+UseCMSCompactAtFullCollection开关,用于享受完FullGC后,免费附赠一个碎片整理过程,内存整理的过程是无法并发的。空间碎片问题没有了,但停顿的时间变长了。

      7.G1收集器

        1.G1收集器是垃圾回收器理论进一步发展的产物,有两个明显的改进:

          1.G1收集器是基于标记整理算法实现的收集器,也就是不会产生空间碎片

          2.可以精确的控制停顿,既能让使用者明确指定在一个长度为毫秒的时间片段内,消耗在垃圾收集的时间不超过N毫秒,这几乎已经是实时java的垃圾收集器的特征。

        2.G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,这是由于他能够极力的避免全区域的垃圾收集,G1将整个Java堆分为多个大小固定的独立区域,并跟踪这些区域的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的手机时间,优先收集垃圾最多的区域。(Garbage First 的由来)

    14、垃圾收集器参数总结

      1.UseSerialGC :运行Client的默认值,打开后,使用Serial+Serial Old组合收集器

      2.UseParNewGC :使用ParNew + Serial Old 组合收集器

      3.UseConcMarkSweepGC:使用ParNew+CMS+SerialOld组合,Serial Old为备份

      4.UseParallelGC :使用Parallel Scavenge+Serial Old的收集组合进行内存回收

      5.UseParallelOldGC:使用Parallel Scavenge +Parallel Old

      6.SurvivorRatio:新生代中Eden与survivor的比例:默认为8

      7.PretenureSizeThrehold:直接晋升老年代的对象大小,超过这个大小的将直接在老年代分配

      8.MaxTenureThreshold:晋升到老年代的对象年龄,每个对象在坚持过一次minorGC后,年龄加1,超过这个值就会进入老年代

      9.UseAdaptiveSizePolicy:动态调整Java堆各区域的大小进入老年代

      10.HandlePromotionFailure:是否允许分配担保失败,即老年代剩余空间不足以应付新生代的整个Eden和survivor区的所有对象的极端情况

      11.ParallelGCTreads:设置并行GC时的内存回收线程数

      12.。。。

    15、内存分配与回收策略

      1.对象内存分配,往大方向讲,就是在堆上分配,也可能在栈上分配,主要在新生代的Eden区,如果启动了本地线程缓冲,将按照线程优先在Tlab上分配。少数可能直接在老年代分配。分配规则并不固定,主要取决于当前使用的垃圾回收器的组合,以及内存相关参数的设置有关。

      2.对象优先在Eden区分配。当Eden区没有足够的空间进行分配时,虚拟机将会发起一次minorGC

      3.虚拟机提供-XX:+PrintGCDetails参数,告诉虚拟机在发生垃圾回收时打印内存回收日志。

      4.新生代minorGC:指发生在新生代Eden区的垃圾回收动作,Minor回收非常频繁,一般回收速度也比较快

      5.老年代MajorGC(Full GC):发生在老年代,经常伴随至少一次MinorGC,并非绝对,FullGC速度一般比minorGC慢10倍以上。

      6.大对象直接进入老年代:大对象值指的是,需要大量连续内存空间的Java对象。-XX:PretenureSizeThreshold,设置大于这个值的对象直接在老年代分配。这样做的墓地是避免在Eden区和两个survivor区发生大量的内存拷贝。

      7.长期存活的对象将进入老年代虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden区出生并经过第一次minorGC仍然存活,并能被survivor容纳,将被移动带survivor区,并将对象年龄加1.对象在survivor区每熬过一次minorGC,年龄加1,当到达一定年龄时(默认15),就会晋升到老年代。

      8.动态年龄判定:为了更好的适应不同程序的内存状况,虚拟机并不总是要求对象年龄必须达到MaxTenuringThreshold才能晋升,如果在survivor区相同年龄的所有对象大小总是大于survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代

      9.空间分配担保:

        1.在发生minorGC时,虚拟机检测每次晋升到老年代的平均年龄是否大于老年代的剩余大小,如果大于,直接进行一次Fu'llGC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败:如果允许,那么只会进行minorGC,如果不允许,则进行FullGC。

         2.当出现大量对象在minorGC后仍然存活时,就需要老年代进行分配担保,让survivor区无法容纳的对象直接进入老年代。

        3.老年代要进行这样的担保,前提是老年代还有容纳这些对象的空间,一共有多少对象活下来,在实际完成GC前无法明确,所以只好取每一次回收晋升到老年代的对象容量的平均值作为经验值,与老年代的剩余空间比较,决定是否进行FullGC。

    16、虚拟机性能监控与故障处理工具

      1.jps:显示指定系统内所有HotSpot虚拟机进程,虚拟机执行主类 函数的名称,进程本地虚拟机的唯一ID

        jps [option] [hostid]

        1.-q:只输出LVMID

        2.-m:输出传给主类的参数

        3.-l:主类的全名,或jar包路径

        4.-v:JVM参数

      2.jstat:用于手机Hotspot虚拟机各个方面的运行参数,显示本地或远程进程中的类装载,内存,垃圾回收,JIT编译等运行数据

        jstat [option vmid [interval s/ms [count]]] //interval和count代表查询间隔和次数

      3.jinfo:显示虚拟机配置信息,实时查看和调整虚拟机的各项参数

        jinfo [option] pid

      4.jmap:生成虚拟机的内存存储快照

      5.jhat:用于分析heapdump文件,会建立一个http/html服务器,让用户可以在浏览器查看结果

      6.jstack:显示虚拟机的线程快照

      7.jdk的可视化工具:JConsoleVisualVM:多合一故障处理工具

     

    17、类加载机制

      1.类从被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期包括:加载,验证,准备,解析,初始化,使用,和卸载七个阶段。其中验证,准备和解析统称为连接。

      2.加载,验证,准备,初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析不一定,他可以在初始化之后在开始,这是为了支持java的运行时绑定(动态绑定)。

      3.什么时候开始类加载的第一个阶段:加载。虚拟机规范没有进行约束,可以由虚拟机具体实现自由把握。但是初始化阶段,虚拟机严格规定了只有四种情况必须对类进行初始化(而加载,验证,准备自然需要在之前完成):

        1.遇到new,getstatic,putstatic,或者invokestatic时,如果类没有初始化过,则需要触发其初始化。

        2.使用java.lang.reflact包方法对类进行反射调用时,如果类还没有初始化过,则需要触发其初始化。

        3.当初始化一个类时,如果法相其父类还没有初始化时,则需要先触发其父类的初始化。

        4.当虚拟机启动时,用户需要指定要执行的主类,虚拟机会先初始化主类

      4.类加载过程详细说明:

        1.加载:需要完成三件事

          1.通过一个类的全限定名来获得定义此类的二进制字节流

          2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

          3.在java堆中生成一个代表这个累的java.lang.Class对象,作为方法区这些数据的访问入口。

        2.验证:这一步是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,并不会危害虚拟机的安全。不同的虚拟机对类的验证有所不同大致分为四个阶段的验证:

          1.文件格式验证:主要验证字节流是否符合Class文件格式的规范,并且能被当前虚拟机处理。

          2.元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范,比如这个类是否有父类,父类是否继承了不允许被继承的类等。。。

          3.字节码验证:主要是进行数据流和控制流分析。比如保证任意时刻操作数栈的数据类型都能与指令代码序列配合工作。

          4,符号引用验证:可以看做是对类自身以外的信息进行匹配性的校验,比如符号引用中通过字符串描述的全限定名是否能找到对应的类。

        3.准备:准备阶段是正式为类变量分配内存并设置变量初始值的阶段,这些内存都将在方法区分配。

        4.解析:是虚拟机将常量池内的符号引用替换为直接地址的过程。

          1.符号引用:以一组符号来描述引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。

          2.直接引用:可以是直接指向目标的指针,相对偏移量或者是能够间接定位到目标的句柄。

        5.初始化:是类加载最后一步。

      5.类加载器:完成“通过一个类的全限定名来获取此类的二进制字节流的过程”,实现的代码叫做类加载器。

        1.比较两个类是否相等,只有在这两个类是同一个类加载器加载的前提下才有意义。否则即使这两个类源于同一个class文件,只要他们的类加载器不同,这两个类必定不相等。这里的相等包括类对象的equals方法,isAssignableFrom(),isinstance()方法返回的结果。

      6.双亲委派模型

        1.站在java虚拟机角度,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是用C++实现的,是虚拟机的一部分;另外一种是其他所有类加载器,这些是java语言实现的,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。

        2.从开发角度类加载器可以分为这三类

          1.启动类加载器

          2.扩展类加载器

          3.应用程序类加载器

        3.我们的应用程序都是由这三类类加载器相互配合加载的,如果有必要可以加入自己的类加载器。

        4.双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承的方式实现,都是使用组合关系复用父加载器。

        5.双亲委派模型的工作过程:如果一个类加载器收到类加载请求,他首先不会自己去尝试加载这个类。而是吧这个请求委派给父类加载器去完成,每一个层次的类加载都是如此,因此所有加载的请求都应该传送到顶层的启动类加载器中,只有父加载器反馈无法完成加载请求时,子加载器才会尝试自己加载。

        

  • 相关阅读:
    核函数基础一简单解释
    矩阵的基本性质 之 正规矩阵,矩阵的迹,行列式,伴随矩阵,矩阵的逆,对角矩阵,矩阵求导
    矩阵的基本性质 之 对称矩阵,Hermite矩阵,正交矩阵,酉矩阵
    矩阵的基本性质 之 矩阵加减法,数乘,乘法,转置
    机器学习实战基础(二十七):sklearn中的降维算法PCA和SVD(八)PCA对手写数字数据集的降维
    拉格朗日对偶性
    批处理符号2
    批处理符号1
    set命令
    goto命令
  • 原文地址:https://www.cnblogs.com/yangyanga/p/12881884.html
Copyright © 2011-2022 走看看