java虚拟机||数据区域、对象分配与垃圾收集器

一、运行时数据区域

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,包括以下几个区域:

我们先举个栗子,方便回头再来看这个

MyClass.java

public class MyClass {
    public void m1(){
        System.out.println("你真帅...");
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        MyClass mc=new MyClass();
        mc.m1();
    }
}

 

1、程序计数器:

程序计数器是线程私有,存放下一条需要执行的字节码指令,分支,循环,跳转,异常处理线程恢复等基础功能都需要依赖这个计数器来完成,每条线程都有一个独立的程序计数器,各个程序计数器之间互不影响。

2、java虚拟机栈本地方法栈

java虚拟机栈是线程私有,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈出栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型、对象引用。

3、java堆

java堆是虚拟机里最大的一块内存,线程共享,用于存放创建对象实例,java堆是垃圾收集的主要区域,也被称作GC堆;java堆也可以细分为:新生代和老年代;再细致可以为:Eden空间、From Survivor空间、To Survivor空间等。

4、方法区

方法区是线程共享,用于存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它又被称为非堆。人们也把方法区称为“永久代”,该区域的内存回收目标只要是针对常量池的回收和对类型的卸载。

5、运行时常量池

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

6、直接内存

二、对象的创建过程

1、详细创建过程

2、对象的内存布局

对象在内存中存储的布局可以分为3块区域:

1、对象头:对象头可以分为两部分:第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志,线程持有的锁、偏向线程ID、偏向时间戳等;第二部分是类型指针,即对象指向它的类数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须有一块用于记录数组长度的数据。

2、实例数据:实例数据是对象的真正存储的有效信息,各种字段内容。或许是从父类中继承而来,或是子类中定义

3、对其填充:这个并不是必须存在的,没有什么特殊含义,起占位符作用。

3、对象的访问定位

对象访问定位说的就是如何拿到你在堆中创建的对象,栈是通过栈上的reference数据来操作堆上的具体对象的,目前通过两种方式来访问对象:

1、句柄:使用句柄,java堆会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与数据各自的具体地址。使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据 ,而reference本身不需要改变

2、直接指针:如果直接使用指针访问,那么reference中存储的就是对象地址。使用直接指针访问的最大好处就是速度快,节省指针定位的时间开销。对于SunHotSpot,使用的是直接指针。

三、OutOfMemoryError异常

1、java堆溢出

在避免垃圾回收机制清除对象的情况下,在java堆不断地创建对象就会产生OOM(out of memory)内存溢出异常

内存溢出:内存溢出是一切正常的情况下,堆的空间不够用,造成内存溢出

内存泄漏:该回收的对象,垃圾收集器无法对它们自动回收

2、虚拟机栈和本地方法栈溢出

如果线程请求的栈深度大于虚拟机允许的最大深度,将抛出StackOverflowError异常

如果虚拟机在扩展栈时无法申请到足够的内存空间,则将抛出OutOfMemoryError异常

3、方法区和运行时常量池溢出

方法区用于存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。通过运行时产生大量的额类去填满方法区,直到溢出,产生OOM异常

4、本地直接内存溢出

 

三、垃圾收集器

1、判断对象的生命状态:

1、引用计数算法:给对象添加一个引用计数器,如果对这个对象有引用的时候,计数器加1,引用失效时,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。

2、可达性分析法:通过一系列称为“GC Roots”的对象作为起始点,从这个节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。可作为GC Roots的对象包括以下几种:

  • 虚拟机栈中引用的对象
  • 方法区中类静属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(一般是Native方法)引用的对象

2、垃圾收集算法:

(下列算法根据《深入理解java虚拟机》中语言描述和配图来理解,这里不予描述)

  • 标记-清除算法:
  • 复制算法:
  • 标记-整理算法:
  • 分代收集算法:

3、Hotspot的算法实现:

  • 枚举根节点:
  • 安全点:
  • 安全区域:

4、垃圾收集器:

1、serial收集器:

工作在新生代,Serial收集器是一个单线程的收集器,它使用的是一条收集线程完成垃圾收集工作,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。到目前为止,它依然是虚拟机运行在Client模式下的默认新生代收集器。Serial收集器由于没有线程交互,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。

2、Parnew收集器

工作在新生代,ParNew收集器其实就是Serial收集器的多线程版本,是一款并发收集器,实现了让垃圾收集线程与用户线程同时工作。

3、Parnew Scavenge收集器

工作在新生代,使用的是复制算法的收集器,优势并行的多线程收集器,Parnew Scavenge收集器的目的则是达到一个可控制的吞吐领,也被称为“吞吐量优先”收集器。Parnew Scavenge提供了一些可控制吞吐量的参数,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略。自适应调节策略也是Parnew Scavenge与ParNew收集器的一个重要区别。

4、Serial old收集器

工作在老年代,是Serial的老年代版本,是一个单线程收集器,使用‘标记-整理’算法,这个收集器的意义也是在于给Client模式下的虚拟机使用。有两个用途:一种是与Parnew Scavenge收集器搭配使用,另外一种是作为CMS收集器的后备预案。

5、Parallel old收集器

工作在老年代,Parallel old是Parallel Scavenge收集器的老年代版本,使用“标记-整理”算法。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parller Old收集器。

6、CMS收集器

工作在新生代,是一种以获得最短回收停顿时间为目标的收集器。CMS是基于“标记-清除”算法实现的,它的运作整个过程:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

CMS优点体现在:并发收集、低停顿。

缺点:CMS收集器对CPU资源非常敏感、CMS收集器无法处理浮动垃圾、基于“标记-清除”算法实现的收集器在收集结束后会产生大量空间碎片。

7、G1收集器:工作在新生代与老年代,是一款面向服务应用的垃圾收集器,使用“标记-整理”算法实现,它具有以下的特点:

  • 并行与并发
  • 分代收集
  • 空间整合
  • 可预测的停顿

G1收集器之所以能够建立可预测的停顿时间模型,是因为它可以有计划的避免在整个java堆中进行安全区域的垃圾收集。

G1收集器在运作分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值