JVM
程序计数器
作用:记住下一条指令的执行地址
特点:
- 线程私有
- 不会存在内存溢出
栈
虚拟机栈
栈内的最小单元式栈帧,栈帧是每个方法运行时需要的内存,每个线程只能有一个活动栈帧,栈内存不需要垃圾回收,每个方法在执行的同时都会创建一个栈帧包括方法的局部变量,操作数据栈,动态链接(调用其他方法),出口等。
栈内存溢出:栈帧过多栈帧过大
本地方法栈
- 本地方法栈是方法上带了 native 关键字的栈字
- 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务
- native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。
- 本地方法栈中就是C和C++的代码
方法区
线程共享的区域用于存储已被JAVA虚拟机加载的类信息、常量、静态变量
当方法区无法满足内存分配要求时,抛出OutOfMemoryError异常
堆
- java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
- 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
- 从内存回收角度来看java堆可分为:新生代和老生代。
- 从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
- 无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。
- 根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
直接内存
- 直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但是既然是内存,肯定还是受本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。
- 在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native(本地)堆中来回复制数据
直接内存与堆内存的区别:申请空间直接内存优于堆内存,多次读写操作性能IO操作优于堆内存。
垃圾回收
垃圾回收机制简称GC
GC主要用于Java堆的管理。Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。GC回收是不定时的回收堆内存的不可达对象。我们只能通过System.gc建议回收对象、
System.gc()
finalize关键方法
- 每次执行GC操作之前调用的方法,做必要的清理工作
- 定义在Object方法中,所有的类都继承了它。
堆的划分
新生代
- 新生代可以分为三个区域,分别为:伊甸园,From Survivor、To Survivor,默认比例位8:1:1,幸存区的作用在后GC算法进行介绍
- 新生代寄实际可用的内存只占百分之九十
老年代
老年代是指在多次进行GC操作后任然被标记仍被判定存活的对象会被存入老年代
GC类别
Minor GC:新生代GC,新生代垃圾回收比较频繁,触发条件为伊甸园区满额了或新创建的对象大小大于伊甸园所剩余的空间
Major GC:指老年代GC,通常执行Major GC会有MinorGC,但是速度慢的多。触发条件为晋升对象的空间大于老年代所剩余的空间,或方法区所剩空间不足,CMS GC异常
Full GC:指整个堆内存空间不足,触发Full GC 但通常程序员要避免Full GC触发条件:
- 每次晋升到老年代的对象平均大小>老年代剩余空间
- MinorGC后存活的对象超过了老年代剩余空间
- 永久代空间不足
- 执行System.gc()
- CMS GC异常
- 堆内存分配很大的对象
GC算法
引用计数法
每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,计数器为0就代表该对象死亡,这时就应该对这个对象进行垃圾回收操作,但因为难以解决对象之间的相互引用所以不建议使用
标记清除
为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。
分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作
- **优点:**可以解决循环引用的问题,且可以到必要时再进行回收
- **缺点:**回收时需要stop the word,且效率不高,且会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到)
- **应用场景:**一般应用于老年代,因为老年代生命周期比较长
标记整理
标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化。标记-整理法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
**优点:**解决标记清除算法出现的内存碎片问题,
**缺点:**压缩阶段,由于移动了可用对象,需要去更新引用。
**应用场景:**该算法一般应用于老年代,因为老年代的对象生命周期比较长
复制算法
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。这个算法与标记-整理算法的区别在于,该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。
**优点:**在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。
**缺点:**会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,复制算法的性能会变得很差。
**应用场景:**复制算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用复制算法进行拷贝时效率比较高。
jvm将Heap(堆)内存划分为新生代与老年代。又将新生代划分为Eden与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 与To Survivor之间实行复制算法。不过jvm在应用复制算法时,并不是把内存按照1:1来划分的,这样太浪费内存空间了。一般的jvm都是8:1。也即是说,Eden区:From区:To区域的比例是始终有90%的空间是可以用来创建对象的,而剩下的10%用来存放回收后存活的对象。
垃圾收集器
-
新生代收集器:Serial、ParNew、Parallel Scavenge
主要算法:复制算法,不支持并行操作只有Serial为单线程其余为多线程
-
老年代收集器:CMS、Serial Old、Parallel Old
主要算法:CMS 为标记清除算法其余为标记整理算法,不支持并行操作只有Serial old为单线程其余为多线程
-
整堆收集器:G1
搭配关系:Serial—CMS、Serial Old
ParNew—CMS、Serial Old
Parallel Scavenge—Serial Old、Parallel Old
Serial
特点:
- 新生代收集器,使用复制算法收集新生代垃圾。
- 单线程的收集器,GC工作时,其它所有线程都将停止工作。
- 简单高效,适合单 CPU 环境。单线程没有线程交互的开销,因此拥有最高的单线程收集效率。
ParNew
特点:
- 新生代收集器。ParNew垃圾收集器是Serial收集器的多线程版本,采用复制算法。
- 除了多线程外,其余的行为、特点和Serial收集器一样。
- 只有它能与 CMS 收集器配合使用。
- 但在单个CPU环境中,不比Serail收集器好,多线程使用它比较好。
Parallel Scavenge
特点:
- 新生代收集器。
- 采用复制算法。
- 多线程收集。
- 与ParNew 不同的是:高吞吐量为目标,(减少垃圾收集时间,让用户代码获得更长的运行时间)
Serial Old
特点:
- 老年代收集器, 采用"标记-整理"算法。
- 单线程收集。
Parallel Old
特点:
- 针对老年代。
- 采用"标记-整理"算法。
- 多线程收集。
- 但在单个CPU环境中,不比Serial Old收集器好,多线程使用它比较好。
CMS
特点:
- 针对老年代,采用标记-清楚法清除垃圾;
- 基于"标记-清除"算法(不进行压缩操作,产生内存碎片);
- 以获取最短回收停顿时间为目标;
- 并发收集、低停顿;
- CMS收集器有3个明显的缺点:1.对CPU资源非常敏感、2.无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败、3.产生大量内存碎片
- 垃圾收集线程与用户线程(基本上)可以同时工作
G1
-
G1 收集器:分代收集器。当今收集器技术发展最前沿成果之一,是一款面向服务端应用的垃圾收集器。G1可以说是CMS的终极改进版,解决了CMS内存碎片、更多的内存空间登问题。虽然流程与CMS比较相似,但底层的原理已是完全不同。
-
特点:
- 能充分利用多CPU、多核环境下的硬件优势;
- 可以并行来缩短(Stop The World)停顿时间;
- 也可以并发让垃圾收集与用户程序同时进行;
- 分代收集,收集范围包括新生代和老年代
- 能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
- 能够采用不同方式处理不同时期的对象;
- 应用场景可以面向服务端应用,针对具有大内存、多处理器的机器;
- 采用标记-整理 + 复制算法来回收垃圾
//如何设置JVM参数底下会讲解:这里只是列举一部分参数:
设置垃圾收集器:"-XX:+UseG1GC":指定使用G1收集器;
设置垃圾收集器参数:"-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
设置垃圾收集器参数:"-XX:MaxGCPauseMillis":为G1设置暂停时间目标,默认值为200毫秒;
设置垃圾收集器参数:"-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region
JVM参数设置
#常用的设置
-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
-Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
-XX:NewSize=n 设置年轻代初始化大小大小
-XX:MaxNewSize=n 设置年轻代最大值
-XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4
-XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8
-Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
-XX:ThreadStackSize=n 线程堆栈大小
-XX:PermSize=n 设置持久代初始值
-XX:MaxPermSize=n 设置持久代大小
-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。
#下面是一些不常用的
-XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
-XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用
等等等......
etter方法性能
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用
等等等…