1.Java内存模型

运行时数据区域:
线程共享(Heap堆区、MetaSpace 元空间)、线程私有(虚拟机栈、本地方法栈、程序计数器)
程序计数器:
1.程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候,能够知道当前线程的运行位置。
2.程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,线程的创建而创建,线程的结束而死亡。
虚拟机栈:
虚拟机栈由一个个栈帧组成,存在压栈出栈。例如main方法中调用别的方法,大致流程为main方法先入栈,其他方法再入栈,执行完该方法,该方法出战。最后main方法也相应的出栈。
Java 虚拟机栈会出现两种错误StackOverFlowError 和 OutOfMemoryError。
●StackOverFlowError: jvm规定了栈的最大深度,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
●OutOfMemoryError: JVM的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
本地方法栈:
和虚拟机栈一样,只不过执行的本地Native 方法。
堆:
存放对象实例和数组,堆是JVM 所管理的内存中最大的一块区域,被所有线程共享的一块内存区域。垃圾GC的主要位置。
创建一个新对象,在堆中的分配流程:
JVM虚拟机 - 图1
堆区最容易出现的就是 OutOfMemoryError 错误,这种错误的表现形式会有以下两种:

  1. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded : 当 JVM 花太多时间执行垃圾回收,并且只能回收很少的堆空间时,就会发生此错误。
  2. java.lang.OutOfMemoryError: Java heap space:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。
    此种情况,与配置的最大堆内存有关,且受制于物理内存大小。

元空间:
用于存放类信息、常量、静态变量、JIT即时编译器编译后的机器代码等数据等。例如:java.lang.Object类的元信息、Integer.MAX_VALUE常量等。(并不在虚拟机中,而是使用本地内存)
常量池:
8 种基本类型的包装类和常量池:

  • Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;
  • 前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据;
  • Character 创建了数值在[0,127]范围的缓存数据;
  • Boolean 直接返回 True Or False;
  • 如果超出对应范围仍然会去创建新的对象
  • String创建首先会到常量池找是否有一样的,有一样的直接指向,如果没有在创建。

2.Java垃圾回收机制

JVM 内存分配与回收

  • JVM的自动内存管理主要是进行对象内存的分配与回收,最核心的功能是 堆 内存中对象的分配与回收。
  • 采用分代垃圾回收算法,可分为新生代(a.Eden区 b.幸存区(from区, to区)(8:1:1) ),老年代。

    Java内存分配策略:

    ●对象优先在 Eden 区分配
    ●大对象直接进入老年代
    ●长期存活的对象将进入老年代
    大致过程:
    正常情况下,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。

    对象进入老年代的情况

    1.大文件直接进入老年代
    2.长期存活的对象将进入老年代

    GC条件:

    Minor GC条件:Eden区满时
    Full GC触发条件(所有用户线程全部暂停,然后对年轻代和老年代都清理):
    (1)调用System.gc时,系统建议执行Full GC,但是不必然执行
    (2)老年代空间不足
    (3)方法去空间不足
    (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。

    3.如何判断对象已死:

    1.引用计数器算法: 对象添加引用计数器,每当有地方引用,则加一。引用失效则引用减一。为零的时候对象不被引用。存在循环引用问题,例如A-B, B-A 。
    2.可达性分析算法: GC Roots的对象为起点,从这些节点开始向下搜索,没有任何引用链相连,该对象不可用

    其中GC Roots包含以下四点:
    1.虚拟机栈中引用的对象
    2.方法区中类静态属性引用的变量
    3.方法区中常量引用的对象
    4.本地方法Native方法引用的对象

强引用:强引用一直存在就不会被回收。例如Object object =new Object();
软引用:有用但非必须的对象,当内存溢出异常之前,通过垃圾回收进行一次回收。
弱引用:非必须的对象,仅能生存到下一次垃圾回收。
虚引用:最弱的引用关系。

4.回收算法:

标记清除:标记不需要回收的,然后清除没有被标记的。对空间浪费极大,对内存的利用率不高。造成大量 的内存碎片。
标记整理:对标记的向内存的一端进行移动。对空间利用好。
复制算法:内存复制两个一摸一样的内存,存活的对象复制到一块预留区,然后把之前需要GC的清除。

5.垃圾收集器:

1.Serial收集器:单线程,停掉所有工作线程,回收完之后然后用户线程开始工作。Client 模式下的默认新生代收集器。采用复制算法。
适用场景:客户端模式下,如用户桌面的应用场景以及部分微服务应用中。
2.ParNew收集器:多线程版本,多个线程进行GC,但同样要暂停用户线程。
适用场景:服务端模式下,JDK 7之前的遗留系统中首选的新生代收集器
3.Parallel Scavenge:使用“复制算法”的多线程收集器,尽可能减少垃圾回收,用户线程的暂停时间。
适用场景:高吞吐量则可以最高效率地利用CPU资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。
老年代:
1.Serial Old:老年代版本,单线程收集器。采用标记整理算法。
2.Parallel Old:多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合多线程和“标记-整理”算法。
4.CMS收集器 (边污染,边治理)并发收集、低停顿,标记-清除算法实现的收集器。
a初始标记 仅仅关联GC Root直接能关联的对象
b并发标记 同时开启 GC 和用户线程
c重新标记 修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录
d并发清除 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
JVM虚拟机 - 图2
缺点:
1.CMS收集器对cpu非常敏感,占用cpu资源
2.无法处理浮动垃圾,并发清除时仍然会产生垃圾,需要等待下次的收集回收。
3.基于标记清除算法,会产生大量内存碎片
G1收集器

7.常见设置参数:

1647419203(1).png