JVM
JVM内存模型
简图
程序计数器:jvm中的PC寄存器是对物理PC寄存器的一种抽象模拟。用来存储指向下一条指令的地址,由执行引擎读取下一条指令。不存在内存溢出
本地方法栈:支持对本地方法的调用
虚拟机栈:早期也叫java栈。内部保存栈帧,一个栈帧对应一个java方法。栈解决程序运行问题,即程序如何执行,如何处理数据。只有进栈出栈,不需要垃圾回收,溢出就挂了,发生OOM。遵循先进后出。栈帧中存储着局部变量表、操作数栈、动态链接、返回地址。。。 方法执行完对应栈帧出栈,就是释放这个方法所占用的内存。
堆:用来存放java对象实例。一个jvm实例只存在一个堆内存。所有java线程共享java堆
方法区:所有线程共享,存储元数据,例如类结构信息、以及对应的运行时常量池、字段、方法代码等jdk7叫永久代,jdk8叫元空间,元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。
线程共享:堆、方法区
线程隔离:虚拟机栈、本地方法栈、程序计数器
不会发生OOM:程序计数器 仅仅是储指向下一条指令的地址
堆内存细分
逻辑上的
对象分配过程
年龄计数器:经过一次GC后还存在,对象年龄+1,只在判断是否需要进入老年代时使用
类加载过程
jvm跨平台
不要求源程序必须用java来写,只要提供遵循jvm规范的字节码文件即可
类加载器
jdk中的本地方法类一般由根加载器(Bootstrp loader)装载
jdk中内部实现的扩展类一般由扩展加载器(ExtClassLoader)实现装载
程序中的类文件则由系统加载器(AppClassLoader)实现装载
为什么要自定义类加载器
隔离加载类 确保中间件和应用的jar包不冲突 多框架可能有类的路径和名称一致
修改类加载的方式 启动类加载器是必须的,其他类加载器不是必须的,使用自定义类加载器在需要的时候进行动态加载
扩展加载源 扩展字节码的来源
防止源码泄露 java代码容易被反编译篡改,防止这这种情况,对字节码进行加密,自己运行的时候还原成内存中的类需要解密
双亲委派机制
java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。
而且加载某个类的class文件时,java虚拟机采用的是双亲委派机制,即把请求交由父类加载器处理,父类加载器加载失败才会交由子加载器处理
——它是一种委派模式
自定义一个java.lang包下的String类,new String()的时候,AppclassLoader加载的是核心类库下的String类而不是自己创建的
——沙箱安全机制 防止恶意攻击
优势:
避免类重复加载
保护程序安全,防止核心API被随意篡改
垃圾回收机制
未曾手动清除对象 -》 jvm垃圾回收机制进行释放
什么是垃圾
垃圾值得是在运行程序中没有任何指针指向的对象,即不再使用的对象,这个对象就是需要被垃圾回收
怎么判断?标记阶段的算法:引用计数法、可达性分析法
为什么需要GC
不进行垃圾回收,内存迟早被消耗完
可以清理内存碎片,以便jvm将整理出来的内存分配给新的对象
没有GC不能保证程序的正常运行
好处:
降低内存泄露和内存溢出的风险
自动内存管理机制,让程序员可以更专注于业务
GC的作用区域
GC的作用区域是方法区和堆
三种GC
jvm在进行GC时,并非每次都对上面三个内存(新生代、老年代、方法区)区域一起回收,大部分时候回收的都是新生代。
针对hotSpot vm的实现,它里面的GC按照回收区域分为两大类型:一种是部分回收(partial GC),一种是整堆回收(Full GC)
部分回收(partial GC):不是完整收集整个java堆的垃圾。其中又分为
新生代收集(Minor GC/Young GC):只是新生代(Eden/s0/s1)的垃圾收集
老年代收集(Major GC/Old GC):只是老年代的垃圾收集
整堆收集(Full GC):收集整个java堆和方法区
年轻代Minor GC触发机制:
- 当年轻代空间不足时,触发Minor GC,之力指的是Eden区满了,Survivor区满不会引发GC。
- 因为java对象大多具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快
- Minor GC会引发STW,暂停其他用户的线程,等待垃圾回收结束,用户线程才恢复运行
老年代Major gc触发机制:
- 老年代空间不足,会尝试触发Minor GC,如果之后空间还是不足,会触发Major GC
- 如果Major GC后,内存还不足,就报OOM了
Full GC触发机制:
- 调用system.gc()时,系统建议执行Full GC ,但是不是必然执行
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC 后进入老年的的平均大小大于老年代的可用内存
- 由Eden区,from区向to区复制时,对象大小大于to区可用内存,则把该对象转到老年代,且老年代的可用内存小于该对象大小
垃圾回收相关的算法
标记阶段:
引用计数算法
可达性分析算法
清除阶段:
标记-清除算法
标记阶段:引用计数法
对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况,1表示有一个引用指向,0表示没有被引用,可以回收
有点:简单,垃圾对象便于识别,判定效率高,回收没有延迟性
缺点:
- 需要单独的字段存储计数器,增加了空间开销
- 每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销
- 无法处理循环引用的情况,只是一条致命缺陷,导致在java的垃圾回收器中没有使用这类算法
标记阶段:可达性分析法
以根对象集合(GC ROOTS)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
内存中存活对象都会被根对象集合直接或者间接连接着,搜索走过的路径称为引用链(reference chain)
如果目标对象没有任何引用链相连,则是不可达的,意味着对象已经死亡,可标记为垃圾对象
在可达性分析算法中考,只有能够被根对象集合直接或者间接连接的对象才是存活对象
什么是GC ROOTS
⼀组必须「活跃」的引⽤
栈帧指向的堆的对象引用、静态变量引用、被java本地方法所引用的对象引用等
STW
stop the word
可达性分析工作必须在一个能保障一致性的快照中进行,这点不满足的话准确性就无法保证
=》GC时必须STW(停下用户线程),不可避免,只能尽量缩短时间
清除阶段:标记清除算法(mark-sweep)
当堆中的有效内存空间被耗尽时,会停止整个程序,然后进行标记、清除两项工作
标记:Collector 从引用根节点开始遍历,标记所有被引用的对象,一般是在对象的Header 中记录为可达对象。
清除:Collector 对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。(并不是真的清除,记录垃圾对象向地址,直接被新对象覆盖使用)
缺点:
- 效率不高
- GC时停止整个应用程序,导致用户体验差
- 清理出来的空闲内存不是连续的,产生内存碎片,需要维护一个空闲列表(需要清除的对象地址)
垃圾收集器
垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。
由于JDK版本处于告诉迭代过程中,因此Java发展至今已经衍生了众多的GC版本
发展历史
7款经典垃圾收集器
finalization机制
java提供对象终止机制,允许开发人员提供对象被销毁之前的自定义处理逻辑
finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放
不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用
在finalize()时可能导致对象复活
finalize()方法的执行时间是没有保障的,完全由GC线程决定,极端情况下,若不发生GC,则finalize()将没有执行机会
优先级低的线程主动调用finalize()也不一定马上执行
相关概念
System.gc()
仅仅是提醒进行垃圾回收,不确定是否马上执行gc
内存溢出与内存泄露
内存溢出:
程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗 OOM
OOM:没有空闲内存,并且垃圾收集器也无法提供更多内存
java虚拟机堆内存设置不够 可以通过-Xms、-Xmx来调整
代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在引用)
内存泄露:
严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄露
但是一些不太好的实践,导致对象的声明周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄露”。
一旦内存泄漏,程序中的可用内存会被逐步蚕食,知道耗尽所有内存,最终出现OOM
常用命令
-Xms:初始堆大小,默认为物理内存的1/64(<1GB);空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制
-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn:新生代的内存空间大小
-XX:SurvivorRatio:默认值8, eden:from:to = 8:1:1
-Xss:每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。一般小的应用, 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。
-XX:PermSize:设置永久代(perm gen)初始值。默认值为物理内存的1/64。
-XX:MaxPermSize:设置持久代最大值。物理内存的1/4。
作者: deity-night
出处: https://www.cnblogs.com/deity-night/
关于作者:码农
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(***@163.com)咨询.