标签:
(1)在java中,分配内存和回收内存都由JVM自动完成,甚至不需要写和内存相关的代码
(2)物理内存即RAM还有寄存器(一种存储单元,用于存储计算机单元执行指令(如整形浮点等运算)的中间结果)是处理器通过地址总线连接的。地址总线:其宽度决定了一次可以存寄存器或者RAM中获取多少个bit和处理器最大的可以寻址的范围,每个地址会引用一个字节,所以如果是32位的总线则可以有4G的内存空间。(通常情况下地址总线和RAM或寄存器有相同的位数)
(3)通常操作系统的内存申请空间是按照进程来管理的,每个进程间不会互相重合,操作系统保证每个进程拥有一段独立的地址空间。(逻辑上独立,物理空间不一定独立,如虚拟内存,虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。)
(4)由于程序越来越庞大何设计的多任务性,物理内存无法满足要求,出现了虚拟内存,虚拟内存使得多个进程可以共享物理内存,并且逻辑上独立。虚拟内存提高了内存利用率,并且可以扩展内存空间,使得一个虚拟的地址可以映射到物理内存,文件或者其他可以寻址的存储上。如一个进程在不活动的情况下,操作系统将这个物理内存中的数据移到一个磁盘文件下(频繁地交换物理内存和磁盘上的数据,会导致效率低下,需要关注)。
(1)电脑的内存地址空间将被划分为内核地址空间和用户空间,程序只能使用用户空间的内存(指程序能够申请的内存)(如windows32为默认内核空间和用户空间的比例是1:1,linux32为默认的比例是1:3)
(2)内核空间主要指操作系统用于程序调度、虚拟内存或者连接硬件资源等的程序逻辑。程序不能访问操作系统的空间,并且不能直接访问硬件资源,必须通过系统提供的接口调用。(每一次系统调用都会引起内核空间和内存空间的切换,这一操作比较耗时)
(1).java堆:用于存储java对象的内存区域,可以通过Xmx(最大大小)和Xms(初始大小)来控制大小,默认空余堆内存少于40%时就扩大到Xmx,空余堆内存大于70%时就缩小到Xms,因此,服务器一般把xmx和xms设置成一样,避免在GC后调节堆的大小。
(2).每个线程创建时,JVM都会为它创建一个运行方法栈、局部变量的堆还有操作栈。
(3).类和类加载器:类和类的加载器本身同样需要存储空间,存储在永久代PermGen(属于方法区,即java堆的永久区部分)
ps:
而JVM的默认类加载器都不满足该条件,所以他们加载的类都不能卸载。
(4)NIO:NIO使用ByteBuffer.allocateDirect()方法分配内存,可以避免数据从内核空间到用户空间的复制,提高效率,但是该方法直接使用的是本机内存而不是java堆内存,直接的ByteBuffer对象可以自动清理本机缓存区,但是其只是作为GC时的一部分执行,而GC只在Java堆被填满或者显示调用System.gc()来执行(也就是自动的GC只检查Java堆是否满,而不知道NIO操作的本机内存是否需要释放。),以至于NIO在很多框架中是通过显示调用System.gc()执行NIO内存的释放的(其实2对象本身有clean方法可以释放,见jdk随笔额,heap&direct)
(5)JNI:JNI使得本机代码(如C语言)可以调用java方法,JVM会准备空间以供运行本地方法,也会增加java运行时的本机内存占用。
JVM是按照运行时数据的存储结构来划分内存结构的,根据不同的格式存储在不同的区域。运行时数据包括java程序本身的数据信息和JVM运行Java程序需要的额外数据信息,java虚拟机规范将Java运行时数据分为6种:PC寄存器数据、Java栈、堆、方法区、本地方法区、运行时常量池。
PC寄存器数据:
用于保存当前执行程序的内存地址,也就是记录某线程当前执行的方法的那一条指令,如线程的执行被中断后就会依靠这些数据来恢复(JVM规范之定义了对java方法需要记录指针,对本地方法则没有规范)
Java栈:
java栈与线程相关联,每创建一个线程就会为该线程创建一个栈,而线程中运行的每一个方法则与栈中的每一个栈帧关联起来,栈帧中包含局部变量,操作栈,方法返回值等。每一个方法完成,就会弹出栈帧的元素(操作栈的栈顶元素),作为返回值,清除这个栈帧。java栈的栈顶就是当前正在执行的活动栈,PC寄存器会指向这个方法的地址。Java栈和线程对应起来,这些数据不是线程共享的,不存在一致性问题
堆:
存储Java对象的地方,由于时所有线程共享的,所以需要关心数据的一致性问题。
方法区:
用于存储类结构信息,如常量池、域,方法数据、方法体,构造函数、包括类中的专用方法、实力初始化、接口初始化等
运行时常量池:
包括编译器的数字常量,方法或者域的引用。(注意,这一区域属于方法区)
本地方法栈:
JVM为运行native方法准备的空间。由于很多native方法是用c语言实现的,所以又叫C栈。这个区域jvm并没有严格的限制,由不同的JVM实现者自由实现。
JVM的内存分配主要基于堆和栈,
栈:
堆
静态内存的分配和回收:类中的局部变量和对象的引用都是静态内存分配的(这一部分内存空间在栈上分配),在编译时这一部分空间已经确定,只是在程序被加载时一次性分配,而当方法运行结束时随着对应栈帧的撤销回收。
动态内存分配和回收:像实例等数据只有在JVM解析类对象后才能知道具体需要分配多少空间,并且堆中的这些数据只有在对象不再被引用时才会被回收。只要某个对象不再被其他活动对象所引用就可以被回收,而活动对象是指可以被根集合对象所到达的对象。根集合对象所包含的对象跟jvm具体实现有关,但是大都会包含如下一些元素:方法中局部变量的引用、java操作栈中的对象引用、常量池中的对象引用、本地方法中持有的对象引用、类的class对象(当该Class对象不再被使用时同样会被回收)。
基于分代的垃圾收集算法:分为young、old、perm三个区
young区分为eden区和两个survivor区,eden区满后会触发minorGC,minorGC后仍存活的对象将放到survivor区(若另一个survivor区存在活动对象将放到同一个区中,保证一个survivor区是空的)
old区中已满将会触发FullGC,old区中存放的是:
perm区主要存放类的class对象,只有在FullGC时才会被回收
三类垃圾收集算法,Serial Collector、Parallel Colllector、CMS Collector
JVM在client模式下的默认的GC方式(可以通过配置jvm参数 -XX:+UserSerialGC来配置实用该算法)-XX:+PrintGCDetails 可以配置打印GC日志。所有创建的对象都将在Eden区分配,如果创建的对象超过Eden区的大小或者超过PretenureSizeThreshold配置(-XX:PretenureSizeThreshold=123)参数的大小都只能在old区分配。当Eden区空间不足时会触发minorGC,但是触发minorGC之间会检查晋升到Old区的平均对象大小是否大于old的剩余空间,如果大于则触发FullGC,如果小于则根据HandlePromotionFailure(是否允许担保失败)参数,如果为true则仅触发MinorGC,否则触发FullGC。MinorGC时除了将Eden区的非活动对象回收外,还会把一些年老的对象晋升到Old区,而这个年老对象的‘岁数’则通过 -XX:MaxTenuringThreshold=10设置(在survivor的from/to区之间移动一次则为一岁),另外如果To的Servivor区空间不足移入对象时,这些对象也会直接放入Old区。如果old区或者Perm区空间不足时就会触发FullGC。GC时因为是串行的,所以动作是单线程完成的,JVM中的其他应用程序会全部停止。
Parallel GC根据MinorGC和FullGC的不同分为三种,分别是ParNewGC、ParallelGC和ParallelOldGC。
ParNewGC:
可以通过参数 -XX:+UseParNewGC参数来指定,与Serail Collector相似,只是回收是多线程并行的,并且通过一个UseAdaptiveSizePolicy配置参数来控制对象经过多少次回收后可以直接放入old区。
ParallelGC:
是server模式JVM下的默认GC方式,可以通过 -XX:+UserParallelGC参数来强制指定,并行回收的线程数可以通过 -XX:ParallelGCThreads来指定,这个值有个计算公式,如果cpu核数小于8,则可以和核数一样,如果大于8值为:3+(核数*5)/8,可以通过 -Xmn:10m来控制Young区的大小,而Eden、FROM区的大小比例可以通过 -XX:SurvivorRatio=8来设置Eden和FromSpace的比值是8:1(当然To区也占1)。当在Eden区中申请内存空间时,如果Eden区不够,则比较当前申请空间时否大于Eden的一半,是的话则直接在old中分配,不是的话则会执行MinorGC,但是执行MinorGC之前会检查old区的平均晋升大小是否大于剩余空间,大于则触发FullGC,并且在执行FullGC后会再一次检查old的晋升的平均大小是否大于剩余空间,不是的话会再次触发FullGC,也就是说可能会触发两次FullGC。,Young区的晋升规则可以通过以下参数设置:AlwaysTenure:默认为false,为true则表示只要在MinorGC时存活则晋升,NeverTenure,默认为false,是true则永不晋升。如果上面两个参数都没有配置的情况下设置UseAdaptiveSizePolicy,则启动时将以InitialTenuringThreshold值作为存活次数的阀值,并且在每次GC后调整。如果不使用UseAdaptiveSizePolicy则将以MaxTenuringThreshold为准(通过-XX:-UseAdaptiveSizePolicy设置)另外如果MinorGC时Servivor的To区空间不够,也会直接放到old区。old或者Perm区满时会触发FullGC,如果配置了参数ScavengeBeforeFullGC则在FullGC之前会触发MinorGC
PrarllelOldGC:
可以通过 -XX:+UseParallelOldGC参数来强制指定,同样可以通过-XX:ParallelGCThreads来指定线程数,这个值有个计算公式,如果cpu核数小于8,则可以和核数一样,如果大于8值为: 3+(核数*5)/8。与ParallelGC的不同在于FullGC,它的FullGC动作为清空整个Heap对中的垃圾对象,清楚Perm区中已经被卸载的类信息,并进行压缩,而ParallelGC只清楚部分heap堆中的垃圾对象,并对部分空间进行压缩。
可以通过 -XX:+UseConcMarkSweepGC来指定,并发的默认线程为4,也可以通过ParallelCMSThreads指定。CMS Collector使用CMS GC、Minor GC、FullGC。而CMS GC不同于其他两种GC,触发规律是基于Old区、和Perm区的使用率(触发后回收对应old或perm区的内存),达到一定比例就会触发(默认是92%),该比例可以通过CMSInitiatingOccupancyFraction来指定,另外设置让Perm区也使用CMS GC可以通过参数 -XX:+CMSClassUnloadingEnabled来指定。这个模式下的minorGC与Serial Collector基本一致,只是采用多线程。FullGC只在两种情况触发,一种是Eden分配失败后分配到To区,To区满分配到Old区,Old区不够则触发FullGC,另外一种是当CMS GC向Old申请内存失败时会触发FullGC。Hotspot1.6下使用这种算法并显示调用System.gc(如Nio可能需要显示调用),且设置了ExplicitFCInvokesConcurrent参数,将会导致内存泄露。
日志格式:
[GC [<Collector>:<starting occupancy1> -> <ending occupancy1> (total size1) , <paise time1> secs ] <starting occupancy2> -> <ending occupancy2> (total size2), <paise time2> secs ]
<Collector> 收集器的名称
<starting occupancy1>Young区GC前内存
<endingoccupancy1>Young区GC后内存
<paise time1>YOUNG区局部收集时JVM的暂停时间
<starting occupancy2>表示JVMHeap GC前内存
<endingoccupancy2>表示JVMHeap GC后内存
<paise time2>GC过程中JVM的暂停总时间
GC日志对内存泄露的判断:
JVM Crash日志分析:
标签:
原文地址:http://www.cnblogs.com/wade-luffy/p/5931138.html