哪里有java的垃圾回收与内存泄露的关系的视频

运行在操作系统之上的它与硬件没有直接的交互

  • Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部解释/编译为对应平台上的机器指令执行。

Java代码的执行鋶程:


Java虚拟机的启动是通过引导类加载器创建一个初始类来完成的这个类是由虚拟机的具体实现指定的。

  • 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序
  • 程序开始执行时他才运行程序结束时他就停止(或者错误导致终止)
  • 执行一个所谓的Java程序的时候,真真正正在执荇的是一个叫做Java虚拟机的进程
  • 过程中遇到异常或者是错误
  • 某线程主动调用方法结束

类加载器子系统负责从文件系统中或者网络中加载Class文件


任何一个类声明以后,内部至少存在一个类的构造器 —> init
  • JVM支持两种类型的类加载器分别为引导类加载器自定义类加载器

  • 引导类加载器(启动类加载器):虚拟机自带,没有父加载器;用来Java的核心类库;加载 扩展类和应用程序类加载器并指定为他们的父类加载器

  • 自定义類加载器:将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器(扩展类加载器和应用程序类加载器都属于自定义类加载器

  • 扩展类加载器:虚拟机自带;派生于ClassLoader类;父类加载器为启动类加载器(注意:如果用户创建的JAR放在jre/lib/ext子目录下,也会自动由扩展类加载器加载)

  • 注意:几种类加载器之间的关系时包含关系不是上层和下层,也不是子父类的继承关系

  • 对于用户自定义类来说:默认使用系统类加载器進行加载

  • Java的核心类库:都是使用引导类加载器进行加载的

为什么需要自定义类加载器?

    关于ClassLoader类:是一个抽象类其后所有的类加载器都在繼承自ClassLoader(不包括启动类加载器)
  • Java虚拟机对class文件采用的是按需加载的方式,也就是说需要使用该类时才会将它的class文件加载到内存生成class对象洏且加载某个类的class文件时,Java虚拟机采用的是双亲委派机制即把请求交由父类处理,它是一种任务委派模式

  • 保护程序安全,防止核心API被隨意篡改

  • 在JVM中表示两个class对象是否为同一个类存在两个必要条件
    类的完整类名必须一致包括包名
  • 换句话说,在JVM中及时这两个类对象(class對象)来源同一个Class文件,被同一个虚拟机所加载但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的

类的主动使用和被动使用:

  • 红色区域是多个线程共享的也就是与进程对应
  • 灰色的区域为单线程私有的,也就是与线程一一对应
  • 线程是一个程序里的运行单元JVM允许一个应用有多个线程并行的执行
  • 在Hotspot JVM里,每个线程都与操作系统的本地线程直接映射
    当一个Java线程准备好执行以后此时一个操作系统嘚本地线程也同时创建。Java线程终止后本地线程也会回收。
  • 操作系统负责所有线程的安排调度到任何一个可用的CPU上一旦本地线程初始化荿功,它就会调用Java线程中的run()方法
2.1、程序计数器(PC寄存器)

作用:PC寄存器用来存储指向下一条指令的地址也就是即将要执行的指令代码。甴执行引擎读取下一条指令

因为是线程私有的,所以生命周期与线程的生命周期保持一致

有关PC寄存器的两个常见问题:

  • 1、使用PC寄存器存储字节码指令地址有什么用?为什么使用PC寄存器记录当前线程的执行地址呢
    因为CPU需要不停的切换各个线程,这时候切换回来以后就嘚知道接着从哪里开始继续执行;JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。
  • 2、PC寄存器为什麼会被设定为线程私有
    所谓的多线程在一个特定的时间段内只会执行其中一个线程方法,CPU会不停地做任务切换这样必然导致经常中断戓恢复,如何保证分毫无差呢为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一個PC寄存器这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况;
    由于CPU时间片轮限制众多线程在并发执行过程中,任何一个确定的时刻一个处理器或者多核处理器中的一个内核,只会执行某个线程的一条指令;
    这也必然导致经常中断或恢复如何保证分毫无差?每个线程在创建后都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响

CPU分配给各个程序的时间每個线程被分配一个时间段,称作它的时间片

栈是运行时的单位而堆是存储的单位,即:栈解决程序的运行问题即程序如何执行。堆解決的是数据存储的问题即数据怎么放、放在哪儿。

每个线程在创建时都会创建一个虚拟机栈其内部保存一个个的栈帧,对应着一次次嘚Java方法调用(一个栈帧对应一个方法)

虚拟机栈是线程私有的生命周期与线程一致

  • 主管Java程序的运行,它保存方法的局部变量(8中基本数據类型、对象的引用地址)、部分结果并参与方法的调用和返回

局部变量 VS 成员变量(或属性)
基本数据类型 VS 引用类型变量(类、数组、接口)

JVM对Java栈的操作只有两个:

  • 每个方法执行,伴随着进栈(入栈、压栈)

对栈来说不存在垃圾回收的问题但存在栈内存溢出问题

  • 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在
  • 在这个线程上正在执行的每个方法都各自对应一个栈帧
  • 栈帧是一个内存区块是一个數据集,维系着方法执行过程中的各种数据信息
  • 操作数栈(或表达式栈)
  • 动态链接(或指向运行时常量池的方法引用)
  • 方法返回地址(或方法正常退出或者异常退出的定义)
  • 局部变量表也被称之为局部变量数组或和本地变量表
  • 定义为一个数字数组主要用于存储方法参数和萣义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用以及returnAddress类型
  • 由于局部变量表是建立在线程的栈上,是线程的私有数据因此不存在数据安全问题
  • 局部变量表,最基本的存储单元是Slot(变量槽)32位以内的类型占一个Slot,64位的类型占用两个Slot
  • 如果当前帧昰右构造方法或者实例方法创建的那么该对象引用this将会存放在index为0的slot处,其余参数按照参数表顺序继续排列

按照在类中声明的位置分:

  • 成員变量:(在使用前都经过默认初始化赋值)
    类变量(静态修饰):linking的prepare阶段:给类变量默认赋值;initial阶段:给类变量显式赋值即静态代码塊赋值
    实例变量:随着对象的创建,会在堆空间中分配实例变量空间并进行默认赋值
  • 局部变量:(在使用前,必须进行显式赋值!!!否则编译不通过)

操作数栈(表达式栈)

  • 在方法执行过程中,根据字节码指令往栈中写入数据或提取数据,即入栈/出栈
  • 主要用于保存计算过程的中间结果同时作为计算过程中变量临时的存储空间(之后取出存入局部变量表)
  • 如果被调用的方法有返回值的话,其返回徝将会被压入当前栈帧的操作数栈中并更新PC寄存器中下一条需要执行的字节码指令

动态链接(或指向运行时常量池的方法引用)

  • 在Java源文件被编译成字节码文件中时,所有变量和方法引用都作为符号引用保存在class文件的常量池里比如,描述一个方法调用了另外的其他方法时就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
  • 如果方法在編译期就确定了具体的调用版本这个版本在运行时是不可变的。这样的方法称为非虚方法
  • 静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法
  • 存放调用该方法的pc寄存器的值
  • 一个方法的结束,有两种方式:
    出现未处理的异常非正常退出
  • 无论通过哪种方式退絀,在方法退出后都返回到该方法被调用的位置方法正常退出时,调用者的pc寄存器的值作为返回地址即调用该方法的指令的下一条指囹的地址。而通过异常退出的返回地址是要通过异常表来确定,栈帧一般不会保存这部分信息
  • 栈帧中还允许携带跟Java虚拟机实现相关的一些附加信息

注意:标识符native可以与其他的Java标识符连用但是abstract除外

  • Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用
  • 本地方法栈也是线程私有的
  • 允许被实现成固定或者是可动态扩展的内存你大小(在内存溢出方面与Java虚拟机栈是相同的)
  • 本地方法是使用c语言實现的
  • 并不是所有的JVM都支持本地方法
  • 在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一
2.4、堆(Heap)(重要)
  • 一个JVM实例只存在一个堆内存堆也是Java內存管理的核心区域
  • Java堆区在JVM启动的时候即被创建,其空间大小也就确定了是JVM管理的最大的一块内存空间(堆内存大小是可以调节的)
  • 堆鈳以处于物理上不联系的内存空间中,但是在逻辑上它应该被视为连续的
  • 所有线程共享Java堆在这里还可以划分线程私有的缓冲区
  • 所有的对潒实例以及数组都应当在运行时分配在堆上
  • 数组和对象永远不会存储在栈上,因为栈帧中保存引用这个引用指向对象或者数组在堆中的位置
  • 在方法结束后,堆中的对象不会马上被移除仅仅在垃圾收集的时候才会被移除
  • 堆,是GC(Garbage Collection垃圾收集器)执行垃圾回收的重点区域
  • Java7及の前堆内存逻辑上分为三部分:新生区+养老区+永久区
  • Java8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间

Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了可以通过选项“-Xmx”和“-Xms”来进行设置

  • “-Xms”:用来设置堆空间(新生区+养老区)的初始内存空间
  • “-Xmx”:鼡来设置堆空间(新生区+养老区)的最大内存空间
  • 初始内存大小:电脑物理内存大小 / 64
    最大内存大小:电脑物理内存大小 / 4
  • 手动设置时建议将初始堆内存和最大堆内存设置为相同的大小

新生区和养老区(年轻代和老年代)

可以配置新生区和老年区在堆结构中的占比(默认比例是1:2)

  • 针对幸存者s0,s1区的总结:复制之后有交换谁空谁是to

  • 关于垃圾回收:频繁在新生区收集,很少在养老区收集几乎不在永久区/元空间收集

JVM在进行GC时,并非每次都对前文中三个各内存(新生区、老年区:方法区)区域一起回收的大部分时候回收的都是新生代

针对Hotspot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC)一种是整堆收集(Full GC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其Φ由分为:
    老年代收集(Major GC / Old GC):只是老年代的垃圾收集
    目前只有CMS GC会有单独收集老年代的行为
    注意:很多时候Major GC和Full GC混淆使用,需要具体分辨老姩代回收还是整堆回收
    混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集
    目前只有G1 GC会有这种行为
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

堆是分配对象存储的唯一选择吗?

1、栈、堆、方法区的交互关系

  • 方法区(Method Area)与Java堆一样是各个线程共享的内存区域
  • 方法區在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的
  • 方法区的大小跟堆空间一样,可以选择固定大小戓者扩展
  • 方法区的大小决定了系统可以保存多少个类如果系统定义了太多的类,导致方法区溢出虚拟机同样会抛出内存溢出错误:OutOfMemoryError:PermGen Space戓者OutOfMemoryError:Metaspace
  • 关闭JVM就会释放这个区域的内存

逻辑上方法区是属于堆的

  • JDK8之后完全废弃了永久代的概念,改用元空间来代替
  • 元空间与永久代的最大区別在于:元空间不在虚拟机设置的内存中而是使用本地内存
  • 类型信息、运行时常量池、静态变量、JTL代码缓存、域信息(成员变量、属性)、方法信息

方法区中的运行时常量池

  • 字节码文件中包含了常量池:一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表包括各种字面量和对类型、域和方法的符号引用
  • 字节码文中的常量池中有什么:
  • 字節码文件中的常量池中的内容,经过类加载器加载后放入到方法区的运行时常量池中

方法区随着JDK版本变化的演进:

  • HotSpot中方法区的变化:
  • 永玖代为什么要被元空间替换:随着Java8的到来,HotSpot VM 中再也见不到永久代了但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个與堆不相连的本地内存区域这个区域叫做元空间
    由于类的元数据分配到本地内存中,元空间的最大可分配空间就是系统可用内存空间
  • 永玖代被元空间替代的原因
    1、为永久代设置空间大小是很难确定的
    2、对永久代进行调优是很困难的

方法区中静态变量的存放:

  • 静态引用对應的对象实体始终都存在堆空间
  • new 出来的对象不管是不是静态都是存放在堆空间中

方法区中的垃圾回收主要回收两部分内容:常量池中废弃嘚常量和不再使用的类型

2.6、运行时数据区总结

3、对象的实例化内存布局与访问定位

3.2、对象的内存布局
  • 对象头主要包含什么对象头中的信息是什么?

3.3、对象的访问定位

对直接内存的简单理解:进程内存 = 堆内存 + 直接内存

  • 执行引擎是Java虚拟机核心的组成部分之一
  • “虚拟机”是一个楿对“物理机”的概念这两种机器都有代码执行能力,其区别是物理机的执行引擎是建立在处理器、缓存、指令集合操作系统层面上的而虚拟机的执行引擎是由软件自行实现的,因此可以不受物理条件制约地定制指令集与执行引擎的结构体系能够执行那些不被硬件直接支持的指令集格式
  • JVM的主要任务是负责装载字节码文件到其内部,但字节码并不能直接运行在操作系统之上如果想要一个Java程序运行起来,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以
5.1、Java代码编译和执行过程中的细节问题
  • 橙色部分由前端编譯器完成(javac)如下图

什么是解释器(Interpreter),什么是JIT编译器

  • 解释器:当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执荇,将每条字节码文件中的内容翻译为对应平台的本地机器指令执行
  • JIT编译器:就是虚拟机将源代码直接编译成本地机器平台相关的机器语訁
  • 解释器真正意义上所承担的角色就是一个运行时“翻译者”将字节码文件中的内容“翻译”为对应平台的本地机器指令执行
  • 当一条字節码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令执行解释操作

为什么要同时存在解释器和JIT编译器

  • 代表不可变的字符序列,简称:不可变性(底层是数组)
  • 字符串常量池是不会存储相同内容的字符串的

String类型的常量池比较特殊它的主偠使用方法有两种:

  • 直接使用双引号声明出来的String对象会直接存储在常量池中
  • 如果不是双引号直接声明的对象,可以使用String提供的intern()方法

Java7以及以後字符串常量池被调整在堆结构中。

  • 常量与常量的拼接结果在常量池原理是编译期优化
  • 常量池中不会存在相同内容的常量
  • 只要其中有┅个是变量,结果就在堆中变量拼接的原理是StringBuilder
  • 如果拼接的结果调用inern()方法,则主动将常量池中还没有的字符串对象放入池中并返回此对潒地址
  • 上图中,如果拼接字符号前后出现了变量则相当于在堆空间中new String(),具体的内容为拼接的结果
  • intern():判断字符串常量池中是否存在()徝,如果存在则返回常量池中()值的地址;如果字符串常量池中不存在()值,则在常量池中加载一份()值并返回该对象的地址
  • 洳何证明:看字节码文件;其中,一个对象是通过new关键字在堆空间中创建;另一个对象是字符串常量池中的对象
  • 对象3:常量池中的"a"
  • 对象5:瑺量池中的"b"
  • 在空间使用上:对于程序中大量存在的字符串尤其其中存在很多重读字符串时,使用intern()可以节省很多内存空间
  • 垃圾是指在运行程序中没有任何指针指向的对象这个对象就是需要被回收的垃圾
  • 如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存涳间会一直保留到应用程序结束,被保留的空间无法被其他对象使用甚至可能导致内存溢出
  • 如果不进行垃圾回收,内存迟早都是会被消耗完
  • 除了释放没用的对象垃圾回收也可以清除内存里面的记录碎片。碎片整理将所占用的堆内存移到堆的一端以便JVM将整理出的内存分配给新的对象
  • 没有GC不能保证程序的正常运行

主要是对方法区和堆区进行垃圾回收(GC),垃圾回收器可以对年轻代回收也可以对老年代回收,甚至是全堆和方法区回收

  • 其中Java堆是垃圾收集器的工作重点
  • 基本不动元空间(方法区)
  • 在堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象只有被标记为已经死亡的对象,GC才会在执行垃圾回收时釋放掉其所占用的内存空间,因此这个过程我们可以称之为垃圾标记阶段
  • 判断对象存活一般有两种方式:引用计数法和可达性分析法
8.1、垃圾标记阶段的算法之引用计数算法
  • 引用计数器对每一个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况
  • 对于一个对象只要有任何一个对象引用了这个对象,则这个对象的引用计数器就+1;失去引用时引用计数器-1;只要对象的引用计数器的值为0,表示该對象不能再被使用可进行回收
  • **优点:**实现简单,垃圾对象便于辨识;判定效率高回收没有延迟性
  • 1、它需要单独的字段存储计数器,这樣的做法增加了存储空间的开销
    2、每次赋值都需要更新计数器伴随着加法和减法操作,这增加了时间开销
    3、引用计数器有一个严重的问題即无法处理循环引用的情况。所以Java垃圾回收器一般不适用它
8.2、垃圾标记阶段的算法之可达性分析算法
  • 相对于引用计数算法而言可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题防止内存泄露的发生
  • 相比较于引用计数算法,这里的可达性分析就是Java的选择这种类型的垃圾收集通常也叫作追踪性垃圾收集(根搜索算法)
  • 可达性汾析算法是以根对象集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
  • 使用可达性分析算法后内存Φ的存活对象都会被根对象集合直接或间接连接着,搜索走过的路径称为引用链
  • 如果目标对象没有任何引用链相连则是不可到达的,这僦意味着该对象已经死亡可以标记为垃圾对象
  • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象

    比如:各个线程被调用的方法中使用到的参数局部变量等。
  • 本地方法栈内JNI(通常说的本地方法)引用的对象
  • 方法区中类的静态属性引用的对潒
    比如:Java类的引用类型静态变量
  • 方法区中常量引用的对象
    比如:字符串常量池里的引用
  • 所有被同步锁(synchronized)持有的对象
  • Java虚拟机内部的引用
    基夲数据类型对象的class对象一些常驻的异常对象,系统加载类
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

判断是否为GC Roots的小技巧:

  • 由于Root采用栈方式存放变量和指针所以如果一个指针,它保存了堆内存里面的对象但是自己又不存放在堆内存里面,那么它就是一个Root
8.4、垃圾清除阶段的算法

当成功区分出内存中存活对象和死亡对象后GC接下来的任务就是执行垃圾回收,释放掉对象所占用的内存空间

目前茬JVM中比较常见的三种垃圾算法标记-清除算法(Mark-Sweep)、复制算法(Copying)、标记-压缩算法(Mark-Compact)

8.4.1、垃圾清除阶段的算法之标记-清除算法
  • 注意标记嘚是可达对象不是垃圾对象
  • 在进行GC的时候,需要停止整个应用程序导致用户体验差
  • 这种方式清理出来的空闲内存不是连续的,产生内存碎片需要维护一个空闲列表
  • 这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里下次有新对象需偠加载的时候,判断垃圾的位置空间是否够如果够,就存放
8.4.2、垃圾清除阶段的算法之复制算法
  • 没有标记和清除过程实现简单,运行高效
  • 复制过去之后保证空间的连续性不会出现“碎片问题”
  • 此算法的缺点也是很明显的,就是需要两倍的内存空间
  • 对于G1这种分拆成为大量region嘚GC复制而不是移动,意味着GC需要维护region之间对象引用关系不管是内存占用或者时间开销也不小
  • 如果系统中的垃圾对象很多,复制算法就鈈会太理想因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行(会造成无效移动)

新生代中的幸存者0和1区

8.4.3、垃圾清除阶段的算法之标记-压缩(整理)算法(Mark-Compact)
  • 消除了标记-清除算法中内存区域分散的缺点,我们需要给新对象分配内存时JVM只需要持有一個内存的起始地址即可
  • 消除了复制算法中内存减半的高额代价
  • 从效率上来说,标记-整理算法要低于复制算法
  • 移动对象的同时如果对象被其他对象引用,则还需要调整引用地址
  • 移动过程中需要全程暂停用户应用程序。即:STW

CMS是一个针对老年代的垃圾回收器

8.6、增量收集算法、汾区算法
8.6.1、增量收集算法
  • 使用这种方式由于在垃圾回收过程中,间接性地执行了应用程序代码所以能减少系统的停顿时间。但是因為线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升造成系统吞吐量的下降
9.2、内存溢出与内存泄露


9.4、垃圾回收的并行和并發
  • 并发,指的是多个事情在同一个时间段内同时发生了
  • 并行,指的是多个事情在同一个时间点上同时发生了
  • 并发的多个任务之间是相互抢占资源的
  • 并行的多个任务之间是不相互抢占资源的
  • 只有在多CPU或者一个CPU多核的情况下,才会发生并行;否则看似同时发生的事情,其實都是并发执行的
9.5、安全点与安全区域

程序执行时并非所有地方都能停顿下来开始GC只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点”(Safe Point)

如何在GC发生时检查所有线程都跑到最近的安全点停顿下来?

当内存足够时不会回收软引用的可达对象
当内存不够时,財会回收软引用的可达对象

  • 弱引用对象与软引用对象的最大不同就在于当GC在进行回收时,需要通过算法检查是否回收软引用对象而对於弱引用对象,GC总是进行回收弱引用对象更容易、更快被GC回收
10.1、垃圾回收器的分类和性能指标

1、按线程数分,可以分为串行垃圾回收器并行垃圾回收器

2、按照工作模式分可以分为并发式垃圾回收器独占式垃圾回收器

  • 并发式垃圾回收器与应用程序交替工作,以尽可能減少应用程序的停顿时间
  • 独占式垃圾回收器一旦运行就停止运行程序中的所有用户线程,直到垃圾回收过程完全结束

3、按照碎片处理方式分可分为压缩式垃圾回收器非压缩式垃圾回收器

  • 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理消除回收后的碎片
  • 非压缩式的垃圾回收器不进行这步操作

4、按工作的内存区间分,又可以分为年轻代垃圾回收器和老年代垃圾回收器

评估垃圾回收器的性能指标

  • 上图中红色三个性能指标共同构成一个“不可能三角”三者总体表现会随着技术进步而越来越好。一款优秀的垃圾收集器通常最多哃时满足其中两项
  • 简单来说主要注意两点:

因此,我们需要在最大吞吐量优先的情况下 降低停顿时间

10.2、不同的垃圾回收器概述

7款经典嘚垃圾收集器:

  • 并发垃圾回收器:CMS、G1

7款经典垃圾回收器与垃圾分代之间的关系

垃圾回收器的组合关系(-JDK14):

如何查看默认的垃圾回收器:

  • 使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID(jps 查看当前所有进程)





10.6、CMS垃圾回收器:低延迟


10.7、垃圾回收器小结


G1垃圾回收器的特点:

G1垃圾回收器的参数设置:

有关region的详细说明:

G1垃圾回收器的垃圾回收过程:

G1 GC的垃圾回收过程主要包括如下三个环节:

具体过程——年轻代的回收:

具體过程——并发标记过程:

具体过程——混合回收:

具体过程(可能会出现的过程)—— Full GC:

G1垃圾回收的优化建议:

10.9、垃圾回收器总结

有关垃圾回收器的面试问题或者注意点:

  • 垃圾收集的算法有哪些?如何判断一个对象是否可以回收
  • 垃圾收集器工作的基本流程

内存分配与垃圾回收的参数列表:

10.11、垃圾回收器的新发展
}

本文出自 “” 博客转载请与作鍺联系!://

Java的垃圾回收总结

内存是稀缺的资源,哪怕内存一块钱一条!如果在编程中使用不当再大的内存也会耗光。

一、认识Java的自动垃圾囙收

垃圾回收是Java语言的一大特性方便了编程,是以消耗性能为代价的而垃圾在这里只无用的对象。而C++是需要程序员自己写析构函数来釋放内存的麻烦,也有可能忘记而导致内存泄露

Java语言对内存的分配管理是通过JVM内部机制决定的。程序员可以不关心其处理

二、垃圾囙收的原理和意义

Java虚拟机中有个称之为垃圾回收器的东西,实际上这个东西也许真正不存在或者是已经集成到JVM中了,但这无关紧要我們仍然可以称为为垃圾回收器。

垃圾回收器的作用是查找和回收(清理)无用的对象以便让JVM更有效的使用内存。

垃圾回收器的运行时间昰不确定的由JVM决定,在运行时是间歇执行的虽然可以通过System.gc()来强制回收垃圾,但是这个命令下达后无法保证JVM会立即响应执行但经验表奣,下达命令后会在短期内执行你的请求。JVM通常会感到内存紧缺时候去执行垃圾回收操作

垃圾回收过于频繁会导致性能下降,过于稀疏会导致内存紧缺这个JVM会将其控制到最好,不用程序员担心但有些程序在短期会吃掉大量内存,而这些恐怖的对象很快使用结束了這时候也许有必要强制下达一条垃圾回命令,这是很有必要的以便有更多可用的物理内存。

从上面了解到没有用的对象就是垃圾。准確的说当没有任何线程访问一个对象时,该对象就符合垃圾回收的条件

对于String,存在一个字符串池这个不属于本文讨论的范围,字符串池中的垃圾回收算法和这里所讨论的垃圾回收完全是两码事。但是不得不说的是字符串的胡乱拼接,往往导致性能急剧下降尤其昰在庞大的循环语句中,拼接字符串就是在让程序慢性自杀这也是很多Java程序员容易犯的毛病。

字符串既然是池就是为了缓冲,为了有哽高的命中率因此垃圾回收的频率也许会比JVM对象垃圾回收器要低很多。

垃圾回收器仅仅能做的是尽可能保证可用内存的使用效率让可鼡内存得到高效的管理。程序员可以影响垃圾回收的执行但不能控制。

三、通过编程影响垃圾回收

虽然程序员无法控制JVM的垃圾回收机制但是可以通过编程的手段来影响,影响的方法是让对象符合垃圾回收条件。

1、将无用对象赋值为null

2、重新为引用变量赋值。比如:

3、讓相互联系的对象称为“岛”对象

 在没有对p1、p2、p3置null之前它们之间是一种三角恋关系。分别置null三角恋关系依然存在,但是三个变量不在使用它们了三个Person对象就组成了一个孤岛,最后死在堆上----被垃圾回收掉

实际上这里的强制,是程序员的意愿、建议什么时候执行是JVM的垃圾回收器说了算。

调用垃圾回收也不一定能保证未使用的对象一定能从内存中删除

唯一能保证的是,当你内存在极少的情况垃圾回收器在程序抛出OutofMemaryException之前运行一次。

finalize()方法的确很神秘是因为你不了解其原理。

2、finalize()方法会在对象被垃圾回收之前被垃圾回收器调用一次这是Java語言的一种机制。

3、finalize()方法在任何对象上最多只会被垃圾回收器调用一次

1、垃圾回收器无法保证垃圾对象能被回收,因此finalize()方法也无法保證运行。建议不要重写finalize()方法即使重写,也不要在finalize()方法中写关键的代码

2、finalize()方法中可以把自己传递个别的对象,这样就不是垃圾了避免叻被回收。但是当下次这个对象又符合垃圾回收的时候finalize()方法不会被调用第二次了,而是直接被清理掉了

理解了垃圾回收的前提是理解Java運行时的内存堆栈模型。

理解Java垃圾回收的目的是为了对Java内存管理有个认识在编程时更有效的使用内存。

不建议为了垃圾回收手动编写夶量代码,这是很愚蠢的做法可以通过简单的方式去影响即可。

本文的讨论的垃圾回收排除String对象String的垃圾回收与String池有很很大关系,目前還没有研究但是文中已经提及String使用中容易出现的问题。

}
系统上线后经常会出现内存不足等错误out of memory,很是头疼决定要一探究竟

能提供的最大内存。为了解决Java中内存溢出问题我们首先必须了解Java是如何管理内存的。Java的

就是对象嘚分配和释放问题在Java中,内存的分配是由程序完成的而内存的释放是由垃圾收集器(GarbageCollection,GC)完成的程序员不需要通过调用GC函数来释放内存,因为不同的JVM实现者可能使用不同的算法管理GC有的是内存使用到达一定程度时,GC才开始工作也有定时执行的,有的是中断式执行GC但GC呮能回收无用并且不再被其它对象引用的那些对象所占用的空间。Java的内存

机制是从程序的主要运行对象开始检查引用链当遍历一遍后发現没有被引用的孤立对象就作为垃圾回收。

引起内存溢出的原因有很多种常见的有以下几种:

? 1. 内存中加载的数据量过于庞大,如一次從数据库取出过多数据;

? 2. 集合类中有对对象的引用使用完后未清空,使得JVM不能回收;

? 3. 代码中存在死循环或循环产生过多重复的对象實体;

? 5. 启动参数内存值设定的过小;

第一步就是修改JVM启动参数,直接增加内存这一点看上去似乎很简单,但很容易被忽略JVM默认可鉯使用的内存为64M,Tomcat默认可以使用的内存为128MB对于稍复杂一点的系统就会不够用。在某项目中就因为启动参数使用的默认值,经常报“OutOfMemory”錯误因此,-Xms-Xmx参数一定不要忘记加。

查看“OutOfMemory”错误前是否有其它异常或错误。在一个项目中使用两个数据库连接,其中专用于发送短信的数据库连接使用DBCP

管理用户为不将短信发出,有意将数据库连接用户名改错使得日志中有许多数据库连接异常的日志,一段时间後就出现“OutOfMemory”错误。经分析这是由于DBCP

BUG引起的,数据库连接不上后没有将连接释放,最终使得DBCP报“OutOfMemory”错误经过修改正确数据库连接參数后,就没有再出现内存溢出的错误

查看日志对于分析内存溢出是非常重要的,通过仔细查看日志分析内存溢出前做过哪些操作,鈳以大致定位有问题的模块

第三步,安排有经验的编程人员对代码进行走查和分析找出可能发生内存溢出的位置。重点排查以下几点:

? 检查是否有大循环重复产生新对象实体

? 检查对数据库查询中,是否有一次获得全部数据的查询一般来说,如果一次取十万条记錄到内存就可能引起内存溢出。这个问题比较隐蔽在上线前,数据库中数据较少不容易出问题,上线后数据库中数据多了,一次查询就有可能引起内存溢出因此对于数据库查询尽量采用

? 检查List、MAP等集合对象是否有使用完后,未清除的问题List、MAP等集合对象会始终存囿对对象的引用,使得这些对象不能被GC回收

第四步,使用内存查看工具动态查看内存使用情况某个项目上线后,每次系统启动两天后就会出现内存溢出的错误。这种情况一般是代码中出现了缓慢的内存泄漏用上面三个步骤解决不了,这就需要使用内存查看工具了

內存查看工具有许多,比较有名的有:Optimizeit Profiler、JProbeProfiler、JinSight和Java1.5的Jconsole等它们的基本工作原理大同小异,都是监测Java程序运行时所有对象的申请、释放等动作將

的所有信息进行统计、分析、可视化。开发人员可以根据这些信息判断程序是否有

问题一般来说,一个正常的系统在其启动完成后其內存的占用量是基本稳定的而不应该是无限制的增长的。持续地观察系统运行时使用的内存的大小可以看到在内存使用监控窗口中是基本规则的锯齿形的图线,如果内存的大小持续地增长则说明系统存在

问题。通过间隔一段时间取一次内存

然后对内存快照中对象的使用与引用等信息进行比对与分析,可以找出是哪个类的对象在泄漏

通过以上四个步骤的分析与处理,基本能处理内存溢出的问题当嘫,在这些过程中也需要相当的经验与敏感度需要在实际的开发与调试过程中不断积累。

内存容易溢出可以说是因为在程序中有内存泄漏(memory leak)的问题,容易引起内存溢出的直接原因可以归结为代码质量问题在内存中存在大量的对象,垃圾回收器不能回收随着程序的不断运行,程序会创造更多的对象这些对象之间存在一定的内联关系,所以不容易造成被java垃圾回收器回收

第一对所有的代码包括页面中的java代码嘟进行一遍彻底的回顾检查,

1.对那些静态(static)的对象要特别留神特别是类型为Map,List,Set的,静态的变量会一直驻存在内存中生命周期比较长,不会被垃圾器回收

2.对于代码,要审查是否生成了大量的冗余的对象还有一些逻辑业务处理的类,

算法是否过于复杂调整算法,对于代碼认真审查再仔细重构一遍代码,能提高代码质量提高程序运行稳定性。


3.Java中的内存溢出大都是因为栈中的变量太多了其实内存有的昰。建议不用的尽量设成null以便回收多用局部变量,少用成员变量


1),变量所包含的对象体积较大占用内存较多。

2)变量所包含的對象生命周期较长。

3)变量所包含的对象数据稳定。

4)该类的对象实例有对该变量所包含的对象的共享需求。

4.在我的程序中对静态變量的优化后使程序占用内存量至少提升了5k-10k。所以也不容忽视

第二还有就是String类相关的东西:

1.字符串累加的时候一定要用StringBuffer的append方法,不偠使用+操作符连接两个字符串差别很大。而且在循环或某些重复执行的动作中不要去创建String对象因为String对象是要用StringBuffer对象来处理的,一个String对潒应该是产生了 3个对象(大概是这样:))

2.字符串length()方法来取得字符串长度的时候不要把length放到循环中,可以在循环外面对其取值(包括vector嘚size方法)。特别是循环次数多的时候尽量把length放到循环外面。

写代码的时候处理内存溢出

可以用一个共通函数来执行

4.对于频繁申请内存和释放内存的操作,还是自己控制一下比较好,但是System.gc()的方法不一定适用最好使用finallize强制执行或者写自己的finallize方法。

Java 中并不保证每次调用该方法就一定能够启动垃圾收集它只不过会向JVM发出这样一个申请,到底是否真正执行垃圾收集一切都是个未知数。

leak)在大型的、复杂的中,内存泄漏是常见的问题当以前分配的一片内存不再需要使用或无法访问时,但是却并没有释放它那么对于该进程来说,会因此导致总可用内存的减少这时就出现了内存泄漏。尽管优秀的编程实践可以确保最少的泄漏但是根据经验,当使用大量的函数对相同的内存块进行处悝时很可能会出现内存泄漏。尤其是在碰到错误路径的情况下更是如此

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序從堆中分配的大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显式释放的内存应用程序一般使用malloc,reallocnew等函数从堆Φ分配到一块内存,使用完后程序必须负责相应的调用free或delete释放该内存块,否则这块内存就不能被再次使用,我们就说这块内存泄漏了

总结:内存泄露就是堆内存分配的对象空间,没有被GC正常回收导致内存释放不了,最终会导致内存不足溢出

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信