jvm的深入理解(一)

虚拟机内存

  1. 运行时数据区域
  • 虚拟机栈
    • 线程私有的
    • 线程每调用一个java方法都会压入一个栈帧,存放的是局部变量。
  • 本地方法栈
    • 与虚拟机栈差不多,只是是对于本地方法而言的及native方法。
  • 程序计数器
    • 线程所执行的字节码的行号指示器,为了发生线程切换后能够恢复到原来的位置,因此每个线程都需要单独的程序计数器
    • 存放的是我们几乎所有的对象实例,线程共享。
  • 方法区
    • 存放的是类信息,常量,静态变量。(也会有垃圾回收,针对常量池,类型信息,对类型信息的卸载条件很苛刻:1.给类型没有实例对象2.该类型的类对象没有引用3.加载此类型的classLoader已被回收)
  1. 直接内存
    • 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现,所以我们放到这里一起讲解。在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

Java堆中对象分配、布局和访问

  • new对象的过程
    • 类加载检查
    • 类加载检查完成后,对象的大小可以确定,因此就去堆中分配一块这个大小的内存(CAS)。因为垃圾回收器的不同,分配方法不同,分为指针碰撞法和空闲列表法。内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头).虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中.------在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始——方法还没有执行,所有的字段都还为零。在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始——方法还没有执行,所有的字段都还为零。
  • 对象的内存布局
    • 对象头
      • mark word
      • 类元数据指针
      • 如果是数组对象,那就还有数组长度(因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。)
    • 实例数据:因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。
    • 填充字段:因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。

outofmemery的各种情况

除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下文称OOM)异常的可能

  • Java OOM
    • 可能是内存泄漏也可能是内存溢出,可以通过内存映像分析工具,确定是溢出还是泄漏。可以用工具进一步查看引用链,排除问题。如果是溢出,那么就要去看看xms和xmx参数,看看能否在内存进行扩展。
  • 虚拟机栈和本地方法栈溢出
    在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此栈的溢出就一起讨论
    • 单线程情况下,在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。
    • 这里有一个比较难想到的问题,就是出现oom可能是是因为线程太多,线程分配不到栈容量了,会出现oom。因为操作系统分配给进程的内存是有限的,因此如果jvm总内存一定的情况下,需要减小堆的空间,来使得栈空间可以创建更多的线程。
  • 方法区和运行时常量池溢出
    • 当前的很多主流框架,如Spring、Hibernate,在对类进行增强时,都会使用到CGLib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载入内存。另外,JVM上的动态语言(例如Groovy等)通常都会持续创建类来实现语言的动态性,随着这类语言的流行,也越来越容易遇到与代码清单2-8相似的溢出场景。

垃圾回收

  • 三个关键问题
    • 哪些内存需要回收?
    • 什么时候回收?
    • 怎么回收?

对象已死?

  • 引用计数法(存在相互引用问题)
  • 可达性分析(通过GC roots(通常包含虚拟机栈,本地方法栈,方法区(静态属性,常量)的引用对象)进行搜索,不可达的对象需要回收)
  • 注:引用
    • 强引用:普通对象引用
    • 软引用:在将要发生内存溢出之前,把软引用也进入回收,如果还是不够,再抛出内存溢出。
    • 弱引用:只能生存到下一次垃圾收集发生之前
    • 虚引用:一个对象有虚拟引用既不会影响对象的存活,也不能通过虚引用访问对象。唯一能做的就是在对象被回收时能够收到一个系统通知。
  • 注:即使在可达性分析算法中不可达的对象,也并非是“非死不可”的
    • 因为在要真正的宣告一个对象死亡需要两次标记,第一次标记时可达性分析不可达,然后会进行一次筛选,将需要执行finalize()方法的对象筛选出来,放入F-queue中稍后会有一个线程去执行finalize()方法。在这个方法中,如果把自己赋值给了类变量或者其他对象的属性,那么就不会被回收。
  • 回收方法区:废弃常量和无用的类
  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

垃圾回收算法

  • 标记清除法
  • 复制法
  • 标记整理法
  • 分代收集法(虚拟机中采用)
    • 新生代中存在大量的对象死亡,少量存活,98%的对象都会死去,因此采用复制法,但是不像普通复制法那样分为相等的两部分,而是分为eden和survive两部分,比例为8:2,其中survive再分为相等的两部分,总体上8:1:1。但是虽然存活率不高,偶尔也可能有查出10的存活率,因此需要老年代来做担保,如果放不下需要放到老年代中。(如果老年代空闲空间大于年轻代所有对象,担保成功。如果不足,则需要查看设置是否允许担保失败的,允许则如果空闲空间大于历史平均则继续执行minor gc,发生溢出则full gc。)

HotSpot的算法实现

在HotSpot虚拟机上实现这些算法时,必须对算法的执行效率有严格的考量,才能保证虚拟机高效运行。

  • 枚举根节点
  • 安全点,安全点

垃圾收集器(有时间需细化)

  • CMS(concurrent mark sweep)

    • 初始标记(Stop The World)时间短
    • 并发标记:多线程,时间长
    • 重新标记(Stop The World)时间短
    • 并发清除:多线程,时间长
    • 减少了Stop The World的时间
  • G1(Garbage-First)

    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收(最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划)
  • 33
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值