当前位置: 首页 > news >正文

销售型网站的建设流程及特点四川省城乡建设网站

销售型网站的建设流程及特点,四川省城乡建设网站,青年汇网站开发公司,友情链接交换源码转载自 认识JVM--第一篇-对象分配回收算法本来标题党想写成《深入JVM》#xff0c;不过不太敢写#xff0c;我想一小篇博客我想还不足以说明JVM#xff0c;在本文中#xff0c;会就我所知给大家介绍JVM的很多内部知识#xff0c;概念会相对较粗#xff0c;因为太细…转载自   认识JVM--第一篇-对象分配回收算法本来标题党想写成《深入JVM》不过不太敢写我想一小篇博客我想还不足以说明JVM在本文中会就我所知给大家介绍JVM的很多内部知识概念会相对较粗因为太细的内容要写这里肯定写不出来本文主要偏重理论没有什么实践中间除一些官方资料外还有部分自身的理解所以请大家不要完全信任本文内容另外本文会有一小部分纠正以前一篇文章对于intern()使用方法的错误本文会在其中说明使用错误的原因大致文章内容有以下几个部分 1、JVM虚拟内存组成及操作系统地址表 2、新生成对象在HeapSize是如何变化的 3、虚拟机如何定义回收算法 4、JVM占用的空间除HeapSize还会占用什么OutOfMemory种类 5、纠正错误intern()的使用上的错误好现在开始话题吧 1、JVM虚拟内存组成及操作系统地址表1.1.虚拟地址大致概念在OS层面一般是由逻辑地址映射到线性地址如果线性地址管理如果启动了分页那么线性地址就会转换到相应的物理地址上否则就直接认为是物理地址程序设计中所用到的地址单元就是逻辑单元如在C语言中的表示指定的地址就是逻辑地址而物理地址也并非我们所认为的RAM还应该包括网卡、显存、SWAP等相关内容也就是由OS所管理所有可以通过顶层逻辑单元映射到的目标地点不过绝大部分情况下只需要考虑RAM即可尤其是在服务器上JVM的虚拟内存地址和操作系统的虚拟内存地址不是一个概念操作系统的虚拟内存地址相当于在磁盘上划分的一个SWAP交换区用于内存内存与之做page out和page in的操作这种用于物理内存本身不够而地址空间够用的情况一旦程序出现page out这些情况的时候程序将会变得非常缓慢而JVM的虚拟内存是在有效的空间内分配一个连续的线性地址空间因为JVM想要自己管理内存分配的堆内存都是在自己的heapSize内部因为它要实现一些脱离于存储器本身对非连续堆处理的管理而导致的复杂性也就是JVM去初始化的时候就会加载一块很大的内存单元然后内部的操作都是内部自己完成的。1.2.内存分配一般C语言分配内存是初始化将相应的基本内容和代码段进行加载但是不会加载运行时候的堆栈内存分配也就是在运行到某个具体的函数时通过malloc、callloc、realloc等方方申请的区域这些区域必须从操作系统中重新来分配使用完成后必须进行freeC中必须使用delete方法来释放大家发现没有OS的堆在内存不断申请和释放的过程中必然会产生许多的内存碎片从而导致你在申请一块大内存的时候需要进行逻辑连接导致在申请的速度减小当然LINUX采用了将内存块划分为多个不同大小的板块来较好的处理这个问题不过片段还是存在的不过这种思想的确是很好的而JVM是如何完成碎片的处理的呢后面章节会说到JVM在初始化的时候就会向OS申请一块大内存JVM要求这块内存在地址空间上是连续的物理上未必连续让所有的程序在这个内部区分配由自己来管理所以它内部相当于做了一个小的OS对内存的管理所以JVM是想让java程序员不用关心在哪一个平台上写代码但是你一定要关心java怎么管理内存的线性地址随着实际物理内存的增加将会导致页表非常大甚至于导致多层页表如内存达到96G这一类那么这样管理起来将会非常麻烦正常情况下一个页只有4K可以自己算一下需要多少个管理地址来指向这个4K这个管理地址太大的时候又需要其他的管理地址来管理这个地址就会导致多层地址可能到最后一个大内存有40%都是用于管理内存的真正使用的可想而知所以在LINUX高版本中对于内存寻址方面做了改进就是支持大页面来支持其实是通过一个套件完成的并非OS本身如一个页的大小为1M这样的但是有一些风险在里面它要求大页面内存要么放得下你的内存但是你不能将你的进程一部分放在大页面内存中一部分放在OS管理的小页面内存中也就是说要么这块放得下要么就放在其他地方可能会导致两边正好都差那么一点点的问题在OS这边可以使用SWAP但是系统会很慢而且SWAP很多的情况下肯定会宕机掉。1.3.内存分配状态一个大的进程如果初始化需要分配一块大的内存空间内存空间一般会经历两个状态的转换过程首先内存必须是free状态才可以被分配如果的确是该状态并且空间是够用的那么它首先会占用那么大一个坑在java的heapSize中就是-Xmx参数指定的也就是JVM虚拟机最大的内存空间注意这里-Xmx并没有包含PermSize的空间这个坑是不允许其它进程所占用的内存的状态为reserved的状态当需要使用的空间时内存将会被commited状态在JVM初始化时也就是-Xms状态的内存空间处于这个状态的内存如果发现不够使用物理内存此时就会发生swap区域程序将会变得非常缓慢但是不会造成宕机而很多时候在这个时候定位不出原因所以我们为了让物理内存不够用的现象暴露出来可以被发现至于可以定位不是程序代码的问题我们就直接将swap内存禁用掉有个问题就是既然被reserved的内存就不能被其他进程所占用为什么要在这两个状态之间来回倒腾呢这不是多一个开销吗JVM在来回倒腾的过程中会导致每个区域的容量发生相应的变化必然导致的是FullGC的过程那么JVM一般在服务器端如何设置呢文章后面逐步细化说明。1.4.JVM内存组织关于JVM内存组织方面前面在讲述Java垃圾回收的时候已经提及到了但是讲得不太细有些部分可能算是有错误的所以这里根据上述操作系统知识以及官方部分资料继续深入不敢说完全正确不过至少比以前要更加深入得多首先来看下ORACLE官方给出来的一个JVM内存单元的组织图形其实我看过很多次这个图看得很晕因为以前不了内存分配中commited与reserved的区别以至于我当时认为这副图是说java的HeapSize是由N多个部分组成的并且还包含HeapSize的其实在经过很多资料查阅后尤其是看到一些监控工具后才知道看官方资料也有误区呵呵通过简化我自己画的这副图希望能够帮助大家理解JVM的大致的内存划分这里仅仅提及JVM自己的内存也就是HeapSize和PermSize的部分其余的文章后面说明这里仅仅将上面的图形立起来画了当时看起来要方便理解得很多个人感觉也就是说你首先需要将JVM的两个大板块分开一个是HeapSize也就是上图左侧的部分右边部分为PermSize的尺寸HeapSize也划分为大区域为Young和Old区域Young区域内部划分为三个部分一个是Eden和两个同样尺寸大小的survivor区域注意到的人会发现为什么每个区域内部还有一个virtual区域这就是我们上面说的没有经过commited当时已经占用了地址列表它不能被其他进程所占用当时操作系统一般的提示会认为这是块剩余空间但是实际上是只能被自己使用的这部分上面已经提及至于为什么我们后面来解释这里再提出一些问题就是为什么JVM要提出这么多区域划分来管理呢?如果一个区域可以管理为什么还要搞得那么麻烦呢这么多区域有什么用处我们在第二章对象的分配中将详细说明这部分内容。 2、新生成对象在HeapSize是如何变化的2.1.java新创建对象的方法有哪些首先学习过java的人可能没有人不知道new 这个关键字也就是新创建一个对象的关键字当发生new操作时jvm为你做了什么我们先把这个问题放下对于jvm初始化加载专门处理这里先说除了new之外还有什么方式就是通过java.lang.Class.forName进行动态状态后获取一个新的实例当然方法有重载也通过通过ClassLoader进行动态状态什么是动态装载为什么有了new还要有动态装载而jvm初始化做了什么动态装载和new的区别是什么这也是我们下面要讨论的问题也是PermSize中内容的一大块部分。2.2.jvm初始化需要做什么Jvm在向OS请求了一块地址列表后然后就需要初始化了初始化要做什么呢jvm启动相当于一个进程当然它可以再启动子进程这里我我们只考虑单个进程进程启动必然需要初始化一些内容C语言或者C它会将相应的全局变量以及代码段等内容在内存中进行编译为相应的指令集而jvm做了什么呢jvm它也需要做一些操作首先每一个进程都必须最少一个引导进程也就是我们说的main通过引导进程所关联以及关联的关联也就是importjvm会将这些关联关系的内容形成一个大的jvm网状结构用于关系于class之间并保证每一个class有一份自己的私有池他们放在哪里他们就是放在PermSize也就是很多中文翻译中的永久代每一个Class都有自己独立的私有池去管理自身的结构对一个java程序源文件编写的是对于程序的描述信息生成class也就是描述信息的byte格式在这个过程中会自动完成一些简单逻辑合并工作byte格式是字节码格式也就是按照每8个bit位组成的计算机基本格式只要字符集统一则为每一个操作系统所认知的格式JVM需要做的是将这些统一认知的格式信息翻译为对应操作系统的指令或硬件指令所以JVM真正的意义就是为每一个操作系统编写了一个统一的JRE即java运行时环境而编译环境是所有系统都可以使用的初始化将class的定义加载到内存中会进行相应的转换和压缩总之会形成原有对类型描述和执行顺序而不会出现混乱但并不是对应的操作系统指令对应的操作系统指令是运行时知道的如描述类型、作用域、访问权限等等内容这部分空间大小决定于class的多少也就是你的工程的大小PermSize还包含了其他的内容并且只是在一般情况下不会发生GC但是有些时候还是会发生GC的在后面继续说明这个加载完成后他们在池中自然有自己的内存首地址要寻找他必然要有对应列表列表的基础肯定是属于符号向量了也就是基于名称的一个符号向量那么当发生new时它会在符号向量中寻找对应的class找到后将符号地址转换为对应的class地址并且这个内容只会被转载一次以后可以直接被利用从中找到了class的定义在堆中分配内存时将其定义部分的某些组织单元放置与对象的头部这些代码段对于对象来说是彼此独立就像你在方法体前面增加synchronize关键字对于非静态方法来说不同的对象这个关键字是相互不会影响的也就是说如果多个线程调用的对象不是同一个仅仅在方法非静态方法体上面增加synchronized这对于多线程同步是无效的更多关于多线程的知识如关锁方面的Lock、Atomic等方面的知识不是本文的内容这里不再展开讨论注意这里还没有谈到申请对象以及动态装载动态装载的class一般是不会JVM初始化的时候转入Perm的而是运行时动态装载进去的就像JDBC驱动一样大家几乎都用动态装载来实现动态加载不同数据库连接的目的也就是我们上一节提出的问题动态装载做什么它负责的是运行时装载一些类的定义而不是初始化当然当你通过全名去加载的时候他们会从符号向量中寻找这个类是否已经加载如果已经加载则直接使用否则从相应的包中获取这个class定义然后装载起来装载的单位也是以class为单位并不是以jar包为单位这里请大家如果不要滥用动态加载一个是造成Perm的不稳定另一个是它的效率肯定没有new高因为它需要先去通过符号向量寻找是否存在不存在再加载然后再通过newInstance实例化一个或多个实例当然在某些特殊的时候利用它可以为你的程序带来极高的灵活性。2.2.内存申请时的指针与实例内存申请时上一节已经说到地址空间的和符号引用得到对应数据结构的方法这里不再提及这里就将对象作为整体在堆中在JVM的初衷中它希望新申请的内存是连续的虽然堆的定义是让内存是随机分配的但是对于整个JVM来说它希望分配的内存是较为连续的也就是按照较为条带化的方式进行分配好处有好几个一个是这样非常的简单经过精简后的情况目前一个new翻译为机器码只需要10条左右的指令码近乎与C语言所以在高版本的jdk中new的开销不再是java虚拟机慢的一个原因大家也没有必要去尽量减少new但是也不要滥用业绩虽乱定义不必要的对象其次另一个好处当内存较为连续后内存在分配上就没有类似的大量碎片的问题造成运行一段时间后大量碎片当需要申请一个大内存的时候需要寻找非常多的地方才能将其逻辑上组成而导致分配空间上不必要的浪费而一个简单内存分配String a  new String(abc);这样一条代码会做什么动作呢a相当于是对象的一个指针一样的东西这个空间的大小为一个long的长度也就是可以支持到可以想象的任何内存大小它并不是存放在heapSize中的而是放在stack中的由OS来调度管理也就是当a的作用区域完成这个指针将会断开java中的String不再是C或者C中的一个指针指向的一个字符数组而是一个被包装后的对象也就是java为什么说自己都是对象因为它把原生态的内容进行了包装让程序编写更加简单这里顺便提及一下在较早期的jdk中jvm并不是由一个指针直接指向分配堆中的首地址而是先有一个handle空间这个空间存放了开始说的一些对象的定义和结构信息也就是找到该位置然后由该位置转换到对应的对象上但是那个时候的对象头部信息就没有现在的那么全也就是以前是将一部分handle内容放置在独立的空间上现在的jdk已经没有那样的了。2.3.内存分配后放在哪里如何移动 终于回到上面的话题内存分配后在堆中的什么位置就是我们上面说的heapSize中的Young区域的Eden区域中也就是new的对象绝大部分会放在这里排除一种非常大的对象的特殊情况在java设计的看来有一个特别有意思的地方就是它在新生成的对象中它认为你绝大部分对象都是应该需要被销毁掉的就像在做java WEB应用上一样一个列表请求过来可能请求的内容有2K的内容请求完成后这个内容一般说来自然就不需要了也就是在他原始的考虑下它没有考虑你自己在应用级别去做page cache的操作好那么当内存不够的时候这里指被commited的空间不够的情况下此时java就会做一个动作就是会对Young空间进行回收由于新生成的对象java认为这块空间不会很大而且绝大部分应该是被干掉的内容所以很多时候java会采用单线程的复制算法当然你也可以设置为多线程关于算法的核心在第三章中会说到这里总之先理解找到了活着的对象将其拷贝到其中一个survivor区域中当下一次做操作时就会将Eden中活着的以及前一个surivor活着的一起拷贝到另一个survivor中这就是为什么要设置两个survivor区域而拷贝后Eden区域为空、另一个survivor也为空可以完全直接整体清除掉所以非常快速而拷贝的目标也会被连续化新生成的对象又从Eden的初始位置开始分配空间。当对象每次活着被拷贝到一个survivor时Java虚拟机就会记录下来对象被移动的次数当次数达到一定的程度也就是官方文档所说的足够老的情况这块内存就认为它不太容易被注销掉此时就会被移动到第二个区域Tenured区域这个次数也可以由自己来控制。另外在一般默认的情况下当回收后的内存仍然占用实际目前commited内存的70%以上那么此时虚拟机将会开始扩展这些内存而当回收后的内存小于40%后虚拟机将会降低这部分内存但是其他线程仍然不能使用当然这个参数也是可配置的在文章最后有说明这样收缩和扩展必然导致一些问题但是java的初衷是想让你再没有使用这块地址表的时候回收内存的大小会小一些因为young区域的一般是使用单线程的回收方式这个时间段是会被暂停的所以它认为内存使用较少的时候回收就内存的速度应该加快但是和实际相反的是我们正好需要的是内存使用较大的时候才希望加快回收的速度内存使用小的时候回收都是无所谓的所以我们在很多时候建议将-Xms和-Xmx设置成一样的大小不用这么来回倒腾。在说明下以下三种情况对象会被晋升到old区域1、在eden和survivor中可以来回被minor gc多次这个次数超过了-XX:MaxTenuringThreshold2、在发生minor gc时发现to survivor无法放下这些对象就会进入old。3、在新申请对象大于eden区域的一半大小时直接进入old也可以专门设置参数-XX:PretenureSizeThreshold这个参数指定当超过这个值就直接进入old。当上面的对象被移动到了Tenured区域这个区域一般非常大占用了HeapSize的绝大部分空间此时若它发生一次内存回收就不能像刚才那样来回拷贝了那样代价太大而且这个区域可以说是经得起考验的对象才会被移动过来在概率上是不容易被销毁掉的对象才会被移动过来那么我们很此时想到的就是反过来计算也就是找到需要销毁的对象将其销毁关于算法也是下面第三章要说的内容总之对象会在这里存放着。为什么java不论在Young中的区域会来回倒腾而在Tenured区域也会不断去做压缩就是我们前面说的它希望内存相对较为连续而做的java在Yong的区域它认为可以剩下的内容不会很多所以拷贝的代价并不大所以它认为来回拷贝是一种合适的方法而Tenured区域它采用了清除后一定次数后进行压缩的方式当然这个次数你可以自己去设置在文章的最后是有参数的而它没有采用类似操作系统一样的按照板块大小等一系列算法来完成这也是我比较纳闷的事情不过总体说来这种算法还是可行的希望在划分区域一些策略上能有更大的灵活性这样可以在更多的应用中发挥得更加灵活这样就更好了比较困惑的就是这样的架构自己如果做频繁度不高不低的page cache性能不好估量也许比不做cache更低这个要根据具体情况而定了。2.3.Perm一般还会存放什么内容Perm除了存放上面的Class定义外还一般会存放的内容有静态代码段、final static类型的类变量、String常量以及String被intern后的内容也是最后一章中所要提及以前我自己写错的内容如何应对好常量池以及常量池是否会被GC也是我们所需要说明的内容关于Perm永久代中存放的内容应当如何配置以至于它可以去回收在文章的最后有相应的说明请自行查阅不过对于Perm的大小一般还是不建议去做GC的也就是合理的去使用Perm在程序运行中占用Perm最多的就是String常量尤其是如果大量使用intern的时候就会造成大量Perm膨胀也是最后一部分需要说明的内容不过intern也并非一无是处因为你可以这样说如果它没有用处的话java没有必要再把String的常量放在单独的一个地方它有很多好处只要在适当的时候利用好常量池这个区域在必要的时候可以提高性能具体在最后一章有所讲解。3、虚拟机如何定义回收算法3.1.首先虚拟的回收算法会分成两个部分一个部分是对象的查找算法一个是真正如何回收的方法。一般对于查找有以下两种a)引用计数本来在本文中我不想提及引用计数因为这是最原始也是最垃圾的算法也是较低版本jdk慢得出奇的原因但是为了说明后面的问题不得不简单说明一下引用计数就是通过java虚拟机专门为每个对象记录它被指针指向的个数当发生指针指向它或者被赋值计数器将会被加1而但指向它的指针null或者脱离了作用区域jvm就会将相应的计数器减少1这样简单但是慢死了不仅仅操作上出奇的慢因为要做一个简单的赋值操作要到多个地方去找一大堆东西还有一个就会引起很难检测到的内存泄露那就是当两个或者多个对象存在循环交叉引用的时候此时他们的引用计数将永远不会等于0如使用双向链表或使用复杂的集合类后相互之间的引用也就是垃圾收集器将永远不会认为这是垃圾当然要用复杂的算法可以解决但是这个算法的确很复杂可能垃圾回收会更加慢最后就是这个垃圾回收方式必然导致内存的遍历操作过程。引用计数的示意图如下图所示b)引用树遍历其实是一个图只是有根而已它沿着对象的根句柄向下查找到活着的节点并标记下来其余没有被标记的节点就是死掉的节点这些对象就是可以被回收的或者说活着的节点就是可以被拷贝走的具体要看所在heapSize中的区域以及算法它的大致示意图如下图所示(对象B、G、D、J、K、L、F都是垃圾对象虽然他们也有相互指向但是不是被根节点能遍历到的注意这里是指针是单向的)3.2.内存回收上面的方法我们可以找到内存可以被使用的或者说那些内存是可以回收更多的时候我们肯定愿意做更少的事情达到同样的目的我们会根据一般的情况设置不同的算法来让系统的性能达到较好的程度首先来了解下内存回收的算法或者它的经历有哪些a):标记清除算法这算是比较原始的算法也就是通过上面的查找标记后我们对没有标记的对象进行空间释放的过程这个算法虽然很原始但是是后来所有算法的基础好处的简单缺陷是造成和其他语言一样的内存碎片要通过更加复杂的算法来解决这些碎片另一缺陷就是它这个过程如果用于较大的内存将会导致长时间的对外服务停止当然这个停止也不是传说中那么长只是相对计算机来说比较长至于多长是还和jdk的版本以及厂商有关系BEA曾经在1G的JVM下面测试有300M空间属于可用空间据测试结果为30ms的停止服务时间我想这个时间应该可以接受不过它有自己的测试场景不能完全说明问题而一般情况下在单线程引用下常规的回收起码会比这个时间要长好几倍甚至于10倍以上。b)标记清楚压缩这个算法是也是较为原始的它的出现是为了解决上面一种算法中不能压缩空间的问题但是并非取代因为它导致的另一个问题就是更长时间的服务停止因为压缩就是空间拷贝到一个较为连续的地方而并非对数据本身进行压缩所以很多时候他们是配合使用的如多少次清除后进行一次压缩。c)复制回收也就是在jvm发展的过程中出现的算法现在基本都只能看到一些思想影子在里面但是没有这个方式也就是将其划分为2个相同的大小然后将活着的节点来回拷贝这样造成的内存浪费的非常大的不仅仅是一半的浪费问题而且每次拷贝的开销也是非常大的因为都是涉及到整个jvm活着节点的拷贝过程。d)增量回收这算是现代垃圾回收的一个前身它做的事情就是为了解决复制回收算法中的一个问题就是每次复制造成的空间开销非常大的问题此时它将内存中切分为逐个板块这些板块每个内部使用了复制算法也就是并没有解决空间浪费的问题回收的过程中没有进行细化虽然回收速度较快速而且只会造成局部的停止服务但是对于不同板块大小、不同生命周期的对象还是没有划分开。e分代收集器分代收集器是增量收集的另一个化身或者说延续吧它将板块按照生命周期划分为上面所说的板块每一个板块可以采用不同的算法进行回收这也是和增量回收最大的区别此时可以让jvm的回收达到更好的效果不过由于jvm按照生命周期划分后都是指定板块的所以根据内存大小划分自定义板块是不可能的至少现在好像还没有所以在回收过程中如果内存大了回收起来一样很吃力尤其是对Old区域的回收所以并发回收不得不出现了。f并发回收所谓并发回收是指外部在访问的同时java回收器依然在做着回收工作原早我认为并发回收是不可能的因为你需要知道内存是需要回收的就不能让内存继续的被申请和释放但是SUN的人还是比较天才的还是有办法尽量让他并发去做的并发回收器其实也会暂停但是时间非常短它并不会在从开始回收寻找、标记、清楚、压缩或拷贝等方式过程完全暂停服务它发现有几个时间比较长一个就是标记因为这个回收一般面对的是老年代这个区域一般很大而一般来说绝大部分对象应该是活着的所以标记时间很长还有一个时间是压缩但是压缩并不一定非要每一次做完GC都去压缩的而拷贝呢一般不会用在老年代所以暂时不考虑所以他们想出来的办法就是第一次短暂停机是将所有对象的根指针找到这个非常容易找到而且非常快速找到后此时GC开始从这些根节点标记活着的节点这里可以采用并行然后待标记完成后此时可能有新的 内存申请以及被抛弃java本身没有内存释放这一概念此时JVM会记录下这个过程中的增量信息而对于老年代来说必须要经过多次在survivor倒腾后才会进入老年代所以它在这段时间增量一般来说会非常少而且它被释放的概率前面也说并不大JVM如果不是完全做Cache自己做pageCache而且发生概率不大不小的pageout和pagein是不适合的JVM根据这些增量信息快速标记出内部的节点也是非常快速的就可以开始回收了由于需要杀掉的节点并不多所以这个过程也非常快压缩在一定时间后会专门做一次操作有关暂停时间在Hotspot版本也就是SUN的jdk中都是可以配置的当在指定时间范围内无法回收时JVM将会对相应尺寸进行调整如果你不想让它调整在设置各个区域的大小时就使用定量而不要使用比例来控制当采用并发回收算法的时候一般对于老年代区域不会等待内存小于10%左右的时候才会发起回收因为并发回收是允许在回收的时候被分配那样就有可能来不及了所以并发回收的时候JVM可能会在68%左右的时候就开始启动对老年代GC了。d并行回收并行回收指利用多个CPU对JVM进行并行垃圾回收的过程并行度都是可以设置的可以分别对年轻代和老年代配置是否使用并行回收。 好了回收算法就说到这里那么如何利用好回收算法在看了上面的介绍后是否对JVM有了一个大致的了解具体细节可以慢慢实践在文章最后给出一些常用的java虚拟机内存设置参数的说明不过并不权威需要根据实际情况而定才可以。 下面说下java虚拟机除了消耗基本内存外还会消耗什么内存 4、JVM占用的空间除HeapSize还会占用什么一般来说对于很多学了好几年甚至于很多年java人来说一旦看到OutOfMemeory简称OOM就认为HeapSize不够然后疯狂的增加-Xmx的值但是HeapSize只是其中一个部分当你去做一个实验也就是java启动时直接在程序中疯狂的new 一些线程出来直到内存溢出当-Xms -Xmx设置得越大的时候得到的线程个数会越少为什么呢因为OOM并不是HeapSize不够而导致的而由很多种情况。首先看下操作系统如何划分内存给应用系统其实在Win 32、Linux 32的系统中地址总线为32位的理论上应该可以支持4G内存空间但是当你在Win 32上设置初始化内存如果达到2G就会报错说这个块空间没法做首先默认的Win32系统会按照50%比例给予给Kernel使用而另一部分给应用内存也就是说操作系统内核部分不论是否使用这一半是不会给你的而还有2G呢它在系统扩展的部分也就是并非Kernel的部分有很多静态区域和字典表的内容所以要划分一个连续的2G内存给JVM在Win 32上是不可能的Win 32提出了一种Win 32 3G模式貌似可以划分3G空间其实它只是将内核部分缩小也就是管理部分缩小也就是将一部分划分到外部来使用而且Win 32习惯在内存2G的位置做一些手脚让你分配连续2G没有可能性一般来说在Win 32平台上在物理内存足够的情况下给JVM划分的空间一般是1.4~1.5G左右具体数据没有测试过而Linux 32类似于Win 32 3G模式但是它还是一般情况下分布不凌乱的情况下一般可以给JVM划分到2G的大小。Linux 32 Hugemem是一个扩展版本可以划分更大的空间但是需要付出一些其他的代价理论上可以支持到4G给应用也就是Kenel是独立的Solaris x86-32和AIX 32等系统也类似于Linux 32平台一样。为什么还要预留一些空间出来呢这些空间给谁当你申请一个线程的时候它的除了线程内部对象的开销外线程本身的开销是需要OS来调度完成一般来说会在OS的线程与虚拟机内部有都有一个一一对应的但是会根据操作系统不同有所变化有些可能只有一个总之heapSize外的那部分空间是跑不掉的它放在哪里呢就是放在Stack中的所以上文中的-Xss就是设置这个的在jdk 1.5以后每个线程的大小被默认设置为1M的stack开销我们习惯将这个开销降低。好了知道了指针、线程是在heapSize外部的还有什么呢当你自己使用native方法也就是JNI的时候调用本地其他语言如C、C在程序中使用了malloc等类似方法开辟的内存都不是在heapSize中的而是在本地OS所掌控的另外这部分空间如果没有相应的释放命令就需要在对应finalize方法内部调用其他的native方法来完成对相应对象的释放否则这部分将成为OS级别的内存泄露直到JVM进程重启或者宕机为止操作系统会记录下进程和相应线程和堆内存的关联关系但是进程再没有释放前OS也是不会回收这部分内存的。另外在使用JavaNIO以及JDBC、流等系列操作时当形成与终端交互时会在另一个位置形成一个内存区域这些内存区域都不在HeapSize中。所以常见的OOM现象有以下几种1、heapSize溢出这个需要设置Java虚拟机的内存情况2、PermSize溢出需要设置Perm相关参数以及检查内存中的常量情况。3、OS地址空间不够也就是没有那么多内存分配这个一般是启动时报错。4、Swap空间频繁交互进程直接被crash掉在不同操作系统中会体现不同的情况。5、native Thread溢出注意线程Stack的大小以及本身操作系统的限制。6、DirectByteBuffer溢出这一类一般是在做一些NIO操作的时候或在某种情况下使用ByteBuffer在分配内存时使用了allocateDirect以及使用一些框架间接调用了类似方法导致直接内存的分配如mina中使用IoByte去调用当参数设置为true的时候就分配为直接内存所谓直接内存就是又OS定义的内存而不需要从程序间接拷贝一次再输出的过程提高性能但是如果没有手动回收是回收不掉的导致的Buffer问题如输出大量的内容输入大量的内容此时需要尽量去尝试限制它的大小。 使用非常多的工具区检测Java的内存如jstat只能看HeapSize和PermSize、jmap很细的东西、jpsjava的ps -ef呵呵、jdb这个不是监控工具哈这个是debug工具、jprofile图形支持但是可以远程连接等等jconsole可以看到heapsize、permsizenative mem size这这里叫做non-heapsize等等的使用的趋势图、visualvm(极为推荐的东西图形化查看你可以查看到内存单元分配、交换、回收、移动等等整个过程非常清晰展现jvm的全局资源)、另外pmap可以展现非常清晰的资料可以精确到某一个java进程内部的每一个细节而且可以看到heapsize只是其中很小一部分在solaris操作系统上看得最齐全LINUX下有些进程可能看不太懂也可以在/proc/进程号/maps中查看这里可以看到内存地址单元的起始地址包含了reserved的地址范围和commited的地址范围全局资源使用操作系统top命令和free命令看IBM有一个GCMV免费下载工具也很好Win32有一个WMMap工具都是很好的工具 使用相应的工具观察相应的内容当观察到内存的使用从无到有上升然后处于一个平稳趋势那么这个JVM应该是较为稳定的如果发现它经过一段平滑期后又出现飙升这个必然是有问题的至于什么问题根据前面的学下和实际情况我们可以去分析当它开始后平滑过程出现缓慢上升的过程但是始终会上升到极点那么一个是需要知道物理内存时候可用另一个就是少量的内存泄露JVM现代也有内存泄露只是它的内存泄露并非C、C中的内存泄露。 5、纠正错误intern()的使用上的错误最后一章节我自己纠正一下我自己的错误以前的文章中也就是关于intern的使用最近对他做了一些深入研究因为以前也是和很多同学一样听到别人推荐什么就疯狂的使用知道点原理也是点大概没有深入研究内部的内容。我曾经在文章中说到任何系统最多使用的数据类型必然是String不管做什么所以在String的处理上很有研究推荐使用java的朋友在大量使用对比的时候不要用equals而推荐使用intern()但是我最近发现我错了我这里给大家道歉因为可能会误导很多朋友下面说明下这个东西为什么首先我开始自己怀疑自己的时候是想说如果intern可以做到高效那么equals是不是在String中就没有存在的必要了呢当时对于我理解仅仅为常量池的一个地址对比好比是两个数字的compare仅仅需要CPU的单个指令即可完成于是我开始做了两个实验一个是最原始最初级的方法采用单线程循环1000000次调用equals与intern等值对比并且采用了不同长度的字符串去做比较发现equals竟然比intern要快而且随着字符串长度的增加equals会明显快与intern然后使用多线程测试也是得到一样的效果我首先很不敢相信自己坚持的理论被彻底和谐了后来冷静下来必须需要面对通过很多权威资料的阅读我发现我对JVM常量池的理解还只是一点点皮毛而已所以我做了更加深入的研究。原来intern方法被调用时是在Perm中的String私有化常量池中寻找相应的内容而寻找虽然可以通过hash定位到某些较小的链表中但是还是需要在链表中逐个对比对比的方法仍然是equals也就是抛开hash的开销intern最少要与里面的0到多个对象进行equals操作而且如果不存在还要在常量池开辟一块空间来记录如果存在则返回地址也就是常量池保证每个String常量是唯一的这个开销当然大了而且如果使用在业务代码中将会导致Perm区域的不断增加于是我又反过来想了既然equals比他效率高为啥还要用intern呢而且equals的那个算法对于长字符串逐个字符对比的过程我实在是难以入目而且也实在是觉得不甘心自己的理论就这么容易被和谐掉因为自己已经在不少程序中这样用过这样我岂不是犯下大错了因为自己参与过的项目的确太多了而且有类似的代码我写入了框架中最终发现我可能错了一半也就是历史上的记录可能我有一半类似的代码是错误的为什么呢intern还是有用的我先做了一个测试那就是用一个已经intern好的对象让他与一个常量做等值循环次数和上面一样结果我预料的结果发生了那就是比equals快出了N多倍数随着长度的增加会体现出更加明显的优势因为intern对比的始终是地址和长度无关于是我想到了如何使用它就是在程序中返回通过字符串类似于数字一样的类型判定时如做一个sqlparser的时候经常根据数据类型做不同的动作这样如果用equals会在每次循环时付出很多开销尤其是很多数据库的类型非常多最坏的是从上到下每个字符串匹配一次当然长度不等开销很小长度相等开销就大了intern我就将这些schema信息预先intern掉也就是他们已经指向了常量池当再真正匹配时就不需要用intern了而是直接匹配也就是将这个开销放在初始化的过程中运行时我们不去增加它的开销。所以个人是犯下一个错误并且以前还很张扬的到处宣传呵呵现在觉得有点傻希望在看到某些推荐用什么新东西的时候千万不要在没有研究明白他就去用它甚至于滥用它至少要经过一些简单的测试不过对于现代很多复杂的东西一些简单的测试已经不足以说明问题就像Lock与Synchronize的开销一样如果采用简单的循环的话你会发现新版本的Lock的开销将会比Synchronized的开销更加大它适合的是并发读写的并发所以真正要弄清楚还是研究内在。最后说下我个人对JVM的期望JVM做到了很多个板块之间使用不同的算法而JVM不希望程序员去关心内存但是有些特殊的应用需要JVM提供多的支持当然有些公司对JVM内核进行了改造来适合特殊的应用但是我们更加希望标准的JVM能够提供更加灵活的内存管理机制而不仅局限于配置因为配置适中是死的在很多时候会面临扩展性的限制如很多时候我们认为可以判定很多的对象本身就是不会被回收或者根本不容易被回收的就不用到Young的空间和其他的业务套在一起倒腾了对于经常做page cache的系统而page cache的命中率不是特别高95%以上就很高也不是很低如80%以下这个时候置换到快不慢的而会导致在老年代的回收的频繁起来就我个人希望这些空间都能独立出来甚至于可以由程序去控制和指定当然JVM可以自身去默认尤其是按照一些特殊的对象等级类型或者说对象的大小这些细节都可以采用一些相应的默认GC手段来完成也可以人工的指定当然也在默认情况下可以按照原有的模式进行架构这样JVM的内存调节的灵活将会更加宽松使得它能在各类场合下只要使用相对应的手段配置和程序调整都是可以打到目的的。本文包含大量个人见解如有不是之处请大家多多指教本文到此完结内容粗而不深入细节问题细节讨论。常见参数JVM参数配置java vm Hotspot TM 1.6 •-Xms为初始化为HeapSize的空间即被Commited的尺寸。•-Xmx为最大的HeapSize空间有些尚未被Commited但是已经被进程所Reserved当现在已经被Commit的空间长期处于(jdk1.1还有一个-mx为包含handler表的空间)。•-Xmn设置Young的空间大小此时NewSize和MaxNewSize一致或者分别设置-XX:NewSize128m•-XX:PermSize  64M及-XX:MaxPermSize 64M为永久代的初始大小和最大大小。•-XX:NewRatio 3 为Tenured:Young的初始尺寸比例设置了大小就不再设置此值此时Young占用整个HeapSize的1/4大小。•-XX:SurvivorRatio 6为Eden:Survivor比例大小此时一个Survivor占用Young的1/8大小而Eden占用3/4大小。•-Xss256k为ThreadStack空间大小jdk 1.5以后默认是1M在IBM的jdk中还有-Xoss参数此时每个线程占用的stack空间为256K大小•-XX:MaxTenuringThreshold3一般一个对象在Young经过多少次GC后会被移动到OLD区。-XX:UseParNewGC对Yong区域启用并行回收算法。•-XX:UseParallelGC一种较老的并行回收算法。•-XX:UseParallelOldGC对Tenured区域使用并行回收算法。•-XX:ParallelGCThread10并行的个数一般和CPU个数相对应。•-XX:UseAdaptiveSizepollcy收集器自动根据实际情况进行一些比例以及回收算法调整。•-XX:CMSFullGCsBeforeCompaction 3多少次GC后会进行压缩碎片•-XX:UseCmsFullCompactAtFullCollction打开老年代压缩以下3个参数为永久带回收参数-XX:UseConcMarkSweepGC -XX:CMSClassUnloadingEnabled-XX:CMSPermGenSweepingEnabled对永久带进行相应的回收在jdk1.6中不需要数-XX:CMSPermGenSweepingEnabled-XX:MinHeapFreeRatio这是指剩余空间百分比多少时开始减小commited的内存-XX:MaxHeapFreeRatio指剩余空间百分比多少时开始增加commited的内存直到-Xmx大小。-XX:MaxGCPauseMillis指GC最大的暂停时间当超过这个时间那么JVM会适当调整内存比例前提是使用的是基于比例的YONG和设置。-XX:UseConcMarkSweepGC 启动并发GC一般针对Tenured区域。-XX:CMSIncrementalMode增量GC将内存切块分布在多个局部去GC。-XX:CMSInitiatingOccupancyFraction在并发GC下由于一边使用一遍GC就不能在不够用的时候GC默认情况下是在使用了68%的时候进行GC通过该参数可以调整实际的值。大致的参数设置就这些但是GC本身的参数还有很多尤其是和应用或者和具体硬件结合起来的时候而BEA和IBM也有自己的JDK这里有些参数他们支持有些参数不支持在某些平台和甚至于硬件上可以支持特殊的参数来控制如在部分intel系列的多CPU机器上通过它的NUMA架构可以设置对应参数支撑节点和CPU之间可以实现分工负载、常规服务上都是SMP的而大型机上多半是MPP类似于上面的并发GC在一般情况下是不会进行compact压缩的因为它希望回收的时间短但是充满compact的压缩时间必然不是那么短所以在部分特殊应用下有些使用定宽度的内存尺寸回收后不管空余内存因为每个内存的尺寸都是那么大这样来处理当然这样必然会导致很多的内存浪费但是它的好处是可以没有compact而不存在说要分配的内存分配不到的问题。
http://www.huolong8.cn/news/138102/

相关文章:

  • 大连网站优化步骤wordpress如何上线
  • seo网站技术培训深圳谷歌seo培训班
  • 女性手表网站开发网站做图文水印逻辑
  • 三亚做网站多少钱百度推广和网站建设
  • 手机有些网站打不开怎么解决安卓app开发需要学什么
  • 西安做网站云速网络如何联系网站站长
  • 黄金网站app软件下载安装西安网站建设创意
  • 为何有的网站打不开上海网站高端定制
  • 赣州网上注册公司网站中国商业银行官网
  • 360提交网站收录入口如何注册公司支付宝
  • 建设网站贵吗如何给网站做右侧导航
  • 杭州网站制作推荐网络规划与设计流程
  • 网站不备案有什么影响物流管理系统
  • 做不锈钢的网站有哪些百度商家入驻
  • 广东网站建设熊掌号软件工程毕业可以做网站吗
  • 做网站分几种斗门区建设局网站
  • 给网站做选题外包公司和劳务派遣
  • 聊城做网站价位网页制作软件 ad
  • 深圳建设厅官方网站如何进入微网站
  • 泰州专业做网站公司文件链接 win wordpress
  • 宁波网站制作维护小工程承包app
  • 免费搭建淘宝客网站网络技术网站
  • 网站建站图片asp.net网站本机访问慢
  • 工程建设监理网站多商户wordpress
  • 中山网站建设制作动画形式的h5在哪个网站做
  • 静态网站开发专注WordPress网站建设开发
  • 品牌网站的愿望清单怎么做漂亮的网站改版中 html代码
  • wap网站推荐百度怎么做网页
  • wordpress 调用评论seo推广怎么做视频教程
  • 怎么做网站的需求怎么做flash网站