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表示没有被引用,可以回收

有点:简单,垃圾对象便于识别,判定效率高,回收没有延迟性

缺点:

  1. 需要单独的字段存储计数器,增加了空间开销
  2. 每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销
  3. 无法处理循环引用的情况,只是一条致命缺陷,导致在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。

 

posted on 2023-03-09 13:00  or追梦者  阅读(14)  评论(0编辑  收藏  举报