JVM1:内存区域划分与内存分配策略

Java内存区域划分
线程私有:

  1. 程序计数器:可以看做当前线程所执行的字节码的行号指示器。如果执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,计数器的值为空。
    **程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM情况的区域。

  2. 虚拟机栈:描述的是Java方法执行的内存模型:每个方法执行的同时都会创建一个栈帧用于存储局部变量、操作数栈、动态链接、方法出口等信息。生命周期与线程相同。
    平时我们讲的栈实际上就是虚拟机栈中的局部变量表部分。
    会产生两种异常:1)OOM,虚拟机在动态扩展时无法申请到足够的内存
    2)栈溢出异常StackOverFlowError 线程请求的深度大于所允许的深度
    局部变量表:存放编译器可知的各种基本数据类型(8大数据类型)、对象引用。局部变量表所需的内存空间在编译器完成分配,在方法执行期间不会改变局部变量表大小。

  3. 本地方法栈:与虚拟机栈的作用完全一样。
    区别:本地方法栈为虚拟机使用的Native方法服务,虚拟机栈为虚拟机使用的Java方法服务。

线程共享:

  1. 堆:JVM所管理的最大内存区域,在JVM启动时创建,存放的都是对象实例;JVM规范中:所有对象的实例和数组都要在堆上分配。Java堆是垃圾回收器管理的主要区域。Java堆可以处于物理不连续的内存空间中。在堆中没有足够大的内存完成实例分配并且堆也无法再扩展时,会抛出OOM异常。
  2. 方法区:存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等数据。此区域的内存回收主要针对常量池的回收以及对类型的卸载。当方法区无法满足内存分配的需求时,抛出OOM异常。
  3. 运行时常量池:是方法区的一部分,存放字面量与符号引用。

Java堆溢出
Java用于存储对象实例,如果不断创建对象,并且保证GC Roots到对象之间有可达路径来避免GC清除这些对象,那么在对象数量达到最大堆容量后就会产生内存溢出异常。
内存泄漏:泄漏对象无法被GC
设置JVM参数:-Xms:设置堆的最小值、-Xmx设置堆的最大值
内存溢出:内存对象确实还应该存活。根据JVM对参数与物理内存相比较检查是否应该把JVM堆内存调大;或者检查对象的生命周期是否过长。
虚拟机栈溢出和本地方法栈溢出
对于HotSpot来说,栈容量只需要有-Xss参数来设置,因为HotSpot虚拟机将虚拟机栈与本地方法栈合二为一。
出现StackOverflowError异常时有错误堆栈可以阅读,比较好找到问题所在。如果使用虚拟机默认参数,栈深度在多数情况下达到1000-2000完全没问题,对于正常方法调用(包括递归)完全够用。
如果是因为多线程导致的内存溢出问题,在不能减少线程数的情况下,只能减少最大堆和减少栈容量的方式来换区更多线程。
内存分配策略
Java堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活着,哪些已经“死去”了:

  1. 引用计数法:给对象增加一个引用计数器,每当对象被引用一次,计数器就+1,引用失效时,计数器-1;任何时刻计数器为0的对象就是不能再被使用的,此时对象已“死”。(Python就是使用引用计数法来进行内存管理)但是Java没有使用,因为引用计数器无法解决对象的循环引用问题。
  2. 可达性分析算法:(C#也采用可达性分析算法来进行内存管理)核心思想:通过一系列GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为“引用链”,当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达时)证明此对象是不可用的。
    在Java语言中,可作为GC Roots的对象包含:
    1)虚拟机栈(栈帧中的本地变量表)中引用的对象
    2)方法区中静态属性引用的对象
    3)方法去中常量引用的对象
    4)本地方法栈中JNI(Native方法)引用的对象

JDK2以后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用以及虚引用(强度由强到弱)
1)强引用是代码中普遍存在的,类似于Object obj=new Object()这类的引用,只要强引用还存在,垃圾回收器就永远不会回收被引用的实例。
2)软引用:有用但不必须的对象,在系统将要发生内存溢出之前,将这些对象列入回收范围中进行第二次回收。如果这次回收还是没有足够内存,才会抛出内存溢出异常。
3)弱引用:强度低于软引用,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象
4)虚引用:一个对象是否有虚引用的存在完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是垃圾回收器回收这个对象时会收到一个系统通知。
对象的自我拯救finalize()
宣告一个对象的真正死亡至少需要两次标记;如果对象在进行可达性分析后发现没有GC Roots相连接的引用链,那么他将会被第一次标记并且进行一次筛选,筛选的条件时此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或者该方法已经被JVM调用过,虚拟机会将这两种情况都是为“没有必要执行”,此时的对象才实现了真正的死亡。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象会被放在一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(执行就是虚拟机会触发finalize()方法)稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果成功逃脱,就会被移出即将回收的集合,如果这个时候还没有逃脱,基本上真的已经被回收了。
任何一个对象的finalie()方法只会被系统调用一次

回收方法区:方法区也叫永久代;主要回收两部分内容:废弃常量和无用的类
回收废弃常量和Java堆中的对象十分相似;常量池中,假如一个字符串已经进入了常量池中,但是当前系统没有被任何对象引用,也没有在其他地方引用,如果此时GC并且有必要的话,这个常量会被清理出常量池。常量池的其他类(接口)、方法、字段的符号引用也是如此。
无用的类(同时满足三个条件):
1)该类的所有实例都已经被回收(在Java堆中不存在任何该类的实例)
2)加载该类的ClassLoader已经被回收
3)该类对应的Class对象没有在任何地方被引用,无法再任何地方通过反射访问到该类的方法。
同时满足这三个条件才“可以”被回收,不是一定;在大量使用反射、动态代理等场景都需要JVM具备卸载的功能来防止永久代的溢出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值