赞
踩
Class loader(类装载)
根据给定的全限定名类名(如: java.lang.Object)来装载class文件到 Runtime data area中的method area。
Execution engine(执行引擎)
执行classes中的指令。
Native Interface(本地接口)
与native libraries交互,是其它编程语言交互的接口。
Runtime data area(运行时数据区域)
这就是我们常说的JVM的内存。
首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader) 再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
程序计数器(Program Counter Register)【私有】
当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
Java 虚拟机栈(Java Virtual Machine Stacks)【私有】
每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法栈(Native Method Stack)【私有】
与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
Java 堆(Java Heap)【共享】
Java 虚拟机中内存大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
方法区(Methed Area)【共享】
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等信息外,还有一项信息是常量池(Constant Poll Table)用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。
其中字符串常量池属于运行时常量池的一部分,不过在HotSpot虚拟机中,JDK1.7将字符串常量池移到了java堆中。
直接内存
直接内存不是JVM运行时的数据区的一部分,也不是Java虚拟机规范中定义的内存区域。在JDK1.4中引 入了NIO(New Input/Output)类,引入了一种基于通道(Chanel)与缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java中的DirectByteBuffer对象作为对这块内存 的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java对和Native对中来回复制数据。
直接内存(堆外内存)与堆内存比较
使用场景
为什么要主动调用System.gc
通过触发一次gc操作来回收堆外内存
堆外内存的回收其实依赖于我们的gc机制,首先我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和gc也有关,那就是gc能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。DirectByteBuffer对象在创建的时候关联了一个PhantomReference, 说到PhantomReference它其实主要是用来跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块
版本变化
Jdk1.6及之前:有永久代, 常量池在方法区
Jdk1.7:有永久代,但已经逐步“去永久代”,常量池在堆
Jdk1.8及之后:无永久代,常量池在元空间
物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
内存分配
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。
一般堆大小远远大于栈。 栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
静态变量放在方法区,静态的对象还是放在堆
程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;
堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。
类加载通过后,接下来分配内存。
若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;
即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。
划分内存时还需要考虑一个问题-并发,也有两种方式:
然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),后执行方法。
对象的访问定位
句柄
优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。
直接指针
优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。
强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的 对象来解决内存不足的问题。
软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收, Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(PhantomReference)
虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
引用计数器法
为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1, 当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用 的问题;
可达性分析算法
从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
标记-清除算法:标记无用对象,然后进行清除回收。
优点:实现简单,不需要对象进行移动。
缺点:效率不高,无法清除垃圾碎片。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。
优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
缺点:内存使用率不高,只有原来的一半。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程
ParNew收集器 (复制算法)
新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU 环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法)
吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Serial Old收集器 (标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (标记-整理算法)
老年代并行收集器,吞吐量优先
Parallel Scavenge收集器的老年代版本
CMS(Concurrent Mark Sweep)收集器(标记-清除算法)
老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。
在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的, 所以在gc的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
整个过程分为6个步骤,其中初始标记、并发标记这两个步骤仍然需要“Stop The World”
初始标记(CMS-initial-mark)
为了收集应用程序的对象引用需要暂停应用程序线程,该阶段完成后,应用程序线程再次启动。
并发标记(CMS-concurrent-mark)
从第一阶段收集到的对象引用开始,遍历所有其他的对象引用,此阶段会打印2条日志:CMS-concurrent-mark-start,CMS-concurrent-mark。
并发预清理(CMS-concurrent-preclean)
改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果。 此阶段会打印2条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean。
CMS-concurrent-abortable-preclean
加入此阶段的目的是使cms gc更加可控一些,作用也是执行一些预清理,以减少Rescan阶段造成应用暂停的时间,通过两个参数来来控制是否进行下一阶段:
- -XX:CMSScheduleRemarkEdenSizeThreshold(默认2M):即当eden使用小于此值时;
- -XX:CMSScheduleRemarkEdenPenetratio(默认50%):在concurrent preclean阶段之后,如果Eden占用率高于CMSScheduleRemarkEdenSizeThreshold,开启’concurrent abortable preclean’,并且持续的precleanig直到Eden占比超过CMSScheduleRemarkEdenPenetratio,之后,开启remark阶段
重标记CMS-concurrent-remark
由于上面三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。这一阶段十分重要,因为必须避免收集到仍被引用的对象。
并发清理(CMS-concurrent-sweep)
所有不再被应用的对象将从堆里清除掉。
并发重置(CMS-concurrent-reset)
收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。
参数控制
-XX:+UseConcMarkSweepGC:
使用CMS收集器,-XX:UseParNewGC会自动开启。因此,如果年轻代的并行GC不想开启,可以通过设置-XX:-UseParNewGC来关掉
-XX:+CMSClassUnloadingEnabled
相对于并行收集器,CMS收集器默认不会对永久代进行垃圾回收。
-XX:+CMSConcurrentMTEnabled
当该标志被启用时,并发的CMS阶段将以多线程执行(因此,多个GC线程会与所有的应用程序线程并行工作)。该标志已经默认开启,如果顺序执行更好,这取决于所使用的硬件,多线程执行可以通过-XX:-CMSConcurremntMTEnabled禁用(注意是-号)。
-XX:+UseCMSCompactAtFullCollection
Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction
设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads
设定CMS的线程数量(一般情况约等于可用CPU数量)
-XX:CMSMaxAbortablePrecleanTime
当abortable-preclean阶段执行达到这个时间时才会结束
-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly来决定什么时间开始垃圾收集;如果设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当old代占用确实达到了-XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms gc;如果没有设置,系统会根据统计数据自行决定什么时候触发cms gc;因此有时会遇到设置了80%比例才cms gc,但是50%时就已经触发了,就是因为这个参数没有设置的原因。
G1(Garbage First)收集器 (标记-整理算法)
Java堆并行收集器,G1收集器是 JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。
G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
G1在压缩空间方面有优势
以下场景下G1更适合:
参数
-XX:+UseG1GC
使用G1 GC
-XX:MaxGCPauseMillis=n
设置一个暂停时间期望目标,这是一个软目标,JVM会近可能的保证这个目标
-XX:InitiatingHeapOccupancyPercent=n
内存占用达到整个堆百分之多少的时候开启一个GC周期,G1 GC会根据整个栈的占用,而不是某个代的占用情况去触发一个并发GC周期,0表示一直在 GC,默认值是45
-XX:NewRatio=n
年轻代和老年代大小的比例,默认是2
-XX:SurvivorRatio=n
eden和survivor区域空间大小的比例,默认是8
-XX:MaxTenuringThreshold=n
晋升的阈值,默认是15(译者注:一个存活对象经历多少次GC周期之后晋升到老年代)
-XX:ParallelGCThreads=n
GC在并行处理阶段试验多少个线程,默认值和平台有关。
-XX:ConcGCThreads=n
并发收集的时候使用多少个线程,默认值和平台有关。
-XX:G1ReservePercent=n
预留多少内存,防止晋升失败的情况,默认值是10
-XX:G1HeapRegionSize=n
G1 GC的堆内存会分割成均匀大小的区域,这个值设置每个划区域的大小,这个值的默认值是根据堆的大小决定的。最小值是1Mb,最大值是32Mb
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
对象优先在 Eden 区分配
大对象直接进入老年代
虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配。
长期存活对象将进入老年代
动态对象年龄判定
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代.
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。
如果不成立,则虚拟机会查看 HandlePromotionFailure设置值是否允许担保失败。
如果允许,那么会继续检查老年代最大可用的连续 空间是否大于历次晋升到老年代对象的平均大小
如果大于,将尝试着进行一次Minor GC,尽管这次 Minor GC是有风险的
如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器:
启动类加载器(Bootstrap ClassLoader):
用来加载java核心类库,无法被 java程序直接引用。
扩展类加载器(extensions class loader)
它用来加载 Java 的扩展库。 Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader)
它根据 Java 应用的类路径 (CLASSPATH) 来加载 Java类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
用户自定义类加载器
通过继承 java.lang.ClassLoader类的方式实现,然后覆盖findClass()方法。
ClassLoader超类的loadClass方法用于将类的加载操作委托给父类加载器去进行,只有该类尚未加载并且父类加载器也无法加载该类时,才调用findClass()方法。 如果要实现该方法,必须做到以下几点:
- 为来自本地文件系统或者其他来源的类加载其字节码
- 调用ClassLoader超类的defineClass()方法,向虚拟机提供字节码
加载
根据查找路径找到相应的 class 文件然后导入;
- 通过一个类的全限定名来获取其定义的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
验证
检查加载的 class 文件的正确性;
准备
给类中的静态变量分配内存空间,并将其初始化为默认值
解析
虚拟机将常量池中的符号引用替换成直接引用的过程。
符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
初始化
对静态变量和静态代码块执行初始化工作。
为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化,包括给声明类变量指定初始值,和为类static变量指定静态代码块地址。
OSGi
OSGi 是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。
如果满足下面的指标,则一般不需要进行GC优化:
线程池(java.util.concurrent.ThreadPoolExecutor)
解决用户响应时间长的问题
corePoolSize:核心线程数(最新线程数)
maximumPoolSize:最大线程数
超过这个数量的任务会被拒绝,用户可以通过 RejectedExecutionHandler接口自定义处理方式
keepAliveTime:线程保持活动的时间
workQueue:工作队列,存放执行的任务
Java线程池需要传入一个Queue参数(workQueue)用来存放执行的任务,而对Queue的不同选择,线程池有完全不同的行为:
封装方式:
连接池(org.apache.commons.dbcp.BasicDataSource)
在使用org.apache.commons.dbcp.BasicDataSource的时候,因为之前采用了默认配置,所以当访问量大时,通过JMX观察到很多Tomcat线程都阻塞在BasicDataSource使用的Apache ObjectPool的锁上,直接原因当时是因为BasicDataSource连接池的最大连接数设置的太小,默认的BasicDataSource 配置,仅使用8个最大连接。
当较长的时间不访问系统,比如2天,DB上的Mysql会断掉所以的连接,导致连接池中缓存的连接不能用。
Mysql默认支持100个链接,所以每个连接池的配置要根据集群中的机器数进行,如有2台服务器, 可每个设置为60
initialSize:一直打开的连接数
minEvictableIdleTimeMillis:该参数设置每个连接的空闲时间,超过这个时间连接将被关闭
timeBetweenEvictionRunsMillis:后台线程的运行周期,用来检测过期连接
maxActive:最大能分配的连接数
maxIdle:最大空闲数,当连接使用完毕后发现连接数大于maxIdle,连接将被直接关闭。只有 initialSize < x < maxIdle的连接将被定期检测是否超期。这个参数主要用来在峰值访问时提高吞吐量。
如何保存连接
BasicDataSource会关闭所有超期的连接,然后再打开initialSize数量的连接,这个特性与minEvictableIdleTimeMillis、 timeBetweenEvictionRunsMillis一起保证了所有超期的initialSize连接都会被重新连接,从而避免了Mysql长时间无动作会断掉连接的问题。
JVM启动参数:调整各代的内存比例和垃圾回收算法,提高吞吐量
目标:
前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。
针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率 NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize - XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize - XX:MaxNewSize设置为同样大小
如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点
本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理
在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法**: **- XX:+UseParallelOldGC,默认为Serial收集
线程堆栈的设置
每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太大了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。
可以通过下面的参数打Heap Dump信息
程序算法:改进程序逻辑算法提高性能
架构调优
代码调优
算法、数据结构
JVM调优
垃圾回收器、内存分配
数据库调优
数据表分配、sql优化
操作系统调优
吞吐量
重要指标之一,是指不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用达到的最高性能指标。
延迟
其度量标准是缩短由于垃圾啊收集引起的停顿时间或者完全消除因垃圾收集所引起的停顿,避免应用运行时发生抖动。
内存占用
垃圾收集器流畅运行所需要的内存数量。
这三个属性中,其中一个任何一个属性性能的提高,几乎都是以另外一个或者两个属性性能的损失作代价,不可兼得,具体某一个属性或者两个属性的性能对应用来说比较重要,要基于应用的业务需求来确定。
Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;
Major GC是指发生在老年代的 GC,OldGen区内存不足,触发Major GC,出现了 Major GC 通常会伴随至少一次 Minor GC。 Major GC 的速度通常会比 Minor GC 慢 10 倍以上。
Full GC
清理整个堆空间—包括年轻代和永久代
触发的场景
System.gc
promotion failed (年代晋升失败,比如eden区的存活对象晋升到S区放不下,又尝试直接晋升到Old 区又放不下,那么Promotion Failed,会触发FullGC)
CMS的Concurrent-Mode-Failure
CMS回收过程中主要分为四步:
在2中gc线程与用户线程同时执行,那么用户线程依旧可能同时产生垃圾, 如果这个垃圾较多无法放入预留的空间就会产生CMS-Mode-Failure, 切换为SerialOld单线程做mark-sweep-compact。
新生代晋升的平均大小大于老年代的剩余空间 (为了避免新生代晋升到老年代失败)
系统崩溃前的一些现象:
解决方法
生成堆的dump文件
分析dump文件
Eclipse专门的静态内存分析工具:Mat。
分析内存泄漏
通过Mat我们能清楚地看到,哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系。
回归问题
为什么崩溃前垃圾回收的时间越来越长?
根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据
为什么Full GC的次数越来越多?
因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收
为什么年老代占用的内存越来越大?
因为年轻代的内存无法被回收,越来越多地被Copy到年老代
怎样阻止内存泄露
静态常量池
即.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量, 还包含类、方法的信息,占用class文件绝大部分空间。
运行时常量池
则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。 因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。 通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。
内存溢出 out of memory
是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory
内存泄露 memory leak
是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
实现思路
栈内存溢出(StackOverflowError)
堆内存溢出(OutOfMemoryError:java heap space)
泄露则看对象如何被 GC Root 引用。
溢出则通过 调大 -Xms,-Xmx参数。
持久带内存溢出(OutOfMemoryError: PermGen space)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。