JVM的自动内存管理机制一如何划分JVM内存JVM所管理的内存在运行时会被分为这样几个数据区:虚拟机栈区,堆区,方法区,本地方法栈,程序计数器。程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,每条线程都需要有一个独立的程序计数器,各条线程之间程序计数器互不影响,独立存储,是线程隔离的。程序计数器所在的内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。虚拟机栈,线程私有,它的生命周期与线程相同。虚拟机栈区描述的是Java方法执行内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了8种基本数据类型、对象的引用和returnAddress。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。本地方法栈,作用与虚拟机栈区是相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。堆,Java堆,也称GC堆,是最大的一块,是被线程共享的区域,在虚拟机启动时创建。所有类的实例(对象)和数组都是在堆上分配内存的,堆内存由存活和死亡的对象,空闲碎片区组成,对象所占的堆内存是由自动内存管理系统回收。(数组是一种对象)从内存回收角度来看,Java堆还可以细分为新生代和老年代;甚至还可以分为Eden空间、FromSurvivor空间、ToSurvivor空间等。从内存分配角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存中,只要逻辑上连续即可。方法区在JVM中也是一个非常重要的区域,在HotSpot虚拟机上,方法区被称为“永久代”。虽然Java虚拟机规范把方法区描述为堆区的一个逻辑部分,但还是要区分来对待。方法区用于存储已被JVM加载的类信息(包括类的名称、方法信息、字段信息)、类变量(静态变量)、常量、即时编译器编译后的代码等数据。虽然方法区中有些数据是线程隔离的,但是编译器编译后的代码等数据,是线程共享的。除了和Java堆一样不需要连续的内存和可以固定大小或者可扩展外,还可以选择不实现垃圾收集。但不并非方法区就不要内存回收了,方法区的内存回收只要针对常量池的回收和对类型的卸载。运行时常量池,是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。二对象的创建在语言层面上,创建对象通常仅仅是一个new关键字而已。但在虚拟机中,对象的创建过程大致分为以下四步:第一步,检查类加载。虚拟机遇到一条new指令时,首先需要去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。第二步,分配内存。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可完全确定。分配方式大致有两种:指针碰撞和空闲列表。除了考虑如何划分可用空间之外,还需要考虑在并发的情况下的线程安全。解决方案有两种:一种是对分配空间的动作进行同步处理;另外一种本地线程分配缓冲(TLAB)。第三步,内存空间初始化。如果使用TLAB,这一过程可以提前至TLAB分配时进行。第四步,必要的设置。初始化后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。上面的工作完成后,从虚拟机角度来看,一个新的对象已经产生了,但在程序员的角度来看,对象的创建才刚刚开始,init方法还没有执行,所有字段都还为零。所以,一般来说,执行new指令后会接着执行init方法,把对象按照程序员的意愿进行初始化,这样一个可用的对象才算完全产生出来。三对象的内存布局在HotSpot虚拟机中,对象在内存中存储的布局分为3块:对象头、实例数据和对齐填充。对象头包括两部分信息,一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型字段内容。无论是从父类继承下来,还是在子类中定义的,都需要记录。这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中的顺序的影响。对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotspotVM的自动内存管理系统要求对象起始地址必须是8字节的整数倍。四对象的访问定位建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到Java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。句柄访问方式:Java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。指针访问方式:reference变量中直接存储的就是对象的地址,而Java堆对象一部分存储了对象实例数据,另外一部分存储了到对象类型数据的指针。这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就Hotspot虚拟机而言,它使用的是第二种方式(直接指针访问)。五JVM的内存配置参数-XX:+option启用选项-XX:-option不启用选项-XX:option=value将option参数的值设置为value堆设置-Xms:初始堆大小-Xmx:最大堆大小-Xmn:新生代大小。通常为Xmx的1/3或1/4。新生代=Eden+2个Survivor空间。实际可用空间为=Eden+1个Survivor,即90%。-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:PermSize=n永久代(方法区)的初始大小-XX:MaxPermSize=n:设置永久代最大大小-Xss设定栈容量;对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,因为在HotSpot中并不区分虚拟机和本地方法栈。-XX:PretenureSizeThreshold(该设置只对Serial和ParNew收集器生效)可以设置进入老生代的大小限制-XX:MaxTenuringThreshold=n(默认15)垃圾最大年龄如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率该参数只有在串行GC时才有效。收集器设置-XX:+UseSerialGC:设置串行收集器-XX:+UseParallelGC:设置并行收集器-XX:+UseParallelOldGC:设置并行年老代收集器-XX:+UseConcMarkSweepGC:设置并发收集器垃圾回收统计信息-XX:+PrintHeapAtGC打印GC的heap详情-XX:+PrintGCDetails打印GC详情-XX:+PrintGCTimeStamps打印GC时间信息-XX:+PrintTenuringDistribution打印年龄信息等-XX:+HandlePromotionFailure老年代分配担保(trueorfalse)并行收集器设置-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)并发收集器设置-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。其他-XX:PermSize=10M和-XX:MaxPermSize=10M限制方法区大小。-XX:MaxDirectMemorySize=10M指定DirectMemory(直接内存)容量,如果不指定,则默认与JAVA堆最大值(-Xmx指定)一样。-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照(.hprof文件)以便时候进行分析(比如EclipseMemoryAnalysis)。六JVM的堆内存(heap)简单的来说Java的堆内存分为两块:permantspace(持久代/方法区)和heapspace。持久代/方法区:主要存储结构信息的地方,比如方法体,同时也是存储静态变量,以及静态代码块的区域,构造函数,常量池,接口初始化等等。与垃圾收集器要收集的Java对象关系不大。而heapspace分为新生代和年老代。新生代(由一个Eden区和俩个survivor区组成):对象被创建时(new)的对象通常被放在新生代的Eden区(除了一些占据内存比较大的对象直接进老年代),经过一次GC收集后,存活下来的会被复制到survivor区(一个满了,就全部移动到另外一个大的中,但要保证其中一个survivor为空),经过一定的MinorGC(针对新生代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。年老代:就是上述新生代移动过来的和一些比较大的对象。FullGC是针对年老代的回收新生代的垃圾回收叫MinorGC,年老代的垃圾回收叫FullGC。在年轻代中经历了多次垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。为了做到这点,虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden出生并经过一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor空间中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到年老代中。对象晋升年老代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。年老代溢出原因:循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。持久代溢出原因:动态加载了大量Java类而导致溢出。堆大小=新生代+老年代。其中,堆的大小可以通过参数–Xms、-Xmx来指定。默认的,新生代(Young)与老年代(Old)的比例的值为1:2(该值可以通过参数–XX:NewRatio来指定),即:新生代(Young)=1/3的堆空间大小。老年代(Old)=2/3的堆空间大小。其中,新生代(Young)被细分为Eden和两