JVM内存管理与垃圾回收机制

熟悉jvm内存管理与垃圾回收机制,更好的的处理内存溢出问题

一、 java自动内存管理机制

jvm运行时的数据区域:jvm在线运行时会把它所管理的内存划分为若干个不同的区域,这些区域都有各自的用途以及创建时间和销毁时间。
(1)程序计数器:它是一块较小的内存空间,是当前程序执行字节码的行号指示器(字节码指示器通过改变程序计数器的值来执行下一条需要执行的字节码的指令),它是线程私有的内存,它是jvm规范中唯一一个没有规定任何内存溢出情况的区域
什么是线程私有的内存?:jvm实现多线程是通过线程轮流切换并分配处理器的执行时间来实现,实质上在任何时刻一个处理器只能执行一条线程中的指令,所以为了线程切换后能够恢复到正确的执行位置,每一条线程都需要有一个独立的程序计数器(程序计数器是一块较小的内存空间),各条线程之间的程序互不影响,独立存储,这类内存区域我们称之为线程私有的内存。
(2)java虚拟机栈:java虚拟机栈描述的是java方法 执行 的内存模型,每个方法执行的时候都会创建一个栈帧用于存储局部变量表,操作栈。动态链接、方法出口等信息,每一个方法从被调用到执行完成就对应着一个栈帧在虚拟机中从入栈到出栈的过程
java虚拟机栈是线程私有的,生命周期和线程相同
栈和堆:经常有人把java内存分为栈和堆。栈指的其实就是java虚拟机栈(其实是虚拟机栈中的局部变量表部分)局部变量表中存储了八种基本类型和对象的引用(reference引用类型,并不是对象本身,他可能是一个指向对象起始地址的引用指针),局部变量在编译期间完成内存空间的分配。
(3)本地方法栈:作用与java虚拟机栈类似,java虚拟机栈为虚拟机执行java方法(字节码)服务,本地方法栈为虚拟机使用到的Native方法服务
Native:native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址:如果正在执行的是一个native方法的计数器为空(undefined:未定义)。
(4)堆:java堆是Java虚拟机内存管理中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时就被创建,唯一目的是用来存放对象实例。java堆是垃圾收集器管理的主要区域。(现在的垃圾收集器一般采用分代回收算法,所以堆还可以细分为:新生代和老年代)
方法区(jvm将方法区描述为堆的一部分,别名“永久代”,因为在这里很少进行垃圾回收,所以被称为永久代)
各个线程共享的一块内存区域,用于存储已经被虚拟机加载的类的信息,常量静态变量,即时编译器编译后的代码,在这个区域的垃圾回收一般是对常量池的回收
运行时常量池:属于方法区的一部分,class文件中除了有类的版本、字段方法、接口等信息外还有常量池,用来存放编译生成的各种字面量和符号引用,这些内容将在类加载的时候存到方法区的运行时常量池中。

问题:对一个对象访问是如何进行的?
其实最简单的访问也会涉及到java栈java堆方法区这三个最重要的内存区域之间的关联关系。
例如:Object obj=new Object();若是他出现在一个方法体中
Object obj将会是一个对象的引用,存储在java栈中
new Object()实例将会存储到java 堆中,形成一块存储了对象中各个实例数据值的数据的结构化内存

二、垃圾回收机制(GC)

垃圾收集机制需要解决那些问题?
a:哪些内存需要回收?
b:为什么需要回收?
c:怎么回收?

自动内存管理机制:程序计数器(当前程序执行的字节码的行号指示器)、java虚拟机栈(java方法执行的内存模型)、本地方法栈(native方法执行时)的内存分配具有确定性,随线程而生随线程而灭基本不需要内存回收
可是java堆和方法区就不一样了,一个方法中的多个分支需要的内存是不确定的,只有在程序运行期间才能知道会创建哪些对象,垃圾收集器关注的重点就是这部分内容。

堆中存放着几乎所有的java 堆实例,垃圾收集器进行回收之前必须先确定哪些对象活着,哪些对象已经死了

(1)如何判断一个对象是否会被回收?
引用计数算法:
教科书中判断一个对象是否存活的办法:给每一个对象添加一个引用计数器,每当有一个地方引用他的时候,计数器的值就加1,当引用失效的时候,计数器的值就减1;任何时候计数器都为0的对象就是不可能再被使用的,但是它没有办法解决对象之间互相引用的问题,这样引用计数法就没有办法通过GC收集器回收他们
根搜索算法
通过一系列的名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所有的路径称为引用链,当一个对象到 GC roots没有任何引用链相连(也就是GC Roots到这个独享不可达的时候),就证明此对象是不可用的,所以他们会被判定为可以回收的对象
可以作为GC Roots的对象包括下面的几种:
虚拟机栈(栈帧中本地变量表中引用的对象)、方法区中类静态属性引用的对象、方法区中常量引用的对象、类的静态属性引用的对象

(2)常见的垃圾收集算法
1.标记-清除算法
先标记再清除
缺点;效率低 、会产生大量的不连续的内存碎片 导致以后的程序运行过程中需要分配较大的对象的时候无法找到足够的内存而不得不提前触发另一次的垃圾收集动作。
2.复制算法
将内存按大小容量划分为可以使用的两块,每次只使用其中的一块,当一块的内存用完了,就将还存活着的对象复制到另一部分,直接清除掉之前的那部分。
缺点:将内存缩小为了原来的一半

现在的商业模式都采用复制算法来回收新生代,新生代中98%的对象都是朝生夕死的,所以不需要按照1:1的比例来划分空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden的其中的一块Survivor,当回收的时候将Eden中和Survivor中还存活着的对象一次性的拷贝到另外一块Survivor上(当Survivor不够用的时候,需要依赖老年代进行担保分配内存),最后清理掉原来的两块空间。
3.标记整理算法
过程:标记 移动到一段 清理掉端外界的内存
4.分代收集算法
将java 堆分为新生代和老年代,新生代采用复制算法 老年代采用 标记清除或者标记整理算法

(3)内存分配与回收策略
对象的内存分配:就是在堆上分配
大多数情况下,对象在新生代的Eden分配,如果没有足够的内存,将进行一次minor GC(从新生代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC)
大对象直接进入老年代
长期存活的对象将进入老年代(可以通过对象年龄计数器判断对象的年龄)

参考书籍:深入理解java虚拟机

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-乾坤-

????????????????????????

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值