Tip: jvm是一个庞大的知识体系,其中与开发比较有关联的是内存部分,所以本篇着重介绍内存部分,实践部分极少
所以本篇适合初学者或者入门者,用于理解各种知识点和面试时候可以叨一叨
一.结构
1.方法区(永久区):
方法区和堆类似,是各个线程共享的内存区域,它用于存储类信息,常量,静态变量,即时编译器编译后的代码(项目发布jsp等会被解析成java代码,大量内容会进进出出,就有可能出现方法区溢出的异常)等数据。分析下Java虚拟机规范,之所以把方法区描述为堆的一个逻辑部分,应该觉得她们都是存储数据的角度出发的。一个存储对象数据(堆),一个存储静态信息(方法区)。
2.堆 (线程共享)
对于大多数应用来说,堆是Java虚拟机管理的内存的最大一块,这块区域随着虚拟机的启动而创建。我们创建的对象和数组就是存放在堆里面。它是一块共享的区域,操作共享区域的成员就有了锁和同步(线程的安全问题)。
堆是垃圾回收器管理(GC)的主要区域。新生代、老生代、永久代的概念就是在堆里面,下文会详细介绍这些
堆内存可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
3.栈(线程私有)
栈是线程私有的,它的生命周期与线程相同。栈描述的是Java方法执行时的内存模型,每个方法执行时都会创建一个栈帧(Stack Frame)用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个线程在执行一个方法时,都意味着有一个栈帧在当前线程对应的栈帧中入栈和出栈。
4.程序计数器
记录程序执行到哪一行
5.本地方法栈
jvm是Java执行的平台,但是程序也会调用一些本地操作系统的方法(在windows就是windows中的本地方法,linux则是linux的本地方法),例如一些IO操作等,这些方法就在本地方法栈中。
详细介绍堆内存
1.新生代:
这里也划分三个区域
Eden:对象进入的时候在这里生活,非常欢乐
from:这时候GC垃圾回收来了,他认为不需要的就进行移动回收
to:复制算法使用的空间(下文详细介绍)
这里from:to:Eden大小默认1:1:8 可以自行进行配置
2.老年代
经过N次(默认15)的垃圾回收还留存的,会送往老年代
3.永久区
这里也有垃圾回收机制,本文不多做介绍。有兴趣自行搜索资料
二.垃圾回收(GC)
我们java程序员写程序是不用关注内存(例如C语言需要,这是一件十分棘手的事情),这得益于垃圾回收机制。
1.如何判定是垃圾(这里直说大致原理)
依靠根节点搜索:从程序最根本的那个类开始,看他能够到达哪一些对象,这些可到达的对象就会认为是有用的对象,到达不了的认为是垃圾。(更详细的请自行搜索资料)
2.回收算法(如何回收)
Tip:内存空间里面就是一个个内存地址,这里把内存地址显示为一个个小方格,方便理解
第一种算法 : 标记清除法
1>.标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
2>.在标记完成后统一回收所有被标记的对象
缺点:一个是效率问题,标记和清除两个过程的效率都不高;
另一个是空间问题,不难想象抽走黑色部分,空出来的部分是不连续的,这时候大对象的存储很可能无法找到足够的连续内存。
第二种算法: 标记整理法
把有用的找出来,全部往一个方向移动并且覆盖掉没用的,然后剩下的位置全部清理掉。
缺点:效率慢。并且移动他的内存空间会对用户产生速度的影响。
第三种算法:复制算法
1>将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
1>垃圾回收时 把有用的拷贝到另一半空的空间,然后清理全部,再把有用的拷贝回来(图中没显示拷贝回来的)。
优点:往内存空白处复制操作效率高效,实现简单。并且时常内存中垃圾占大多数,存活的是少数,也就是不一定要留下一半空间,可以选择留下更少的空间。
缺点:复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低
Tip:
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代中因为对象存活率高、没有额外空间对它进行分配担保,就经常使用“标记—清理”或者“标记—整理”算法来进行回收。
3.垃圾回收具体的实现(垃圾回收器)
-----这里只做最简单介绍,它们的实现是非常复杂的,有兴趣可以自行去了解
垃圾回收器有以下几种
1.Serial收集器:
1>、是一个单线程(它工作时,用户线程不工作)的收集器,用户那边称作“Stop The World”
2>、对于运行在Client模式下的虚拟机来说是一个很好的选择
3>、简单而高效
2.Serial Old收集器:
1>、Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
2>、主要意义也是在于给Client模式下的虚拟机使用。
3>、如果在Server模式下,那么它主要还有两大用途:
一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用[1],
另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
3.CMS收集器一款优秀的收集器(服务端默认)
1>、以获取最短回收停顿时间为目标的收集器。
2>、重视服务的响应速度,希望系统停顿时间最短。
3>、基于“标记—清除”算法实现的。
4>、CMS收集器的内存回收过程是与用户线程一起并发执行的。
5>、优点:并发收集、低停顿
4.G1(Garbage-First)收集器
1>、当今收集器技术发展的最前沿成果之一
2>、G1是一款面向服务端应用的垃圾收集器。
3>、优点:
并行与并发:充分利用多CPU、多核环境下的硬件优势
分代收集:不需要其他收集器配合就能独立管理整个GC堆
空间整合:“标记—整理”算法实现的收集器,局部上基于“复制”算法不会产生内存空间碎片
可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
Tip:还有另外多种收集器这里不过多介绍。
垃圾收集器参数总结
收集器设置:
-XX:+UseSerialGC:年轻串行(Serial),老年串行(Serial Old)
-XX:+UseParNewGC:年轻并行(ParNew),老年串行(Serial Old)
-XX:+UseConcMarkSweepGC:年轻并行(ParNew),老年串行(CMS),备份(Serial Old)
-XX:+UseParallelGC:年轻并行吞吐(Parallel Scavenge),老年串行(Serial Old)
-XX:+UseParalledlOldGC:年轻并行吞吐(Parallel Scavenge),老年并行吞吐(Parallel Old)
收集器参数:
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
三.Jvm参数列表
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-Xmx3550m:最大堆内存为3550M。
-Xms3550m:初始堆内存为3550m。
此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。
整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。
JDK5.0以后每个线程堆栈大小为1M,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000左右。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。
设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=15:设置垃圾最大年龄。
如果设置为0的话,则年轻代对象不经过Survivor区,直 接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象 再年轻代的存活时间,增加在年轻代即被回收的概论。