程序员人生 网站导航

自动内存管理机制

栏目:php教程时间:2016-06-29 18:06:54

1:Java内存区域与内存溢出异常

       在运行Java程序时,Java虚拟机会把管理的内存划分为若干个不同的数据区域。

Java虚拟机运行时数据区

数据区域图中,除方法区和堆区是线程同享区外,其他3个是线程隔离的数据区(private)

程序计数器(Program Counter Register):属于线程私有的,占用的内存空间较少,可以看成是当前线程所履行字节码的行号唆使器,字节码解释器工作时就是通过改变这个计数器的值来选择下1条,需要履行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能需要依赖这个计数器来完成,这个区域是jvm规范中没有规定任何OutOfMemoryError情况区域。

虚拟机栈:和程序计数器1样,都属于线程私有,生命周期与线程相同,描写的是java方法履行的内存模型,每一个方法履行都会创建1个栈帧,用于存储局部变量表,操作栈,动态链接,方法出口等信息,每个方法被调用直至履行完成的进程,就对应1个栈帧在jvm stack 从入栈到出栈的进程.局部变量表寄存了编译期可知的各种数据基本类型(Boolean,byte,char,short,int,float,long,double),和对象的援用。这个区域中定义了2种异常情况,如果线程要求的栈深度大于jvm所允许的深度,将抛出StackOverflowError异常,如果jvm可以动态扩大,当扩大没法申请到足够的内存空间是会抛出OutOfMemoryError异常。(这些数据区域异常将在下面的例子都讲到)。

         本地方法栈:与虚拟机栈比较相似。其区分:虚拟机栈为虚拟机履行Java方法服务,而本地方法栈则为虚拟机使用Native方法服务。

         堆(Heap):jvm中内存占用最大的1块,是所有线程同享的1块内存区域.在jvm启动时创建,寄存的是所有对象实例(或数组),所有的对象实例都在这里进行动态分配,当类空间没法再扩大会抛出OutOfMemoryError异常。Java堆是垃圾搜集器管理的主要区域,而搜集器采取分代搜集算法。

         方法区(Method Area):与堆类似,也是各个线程同享的内存区域,主要用来存储加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,当方法区没法满足内存分配时,也抛出OutOfMemoryError异常。运行经常量池是方法区的1部份,用于寄存编译期生成的各种字面量和符号援用相对Class文件常量池的重要特点是具有动态性(常量并不是强迫编译期产生,运行期间也能够新增,例如String类的intern()方法)。

         直接内存(DirectMemort):其实不属于数据区,也不属于java定义的内存区域。由于NIO(New Input/Output)类,引入了1种基于通道与缓冲区(Buffer)的I/O方式。

对象访问

Object object = new Object(); 

Object object 这部份存储在java栈的本地变量表中,作为1个援用(reference)类型存在。

new Object() 这部份存储在java堆中,构成了1块存储了Object类型所有的实例数据值的结构化内存,动态变化,长度不固定。

方法区:在java堆中,必须要找到此对象类型数据,比如,对象类型,基类,实现的接口,方法等都寄存在方法区。

       对象访问方式有两种:句柄和直接指针。

              句柄:reference中存储是对象的句柄地址,而句柄包括了对象实例数据和类型数据各自具体地址信息。好处:在对象移动时只需改变句柄中的实例数据指针,reference本身不需要修改。

              直接指针:reference中直接存储的就是对象地址。好处:速度快,它节省了1次指针定位的时间开消。

实战:OutOfMemoryError异常

1.      Java堆溢出

调剂虚拟机最小值(-Xms)和最大值(-Xmx),并通过参数-XX:+HeapDumpOnOutOfMemoryError生成快照。要解决这个区域的异常,通过内存映像分析工具对快照分析,确认内存中的对象是不是是必要的,分清楚出现了内存泄漏还是内存溢出。若是内存泄漏,通过工具查看泄漏对象到GCRoots援用链,找到泄漏对象是通过怎样的路径与GCRoots相干联并致使垃圾搜集器没法自动回收。若不存在泄漏,则检查虚拟机堆参数与机器物理内存对照看是不是还能调大或从代码上检查某些对象生命周期是不是太长,尝试减少程序运行期的内存消耗。

2.      虚拟机栈和本地方法栈溢出

调理栈容量大小(-Xss)。如果线程要求的栈深度大于虚拟机所允许的最大深度,将会抛出StackOverflowError异常。使用-Xss参数减小栈内存容量或增加此方法帧中本地变量表的程度都使栈深度缩小。

3.      运行经常量池溢出

调理参数-XX:PermSize和-XX:MaxPermSize限制方法区的大小,然后使用String.intern()这个Native方法向常量池中添加内容。运行经常量池溢出,在OutOfMemoryError后面跟随提示信息是“PermGen space”,说明运行经常量池属于方法区(HotSpot虚拟机的永久代)的1部份。

4.      方法区溢出

一样使用参数-XX:PermSize和-XX:MaxPermSize限制方法区的大小,然后不断产生大量的class来加载到内存,从而出现OutOfMemoryError。所以在常常动态生成大量Class的利用中,需要特别注意类的回收状态。

5.      本机直接内存溢出

通过参数-XX:MaxDirectMemorySize指定DirectMemory容量,若不指定则与Java堆最大值1样。可以直接通过反射获得Unsafe实例并进行内存分配,使用unsafe.allocateMemory()申请分配内存。不足时会出现OutOfMemoryError。


2.垃圾搜集器与内存分配策略

概述

       Java内存运行时区域的各个部份,其中程序计数器、VM栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的帧随着方法进入、退出而有条不紊的进行着出栈入栈操作。而Java堆和方法区(包括运行经常量池)则不1样,我们必须等到程序实际运行期间才能知道会创建哪些对象,这部份内存的分配和回收都是动态的。

判断对象已死

       1)援用计数算法(对象中添加1个援用计数器,当有1个地方援用它,计数器加1,当援用失效,计数器减1,任什么时候刻计数器为0的对象就是不可能再被使用的),但援用计数算法没法解决对象循环援用的问题。

       根搜索算法(通过1系列的称为“GCRoots”的点作为起始进行向下搜索,当1个对象到GCRoots没有任何援用链(ReferenceChain)相连,则证明此对象是不可用的),主流程序语言Java,c#都使用此算法。Java语言中,GC Roots包括:  

1.VM栈(帧中的本地变量)中的援用。
2.方法区中的静态援用 和常量援用的对象。
3.JNI(即1般说的Native方法)中的援用。

2)生存还是死亡? 

       判定1个对象死亡,最少经历两次标记进程:如果对象在进行根搜索后,发现没有与GC Roots相连接的援用链,那它将会被第1次标记,并在稍后履行他的finalize()方法(如果它有的话)。这里所谓的履行是指虚拟机会触发这个方法,但其实不许诺会等待它运行结束。这点是必须的,否则1个对象在finalize()方法履行缓慢,乃至有死循环甚么的将会很容易致使全部系统崩溃。 finalize()方法是对象最后1次逃脱死逃亡运的机会,稍后GC将进行第2次范围稍小的标记,如果在finalize()中对象成功解救自己(只要重新建立到GC Roots的连接便可,比方把自己赋值到某个援用上),那在第2次标记时它将被移除出行将回收的集合,如果对象这时候候还没有逃脱,那基本上它就真的离死不远了。 需要关闭外部资源之类的事情,基本上它能做的使用try-finally可以做的更好。      

3)回收方法区

       方法区即后文提到的永久代,很多人认为永久代是没有GC的,这区GC性价比1般比较低:在堆中,特别是在新生代,进行1次GC可以1般可以回收70%~95%的空间,而永久代的GC效力远小于此。但是目前方法区主要回收两部份内容:废弃常量与无用类。需要满足下面3个条件:  
1.该类所有的实例都已被GC,也就是JVM中不存在该Class的任何实例。  
2.加载该类的ClassLoader已被GC  
3.该类对应的java.lang.Class对象没有在任何地方被援用,如不能在任何地方通过反射访问该类的方法。

垃圾搜集算法

1.标记-清除算法(Mark-Sweep

算法分成标记清除两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象。主要缺点有两个,1是效力问题,标记和清算两个进程效力都不高,2是空间问题,标记清算以后会产生大量不连续的内存碎片,空间碎片太多可能会致使后续使用中没法找到足够的连续内存而提早触发另外一次的垃圾搜集动作。

2.复制算法(Copying

将内存分为1块较大的eden空间和2块较少的survivor空间,每次使用eden和其中1块survivor,当回收时将edensurvivor还存活的对象1次过拷贝到另外1块survivor空间上,然后清算掉eden和用过的survivor。复制搜集算法在对象存活率高的时候,效力有所降落。

3.标记-整理(Mark-Compact)算法

标记进程依然1样,但后续步骤不是进行直接清算,而是令所有存活的对象1端移动,然后直接清算掉这端边界之外的内存。

4.分代搜集(Generational Collection)算法

此算法只是根据对象不同的存活周期将内存划分为几块。1般是把Java堆分作新生代和老年代,这样就能够根据各个年代的特点采取最适当的搜集算法。

垃圾搜集器

没有最好的搜集器,也没有万能的搜集器,只有最适合的搜集器。

1.Serial搜集器  
单线程搜集器,搜集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用复制搜集算法,虚拟机运行在Client模式时的默许新生代搜集器。  

2.ParNew搜集器  
ParNew搜集器就是Serial的多线程版本,除使用多条搜集线程外,其余行动包括算法、STW、对象分配规则、回收策略等都与Serial搜集器1摸1样。对应的这类搜集器是虚拟机运行在Server模式的默许新生代搜集器,在单CPU的环境中,ParNew搜集器其实不会比Serial搜集器有更好的效果。  

3.Parallel Scavenge搜集器  
Parallel Scavenge搜集器(下称PS搜集器)也是1个多线程搜集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew搜集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的搜集器实现,它允许较长时间的STW换取总吞吐量最大化。  

4.Serial Old搜集器  
Serial Old是单线程搜集器,使用标记-整理算法,是老年代的搜集器,上面3种都是使用在新生代搜集器。  

5.Parallel Old搜集器  
老年代版本吞吐量优先搜集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS搜集器的话,老年代除Serial Old外别无选择,由于PS没法与CMS搜集器配合工作。  

6.CMSConcurrent Mark Sweep)搜集器  
CMS是1种以最短停顿时间为目标的搜集器,使用CMS其实不能到达GC效力最高(整体GC时间最小),但它能尽量下降GC时服务的停顿时间,这1点对实时或高交互性利用(比方证券交易)来讲相当重要,这类利用对长时间STW1般是不可容忍的。CMS搜集器使用的是标记-清除算法,也就是说它在运行期间会产生空间碎片,所以虚拟机提供了参数开启CMS搜集结束后再进行1次内存紧缩。

内存分配与回收策略 

       分析实验数据与结果。

总结

       GC在很多时候都是系统并发度的决定性因素,虚拟机之所以提供多种不同的搜集器,提供大量的调理参数,是由于只有根据实际利用需求、实现方式选择最优的搜集方式才能获得最好的性能。没有固定搜集器、参数组合,也没有最优的调优方法,虚拟机也没有甚么必定的行动。


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

概述

       给1个系统问题定位问题的时候,知识、经验是关键基础,数据是根据,工具是应用知识处理数据的手段。这里说的数据包括:运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore)、堆转储快照(headdump/hprof)等。常常使用适当的虚拟机监控和分析的工具可以加快我们分析数据和定位解决问题的速度。

JDK的命令行工具

       jdk的命令行工具都放置在jdk/bin目录下,其中包括了我们很熟习的javajavac等工具。这些工具大多都是jdk/lib/tools.jar类库的1层包装而已,它们真实的主要功能都是在tools类库中实现的。下面将介绍几个经常使用的虚拟机监控工具。

1.jps: JVM Process Status Tool ,显示指定系统内所有的HotSpot虚拟机进程

可以列出正在运行的虚拟机进程,并显示虚拟机履行主类的名称,和这些进程的本地虚拟机的唯1ID(LVMIDLocal Virtual Machine Identifier)。虽然功能单1,但是它是使用最频繁的工具。LVMID与系统中的进程IDPID)是1样的。如果同时启动了多个虚拟机进程,没法根据进程名称定位时,那就只能依赖jps命令显示主类的功能才能辨别。

    命令格式:jps [option] [hostid]

履行样例:C:\Documents andSettings\Administrator>jps –lmv

2. jstat: JVM Statistics Monitoring Tool, 用于搜集HotSpot虚拟机各方面的运行数据

用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾搜集、JIT编译等运行数据,它是将运行期定位虚拟机性能问题的首选工具。

命令格式:jstat [option vmid [interval [s|ms] [count]]]

履行样例:C:\Documents andSettings\Administrator>jstat -gc 6820 1000 3代表在进程6820,查询间隔1000毫秒,次数3,查询参数为-gc

3. jinfo: Configuration Info for Java , 显示虚拟机配置信息

jinfo的作用是实时地查看和调剂虚拟机的各项参数。使用jps命令的-v参数可以查看虚拟机启动时显示指定的参数列表,在JDK1.6以后,jinfo还加入了运行期修改参数的能力,可使用-flag [+|-] name  -flag name=value

     命令格式:jinfo [option] pid

4. jmap: Memeory Map for Java, 生成虚拟机的内存转储快照(headdump文件)

jmap1般用于生成堆转储快照。固然jmap的作用也不单单是为了获得dump文件,它还可以查询finalize履行队列,Java堆和永久代得详细信息,如空间使用率、当前使用搜集器等。

      命令格式:jmap [option] vmid

5.jstack: Stack Trace for Java,显示虚拟机的线程快照

       此命令用于生成虚拟机当前时刻的线程快照。它就是当前虚拟机内每条线程正在履行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的缘由:线程间死锁、死循环、要求外部资源致使的长时间等待等。

       命令格式:jstack [option] vmid

6.JConsoleJava监视与管理控制台

JConsole是1个基于JMX的GUI工具,用于连接正在运行的JVM,不过此JVM需要使用可管理的模式启动。如果要把1个利用以可管理的情势启动,可以在启动是设置com.sun.management.jmxremote。除此以外,还可以用JConsole监控tomacat。

JConsole可以以3种方式连接正在运行的JVM:

  • Local:使用JConsole连接1个正在本地系统运行的JVM,并且履行程序的和运行JConsole的需要是同1个用户。JConsole使用文件系统的授权通过RMI连接器连接到平台的MBean服务器上。这类从本地连接的监控能力只有Sun的JDK具有
  • Remote:使用下面的URL通过RMI连接器连接到1个JMX代理:
    service:jmx:rmi:///jndi/rmi://hostName:portNum/jmxrmi。hostName填入主机名称,portNum为JMX代理启动时指定的端口。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials来指定用户名和密码从而进行授权。
  • Advanced:使用1个特殊的URL连接JMX代理。1般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或是1个使用JDK1.4的实现了JMX和JMX Rmote的利用。

当JConsole成功建立连接,它从连接上的JMX代理处获得信息,并且以下面几个标签页显现信息。

  • Summary tab. 监控JVM和1些监控变量的信息。
  • Memory tab. 内存使用信息
  • Threads tab. 线程使用信息
  • Classes tab. 类调用信息
  • VM tab. JVM的信息
MBeans tab.所有MBeans的信息
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐