JVM是按照运行时数据的存储结构来划分内存结构的,JVM在运行Java程序时,将它们划分成几种不同
格式的数据,分别存储在不同的区域,这些数据统一称为运行时数据,运行时数据包括Java程序本身的数据
信息和JVM运行Java需要的额外的数据信息,如下图所示1
JMM
Java内存结构 Java的主内存和私有内存
什么是线程安全?你的预料结果和实际结果一样
一个主内存,3个线程,分别是三个私有栈,分别读主内存的变量等X,
三个线程来读的话,比如x=x+1.x=1.x=x-1那样就乱了
所以就有了锁,什么自旋锁,可重入锁等等
JVM运行时数据区
程序计数器 线程私有 就是记录程序执行的位置,
JAVA虚拟机栈,线程私有 主要是数据结构,先进后出, 这里考虑到JAVA的基本数据类型和对象,对象使用指针,对象不用
本地方法栈-线程私有 主要是操作系统本身的,主要利用JRE技术编译成本地代码,变成操作系统本身能执行的代码
JAVA堆,线程共有 我们线层调优主要用他方法区,线程公有 主要是JAVA代码,比如描述
下面将JVM内存分配
1栈内存分配 默认分配栈的内存是1M,最大是512M
保存参数,局部变量,中间计算过程和其他数据,退出方法的时候,修改栈顶指针就可以把栈帧中的内存销毁
栈的有点:存取速度比堆块,仅次于寄存器,栈数据可以共享
栈的缺点,存在栈的数据大小,生存期是在编译时就确定的,导致其缺乏灵活性
2堆内存分配
堆得优点:动态地分配内存大小,生存期不必事先告诉编译器,它是在运行期动态分配的,垃圾回收
器会自动收走不再使用的空间区域
堆得缺点:运行时动态分配内存,在分配和销毁时都要占用时间,因此堆得效率较低
3类似JAVA的final,在类加载过程中就分配好内存了
jvm堆结构
Eden from to (survivor spaces) old permanaent
java的引用,包括强引用,弱引用,据ibm统计,80%的对象是朝生暮死的,根据不同的区域来引用不同的算法
java堆结构和垃圾回收
java堆得调优的目的就是为了减少fullGc
jvm堆配置参数
1 -xmx 初始堆得大小,默认物理内存的64分之一(<1GB)
2 -xms 最大堆大小 默认物理内存的4分之一(小于1GB),实际中建议不大于4GB
3 一般建议设置 -xmx=-xms 好处是避免每次在gc后,调整堆得大小,
减少系统内存分配开销
4整个堆大小=年轻代大小+年老代大小+持久代大小
jvm新生代(young generation)
1新生代=1个eden区+2个survivor区
2-xmn年轻代大小
3-xx:newratio
年轻代(包括Eden和两个survivor区)与年老代的比值(除去持久代)xms=xmx并且设置了xmn的情况下,改参数不需要进行设置
-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
4-xx:survivorratio
Eden区与survivor区大小的比值,设置为8.则2个survivor区与一个
Eden区的比值为2:8.一个survivor区占整个年轻代的1:10
5用来存放jvm刚分配的java对象
java老年代(tenured generation)
1老年代=整个堆-年轻代大小-持久代大小
2年轻代中经过垃圾回收没有回收掉的对象,被复制到年老代
这里有缓存对象针对弱引用,也就是关系到Java对象引用的知识点,
3老年代存储对象比年轻代年龄大的多,而且不乏大对象
4新建的对象也有可能直接进入老年代
4.1大对象 可通过启动参数设置-xx:pretenureSize Threshold=1024 (单位是字节,默认是0)来代表超过多大时就不在新生代分配,而是直接在老年代分配
4.2大的数组对象,且数组中无引用外部对象(数组是连续内存分配比较特殊,占用空间比较大)
5老年代大小无配置参数
java持久代(perm generation)
1持久代=整个堆-年轻代大小-老年代大小
2-xx:permsize -xx:maxpersize
设置持久代的大小,一般情况推荐把-xx:permSize设置成xx:maxPermSize的值为相同的值,
因为永久代大小的调整也会导致堆内存需要触发fullgc
3存放class,method元信息,其大小与项目的规模,类,方法的数量有关,一般设置为128M就足够,设置原则是预留30%的空间
4永久代的回收方式
4.1 常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收了
4.2 对于无用的类进行回收,必须保证3点
类的所有实列都已经被回收
加载类的classloader已经被回收
类对象的class对象没有被引用(即没有通过反射引用该类的地方)
jvm内存垃圾回收
jvm垃圾收集算法
1引用计数算法
每个对象都有一个引用计数属性,新增一个引用时计数加1,引用释放时减1,计数为0时可以回收,
此方法简单,无法解决对象相互循环引用的问题,还有一个问题是如何解决精准计数(1.2已经不可用)
2根搜索算法
从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象
在java语言中,Gc Roots包括
虚拟机栈中引用的对象
方法区中类静态属性实体引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
jvm垃圾回收算法
1复制算法(copying)
复制算法采用从根集合扫描,并将存活对象复制到一块心得,没有使用过的空间中
这种算法当控件存活对象比较少时,极为高效,但是带来的成本是需要一块内存交换空间用于进行对象
的移动。此算法用于新生代内存回收,从E区回收到S0或者S1
2标记清除算法(mark-sweep)
标记-清除算法采用从根集合进行扫描,对存活的对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,
标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片
3标记整理压缩算法(mark-Compac)
标记整理算法采用标记-清除算法一样的方式进行对象的标记,但是在清除时不同,在回收不存活的对象占用的空间后,会将所有的存货的对象往左端空闲空间移动,并更新对应的指针,标记-整理算法是在标记清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题
名称解释
1串行回收
gc单线程内存回收,会暂停所有用户的线程 stop the word
2并行回收
收集是指多个GC线程并行工作,但此时用户线程是暂停的,所以 serial是串行的,parallel收集器是并行的,而cms收集器是并发的
3并发回收
是指用户线程和Gc线程同时执行,不一定是并行,可能交替,但总体上是在同时进行的),不需要停顿用户线程(其实在CMS中用户线程还是需要停顿的,只是非常短,GC线程在另一个CPU上执行)
那个jvm常见垃圾垃圾回收器
serial回收器(串行回收器)
1是一个单线程的收集器,只能使用一个cpu或一条线程去完成垃圾的收集,在进行垃圾收集时,必须暂停所有其他工作线程,直到收集完成
2缺点:stop-the-world
3优势:简单,对于但cpu的情况,由于没有多线程交互开销,反而可以更搞笑,是client模式下默认的新生代的收集器
新生代serial回收器
1-xx:+useSerialGc来开启
serial New+Serial Old的收集器组合进行内存回收
2使用复制算法
3独占式的垃圾回收,(一个线程进行Gc,串行其他工作线程暂停)
老年代serial回收器
1-xx:+useseialGc来开启
serail New+serial old的收集器组合进行内存回收
2使用标记-压缩算法
3串行的,独占式的垃圾回收器 因为内存比较大的原因,回收比新生代慢
ParNew回收器(并行回收器)
并行回收器也是独占式的回收器,在收集过程中,应用程序会全部暂停,但由于并行回收器使用多线程进行垃圾回收,因为在并发能力较强的cpu上,它产生的停顿时间要短于串行回收器。而在单cpu或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力。它的实际表现很可能比串行回收器差。,
新生代ParNew回收器
1-xx:+UseParNewGc开启
新生代使用并行回收收集器,老年代使用串行收集器
2-XX:ParalleGcThreads指定线程数
默认最好与cpu的数量相当,避免过多的线程数影响垃圾收集的性能
3使用复制算法
4并行的,独占式的垃圾回收器
新生代Parallel Scavenge回收器
1吞吐量优先回收器
关注cpu吞吐量,即运行用户代码的时间/总时间,比如:jvm运行100分钟,其中运行用户代码99分钟,垃圾
收集1分钟,则吞吐量是99%。这种收集器能最高效率的利用cpu,适合运行后台计算
2-xx:+useParallelGc开启
使用Parallel Scavenge+Serial Old收集器组合回收垃圾,这也是在server模式下的默认值
3-xx:GcTimeRatio
来设置用户执行时间占总时间的比例,默认99,即1%的时间用来进行垃圾回收
4-xx:MaxGcPauseMilis设置Gc的最大停顿时间
5使用复制算法
老年代parallel old回收器
1-xx:+UseParallelOldGc开启
使用parallel scavenge+parallel old组合收集器进行收集
2使用标记整理算法
3并行的,独占式的垃圾回收器
cms(并发标记清除)回收器
运行过程分为4个阶段
初始标记: 值标记Gc Roots能直接关联到对象
并发标记 进行GC RootsTracing的过程
重新标记:修正并发标记期间用户程序继续运行而导致标记发生改变的那一部分对象的标记
并发清除
其中标记和重新标记2个阶段仍然需要stop-the-world,整个过程中耗时最长的并发标记和并发清除过程中收集器都可以和用户线程一起工作
CMS(并发标记清除)回收器
1标记-清除算法,同时它又是一个使用多线程并发回收的垃圾收集器
2-XX:ParallelCMSThreads
手工设定CMS的线程数量,CMS默认启动的线程数是(ParallelGcThreads+3)/4)
3-XX:UseConcMarkSweepGC开启
使用ParNew+cms+Serial Old的收集器组合进行内存回收,Serial Old作为cms出现
“Concurrent Mode Failure”失败后的后备收集器使用
4-XX:Cmsinitiating OccupancyFraction
设置cms收集器在老年代空间被使用多少后出发垃圾收集,默认值是68%,仅在cms收集器时有效,
5:-XX:UseCmsCompactAtFullCollection
由于cms收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在cms收集器时有效
6-XX:+CmsFullGcBeforeCompaction
设置cms收集器在运行若干次垃圾收集后再近些一次内存碎片整理过程,通常与UseComPactAfFullCollection参数一起使用
7-XX:cmsinitiaingPermOccupancyFranction
设置Perm Gen使用到达多少比率时触发,默认是92%
要根据不同的用户场景和日志和Gc指标,去调到合适的值,不是瞎调
Gc性能指标
吞吐量 应用花在非Gc上的时间百分比
Gc负荷,与吞吐量相反,指应用花在GC上的时间百分比
暂停时间,应用花在Gc stop-the-world的时间
Gc频率:顾名思义
反应速度:从一个对象变成垃圾到这个对象被回收的时间
一个交互式的应用要求暂停时间越少越好,然后一个非交互性的应用,当然是Gc负荷越低越好
一个实时系统对暂停时间和Gc负荷的要求,都越低越好