一、基础术语
1.1 进程和线程
进程:计算机内部每个正在系统上运行的程序都是一个进程,每个进程包含一个到多个线程
线程:线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作称为多线程
1.2 内存泄漏(Memory Leak):
用动态存储分配函数动态开辟的空间,在使用完毕后未被释放,结果导致一直占据内存单元,一直持续到程序结束,常见的异
常OutOfMemory
1.3 强引用(Strong Reference):
在一个线程内,无需引用直接可以使用的对象,除非引用不存在,否则强引用不会被GC清理,变量
的声明就是强引用,String str = new String("str")
1.4 软引用(Soft Reference):
JVM抛出OOM异常之前,GC会清理所有软引用对象。垃圾回收器在某个时刻决定回收软可达的对象的时
候,会清理软引用,Java虚拟机会尽量让软引用存活的时间长一点,迫不得已在清理
1.5 弱引用(Weak Reference):
弱引用对象与软引用对象最大的不用在于,当GC在进行回收时,需要通过算法检查是否回收软引用,
而对于弱引用总是进行回收。
1.6 虚引用(Phantom Reference):
又称为幽灵引用,主要的目的是在一个对象所占的内存被实际回收之前得到通知,从而可以进行一
些相关的清理工作。虚引用在创建时必须提供一个引用队列作为参数,其次虚引用对象的get方法总是返回null。
1.7 finalization机制:
finalization机制允许开发人员提供对象销毁之前的自定义处理逻辑。Object类提供了finalize方法添加用户自定义销毁
逻辑,如果一个类有特殊的销毁逻辑,可以覆写finalize方法
1.8 Interned Strings
String 类型的常量池,直接使用双引号和使用String提供的intern()方法的字符串会放入常量池
1.9 并行与并发
*并行是指两个或者多个事件在同一时刻发生,而并发是指两个或多个事件在同一时间间隔发生
*并行是在不同实体上的多个事件,并发实在同一实体上的多个事件
*并行是在一台处理器上”同时“处理多个任务,并发是在多台处理器上同时处理多个任务,如hadoop集群
1.10根集合与堆栈
*栈是运行时的单位,而堆是存储的单位
*栈解决程序运行的问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,及数据怎么放、放到哪
1.11 回收算法
标记-清除(Make-Sweep)算法:首先标记除存活的对象,那些没有被标记的就可以被收集了
复制(Copying)算法:将内存分为两个部分,收集的时候,存活的对象从一部分移动到另一部分
标记-整理(Mark-Compact)算法:结合了标记-清除与复制两个算法的优点,分为两个阶段,第一阶段从根节点开始标记所有被
引用的对象,地二个阶段遍历整个堆,清除未标记对象并把存活对象”压缩“到堆的其中一块,
按顺序排放
分代收集(Generational Collecting)算法:java使用的算法
1.12 年轻代(Young Generation)
年轻代分为三部分一个Eden两个Servivor,默认比例为8:1,当GC发生在年轻代对象的行为称为Minor GC。
1.13 老年代(Old Generation)
当GC发生在老年代时称为Major GC 或者 Full GC
1.14 对象提升(Promotion)规则
如果对象在Eden出生并经过一次Minor GC 后仍然存活,并且能被Servivor容纳的话,将被移动到Survivor空间中,并将对象的年龄加1.
对象在Survivor区中每熬过一次Minor GC 年龄就增加1岁,当对象的年龄增加到一定程度(默认15岁)时,就会被提升到老年代中。
1.15 Full GC
除了调用System.gc外,触发Full GC 执行的情况有如下四种
*老年代空间不足:老年代空间只有在年轻代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后,空间仍然不足
则会报Java.lang.OutOfMemoryError:PermGen space
*永久代空间满
*CMS GC 时出现PromotionFailed 和 Goncurrent Mode Failure
*统计得到Minor GC 晋升到老年代的平均大小大于老年代的剩余空间
1.16 Stop the World (STW)
GC 时系统会进入的停顿的状态
1.17 对象存活判断
引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时,可以回收。有循环引用的问题
可达性分析(Reachability Analysis):从GC Roots 开始向下搜索,搜索所有走过的路径,称为引用链。当一个对象到GC Roots没有
任何引用链相连时,则证明此对象是不可达对象。
1.18 堆外内存(off-heap Memory)
JVM内部会把所有的内存分为Java使用的堆内存和Native是用的内存,他们之间是不能共享的。Native内存用完时,如果Java堆有空闲
的内存,这是Native会重新向JVM申请,而不是直接使用Java堆内存。
线程栈、应用程序代码、NIO缓存用的都是对外内存。
1.19 markOop
markOop描述了一个对象(也包括了Class)的状态信息,Java语法层面的每个对象或者Class在JVM的结构表示中都会包含一个markOop
作为Header,当然还有一些其他的JVM数据结构也用它做Header。markOop由32位或者64位构成。
markOop的值根据所描述的对象的类型以及作用的不同而不同。就算在一个对象里,他的值也是可能会不断变化的,比如锁对象,在
一开始创建的时候其实不知道是锁对象,会当成一个正常的对象来创建,在是随着执行到synchronized的代理逻辑时,就知道其实是一个
锁对象了,他的值就改变了,该值是对应栈帧结构里的监控对象列表里的某一个地址。
1.2 Java虚拟机内存模型
Java虚拟机将其内存数据分为程序计数器、虚拟机栈、本地方法栈、Java堆、方法区
*程序计数器:用于存放下一条运行的命令
*虚拟机栈、本地方法栈:用于存放函数调用堆栈信息
*java堆:用于存放Java程序运行时所需的对象等数据
*方法区:用于存放程序的类元数据信息
1.3 程序计数器
每个线程都需要有一个独立的程序计数器,个个线程之间互不影响,它是线程私有的,所以生命周期与线程一致
1.4 虚拟机栈
JVM的架构是基于栈的,即程序指令的每一个操作都要经过入栈和出栈这样的组合型操作才能完成。栈的优势是访问速度比堆块
,它仅次于寄存器。虚拟机栈主要存放一些基本类型的变量,int、short、long、byte、float、double、boolen
、char、及对象的引用。Java虚拟机允许Java栈的大小是动态的或者固定不变的。在Java虚拟机中定义了两种异常与栈有关,即
StackoverFlowError和OutOfMemoryError,如果线程在计算过程中,请求的栈深度大于最大可用栈的深度,则程序运行过程中会
抛出StackoverFlowError异常。在扩展栈的过程中没有足够的内存空间来支持栈的发展,则抛出OutOfMemoryError异常。
1.5 本地方法栈(Native Method Stacks)
本地方法栈和Java虚拟机栈的功能很相似,Java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用。
本地方法不是用Java实现的,而是使用c实现的。
1.6 堆
堆在JVM规范里是一种通用性的内存池(也存在与RAM中),用于存放所有的Java对象。堆是一个运行时数据区,类的对象从中分配空间
,这些对象通过New关键字创建,不需要程序代码显示的释放。堆是由垃圾回收负责的,堆的优势是可以动态地分配内存大小,生产周期
也不需要事先告诉编译器。由于它是运行时动态分配内存的,Java的垃圾收集器会自动收走那些不再使用的数据。但缺点是,由于要在运行
时动态分配内存,所以数据访问的速度较慢。大多数的虚拟机里,Java中的对象和数组都存放在堆中
堆不同于栈的优势是,虚拟机不需要知道从堆内存要分配多少内存区域,也不必知道存储的数据在堆内存活的时间有多长。因此,在
堆内分配存储相对于栈来说,有较大的灵活性。
Java堆区在JVM启动的是否被创建,它只要求逻辑上是连续的,在物理空间上可以不连续。所有线程共享Java堆。
1.7 方法区
方法去主要保存的信息是类的元数据。被所有线程共享。
*类型信息:类的完整名称、父类的完整名称、类型修饰符、类型的直接接口、类表
*常量池:这个类方法域等信息所引用的常量信息。
*域信息:域名称、域类型、域修饰符
*方法信息:方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法栈的局部变量区大小以及异常表
在HotSpot虚拟机中,方法区也被称为永久代,是一块独立于Java堆的内存空间。
GC针对永久区的回收,通常主要从两个方面分析:一是CG对永久区常量池的回收,二是永久区对类元数据的回收。
HotSpot虚拟机对常量池的回收策略是明确的,只要 常量池中的常量没有被任何地方引用,就可以回收。
1.8 引用计数法(垃圾标记算法)
在GC执行垃圾回收之前,首先要区分出内存中哪些是存活对象,哪些是已经死亡对象,CG才会在执行垃圾回收时,释放掉其所占用的
内存空间,因此这个过程我们可以称为垃圾标记阶段。对于一个对象A,只要任何一个对象引用了A,则A的引用计数器就加1,当引用失效
时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能在被使用。引用计数法无法处理循环引用的问题。
1.9 根搜索算法(垃圾标记算法)
HotSpot和大部分JVM都是使用根搜索算法作为垃圾标记的算法。根搜索算法是以根对象集合为起始点,按照从上至下的方式搜索被根
对象集合所连接的目标是否可达(使用根搜索算法后,内存中的存活对象都会被根对象集合直接或间接连接着),如果目标对象不可达,就
意味着该对象已经死亡。根对象集合中包含了5个元素,Java栈内的对象引用、本地方法栈内对象的引用、运行常量池中的对象引用、方法
区中对象的引用、方法区中类静态属性的对象引用以及一个类对应唯一数据类型的Class对象。
1.10 分代收集算法(Generational Collecting)(垃圾回收算法)
它将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率教高的复制算法。
当一个对象经过几次回收后依然存活,对象就会被放入称为老年代的内存空间。老年代使用的标记-压缩算法提高垃圾回收效率。
2.1 GC概念
CG(Garbage Collection)就是JVM中自动内存管理机制的具体实现。在HotSpot中,GCd的工作任务主要划分为两部分,内存的动态分配
和垃圾回收。在内存执行分配之前,GC首先会对内存空间进行划分。JVM堆如果还要进一步划分的话,还可以划分为年轻代(YoungGen)和
老年代(OldGen),其中年轻代又可以划分为Eden空间、From Survivor空间和To Survivor空间。
2.2 Serial收集器
Serial收集器默认为HotSpot中Client模式下的年轻代垃圾收集器(适合在单核下使用)
2.3 ParNew 收集器
是Serial收集器的多线程版本。
2.4 Parallel收集器
和ParNew收集器不同,Parallel收集器可以控制程序的吞吐量大小,和STW的时间间隔。HotSpot的年轻代中除了拥有ParNew收集器
是基于并行回收的以外,Parallel收集器同样采用了复制算法、并行回收和STW机制。和ParNew收集器不同的是,Parallel收集器可以
控制程序的吞吐量大小,因此它也被称为吞吐量优先的垃圾收集器。在程序开发中,开发人员可以通过选项-XX:GCTimeRatio设置执行
回收的时间所占比例,也就是控制GC的执行频率,默认值为99,也就是说,将有1%的时间用来执行垃圾回收,还提供选择
-XX:MaxGCPauseMills设置执行内存回收时STW的暂停时间阀值,如果指定了改选项,Parallel收集器将会尽可能地在设定时间范围内
完成回收。
Parallel收集器也提供用于执行老年代垃圾收集的Parallel Old收集器,Parallel Old采用标记-压缩算法,但同样也是基于并行
回收和STW机制。在程序吞吐量优先的场景中,Parallel收集器和Parallel Old收集器的组合,在Server模式下的回收性能不错。在
程序开发过程中,可以通过选项-XX:UseParallelGC手动指定Parallel收集器执行内存回收的任务。使用选项-XX:UseAdaptiveSizePolicy
可以打开自适应GC策略,在这种模式下,年轻大的大小、Eden和servivor的比例、晋升老年代对象年龄等参数会被自动调整,以达到
在堆大小、吞吐量和停顿时间之间的平衡点,在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、
目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills),让虚拟机自己完成调优工作。
2.5 CMS 收集器
CMS天生为并发而生,低延迟是他的优势,不过垃圾收集算法没有采用标记-复制算法,而是采用标记-清除算法。CMS包括四个阶段,
初始阶段、并发阶段、再标记阶段、并发消除阶段。
CMS收集器的回收周期以一个称为初始标记的阶段开始,在这个阶段中,程序中所有的工作线程都会因为Stop-the-Word机制出现短暂
的暂停,这个阶段主要的任务就是标记出内存中那些被根对象集合所连接的目标对象是否可达,一旦标记完成之后就会恢复之前被暂停的
所有应用程序线程。接下来将会进入并发标记阶段,而这个阶段的主要任务就是将之前的不可达的对象标记为垃圾对象。在CMS最终执行
回收之前,尽管看上去这些垃圾对象都已经被成功标记了,但是由于在并发标记阶段中,程序的工作线程和垃圾收集线程同时运行,或者
交叉运行,因此并发标记阶段无法有效确保之前标记为垃圾的无用对象的引用关系遭到更改,为你解决这个问题,CMS会进入到再次标记
阶段,这样一来,程序会因为Stop-the-World机制再次出现短暂的暂停,以确保这些垃圾对象能够成功且正确的标记。
CMS收集器提供选项-XX:UseCmsCompactAtFullCollection,用于指定执行完Full GC之后是否对内存空间进行压缩整理
CMS收集器提供选项-XX:CMSFullGCCsBeforeCompaction用于设置执行了对少次Full GC后对内存进行压缩整理
CMS收集器提供选项-XX:CMSInitiatingOccupanyFraction,用于设置当老年代中的内存使用率达到多少百分比的时候执行内存回收,默认92
CMS收集器不会等到堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到某一阀值时,便开始进行回收,以确保应用程序在CMS工作过程
中依然有足够的空间支持应用程序运行。如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时
CMS会回收失败,JVM将启动老年代串行收集器进行垃圾回收,如果这样,程序将完全中断。
2.6 Garbage First(G1) GC
G1GC切分堆内存为对个区间(Region),从而避免了很多GC操作在整个Java堆或者整个年代进行。在JVM启动时不需立即指定哪些Region属于
年轻代,哪些Region属于老年代,因为无论年轻代或老年代,他们都不需要一大块连续的内存块,只是有一系列的Region组成而已。
G1的年轻代收集阶段是一个并行的独占收集器。和其他HotSpot垃圾收集器一样,当一个年轻代收集进行时,整个年轻代会被回收,
所有应该程序线程会中断,G1 GC会启用多线程执行年轻代回收。和年轻代不同,老年代的G1回收器和其他HotSpot不同,G1的老年代回收期
不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。这个老年代的Region和年轻代一起被回收。
G1的最大贡献是它可以让我们设置最大停顿时间,只要设置了这个时间,G1就会通过自动调整年轻代空间大小和整体Java堆空间大小
来匹配这个目标停顿时间。比如你设置了一个很短的停顿时间,G1会设置比较小的年轻代、比较大的整个Java堆空间,对应的老年代也会比较大
总得来说,G1和CMS的目标是构建针对大内存回收的较短停顿时间,如果想要提高应用的内部吞吐量,也可以忍受较长的停顿时间,那么还是
选择Parallel GC吧,毕竟他是专门为了高吞吐量而研发的。
3.1 -XX:PrintGCDetails
该选项用于记录GC运行时的详细数据信息并输出
3.2 -Xloggc
以文件形式保存GC日志 -Xloggc:gc.log
3.3 -XX:+PrintGCApplicationStoppedTime
输出GC造成的应用程序暂停间隔。一般和-XX:+PrintGCApplicationConcurrentTime一起使用。
3.4 -XX:ConcGCThreads
用来设置用于执行GC线程的数量
3.5 -XX:G1HeapRegionSize
G1GC用来设置Region的大小1M到32M之间
3.6 -XX:G1HeapWastePercent
这个选项控制G1GC不回收的空闲内存比例,默认是堆内存的5%
07-28
07-28
07-28
07-28
07-28
07-28