Java_内存

来源:互联网 发布:热血江湖衣服强化数据 编辑:程序博客网 时间:2024/05/21 03:55

Java_内存


本文由 Luzhuo 编写,转发请保留该信息.
原文: http://blog.csdn.net/Rozol/article/details/78050885


运行时数据区域分为: 方法区 / 堆 / Java栈 / Native方法栈 / 程序计数器
内存 判断 / 回收 算法
新生代内存划分为 Eden[‘i:dn] 和 Survivor[sərˈvaɪvə(r)] * 2 区
JDK8以后, 永久代被彻底移除, 用元空间代替; 元空间不在JVM中, 而是使用本地内存

Java内存管理

运行时数据区域

  • 方法区

    • 存放被已被加载的类信息 / 常量 / 静态方法 / 字符串常量池 等信息
    • 当方法区无法申请到足够的内存时, 将抛出OutOfMemoryError异常
    • JDK8以后, 永久代已被从方法区彻底移除, 用元空间(Metaspace)代替; 元空间不在JVM中, 而是使用本地内存
      • 方法区是概念, 永久代是方法区的实现, 永久代被移除方法区并不表示方法区没有了
      • 使用本地内存的元空间, 避免了不确定大小的类和字符串的加载导致内存溢出
    • 所有被 new出来的实例对象数组 都在堆上分配内存
    • 堆是GC管理的主要区域
  • Java栈

    • 每个方法的调用执行完成, 都对应着一个栈在Java栈中入栈出栈的过程
    • 当前线程请求的最大栈的深度超过所设定的深度(可设置为动态扩展), 将抛出StackOverflowError异常
    • 当前栈的最大深度为可动态扩展时, 如果扩展时无法申请到足够的内存, 将抛出OutOfMemoryError异常
  • 本地方法栈

    • 与Java栈类似, 只不过这里执行的Native方法不限语言, 不限数据类型
  • 程序计数器

    • 用来记录当前线程的字节码执行位置
    • 当线程被切换后需要恢复到正确的执行位置, 所以每个线程都会有个独立的程序计数器
  • 本地内存

    • 这部分内存不属于运行时数据区的范围
    • 比如JDK4的NIO, 他可以通过Native函数库直接分配对外内存, 然后通过Java堆中的DirectByteBuffer对象操作该内存的引用, 以提高性能
    • 超出可用物理内存限制, 将抛OutOfMemoryError异常

垃圾回收器GC

哪些内存需要被回收

  • 已经不再被任何途径使用的对象需要被回收
  • Java采用 可达性分析算法 判定某对象是否可回收
  • Java中还有对象缓存功能, 对一些对象只在内存紧张时才进行回收

    • 强引用

      • GC永远不会回收掉该引用的对象

        Object obj = new Object();
    • 软引用

      • 一些还有用但非必需的对象, 内存将要溢出之前, 对这些对象进行回收

        Object obj = new Object();SoftReference<Object> softRef = new SoftReference<Object>(obj); // 软引用if (softRef != null) { // 软引用可能已被回收    obj = softRef.get(); // 获取软引用中的对象}
    • 弱引用

      • 一些非必需的对象, 不管内存是否足够, 都对这些内存进行回收

        Object obj = new Object();WeakReference<Object> weakRef = new WeakReference<Object>(obj); // 弱引用if (weakRef != null) { // 弱引用可能已被回收    obj = weakRef.get(); // 获取弱引用中的对象}
    • 虚引用

      • 虚引用完全不会对期生存时间造成影响, 也无法通过虚引用获取该对象, 但是在被GC时可收到通知

        Object obj = new Object();ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); // 引用队列PhantomReference<Object> phantomRef = new PhantomReference<Object>(obj, refQueue); // 虚引用System.out.println("refQueue: " + refQueue.poll()); // => null// GC 发现虚引用, 会将 phantomRef 插入到 refQueue 队列, 此时 obj 未被回收// GC 第二次发现虚引用, phantomRef 插入到 refQueue 队列失败, 才会对 obj 进行回收obj = null;System.gc();Thread.sleep(1000);System.out.println("refQueue: " + refQueue.poll()); // => java.lang.ref.PhantomReference@15db9742
  • finalize() 方法:

    • 如果对象在 finalize() 方法中重新与引用链上的任何一个对象建立关联, 那么该对象会在第二次标记是被移除回收集合, 就能存活下来
    • 由于 finalize() 只会被调用一次, 用过之后下次在触发回收, finalize() 不会被执行, 也就被kill了
    • 所以不建议复写该方法

常见的几种判断对象是否存活的算法:

  • 引用计数算法

    • 给对象添加一个引用计数器, 每当一个地方引用它, 则计数器+1, 每当引用失效, 则计数器-1
    • 当两个对象相互引用时, 即使这两个对象不再被访问, 但是他们相互引用导致计数器不为0, 所以他们无法被回收

      objA.instance = objBobjB.instance = objA
    • 所以Java不会采用该算法来管理内存

  • 可达性分析算法

    • G Roots 为起始点, 向下搜索, 任何与 GC Roots 有引用链(能经过的路径称为引用链)相连的对象, 则判定该存活的对象, 否则判定为可回收的对象
    • Java就是采用该算法来管理内存

什么时候回收

  • 对象优先在新生代Eden区中分配, 当Eden区没有足够内存时, 将执行一次新生代GC.
    • 新生代采用复制算法收集内存
  • 大对象(需要大量连续空间的对象, 如String, Array[])直接进入老年代.
    • 经常出现大对象容易造成提前触发GC
    • 所以尽量避免短命的大对象
  • 长期存活的对象将进入老年代
    • 对象在Eden出生并经历一次新生代GC仍存活,计数1, 将移到新生代Survivor区, 每次经历Survivor区一次新生代GC,计数+1, 当该对象计数≥15时, 将进入老年代.
    • 当新生代Survivor空间中所有相同计数的对象总大小大于Survivor空间1/2时, 计数≥该计数的对象直接进入老年代
  • 在执行新生代GC前, 计算是否是安全的GC(老年代可用空间≥新生代所有对象大小 || 老年代可用空间≥平均晋升大小)
    • 安全的, 将执行一次新生代GC
    • 不安全的, 将执行一次全部GC (full gc)
    • jdk6之后, 是否允许(新生代GC执行)失败参数将不再起作用
  • 流程图

如何回收

  • 复制 算法: 用于新生代
    • 容量比例为: Eden : Survivor = 8 : 1*2
    • 新生代内存按比例划分成一块Eden和两块Survivor
    • 每次使用Eden和其中一块Survivor, 当回收时, 将Eden和Survivor中还存活的对象复制到另一块Survivor上, 然后把Eden和已用的Survivor空间一次清理掉. (每次都是整区回收, 也就无需考虑碎片问题)
    • 如果发生Survivor内存不够用时, 需要老年代内存进行分配担保
    • 示范图
  • 标记-整理 算法: 用于老年代
    • 在老年代, 存活的对象较多, 如果使用复制算法, 效率会变低, 这就需要标记-整理算法
    • 分为”标记”和”清理”两个阶段
      • 标记: 标记处所有存活的对象
      • 整理: 让所有存活的对象向一端移动, 然后直接清理掉端边界以外的空间
    • 示范图

内存中的数据传递案例图



内存优化

工具使用

  • 命令

    • jps: 所有HotSpot虚拟机进程的LVMID
      • 格式: jps [options] [hostid]
      • options:
        • -q: 只输出虚拟机进程的LVMID
        • -m: 进程启动时传给main()的参数
        • -l: 输出全类名
        • -v: JVM参数
      • hostid: RMI注册表中注册的主机名
      • 示范: jps -l
    • jstat: 监视JVM运行状态信息
      • 格式: jstat [ option vmid [interval [s|ms] [count]] ]
        • option
          • -class: 类装载数量 / 类卸载数量 / 总大小 / 装载耗时
          • -gc: Java堆状况(Eden区:E / Survivor区:S / 老年代:O / 新生代GC:YGC / 全局GC:FGC / )
          • -gccapacity: 比-gc多了各区域的最大/最小空间
          • -gccause: 比-gc多了gc原因
          • -gcnew: 新生代gc状况
          • -gcnewcapacity: 略
          • -gcold: 老年代gc状况
          • -gcoldcapacity: 略
          • -compiler: JIT编译器编译方法的信息
          • -printcompilation: 已被JIT编译的方法
        • vmid:
          • 本地: VMID 和 LVMID 一致
          • 远程: [protocol:][//]lvmid[@hostname[:port]/servername]
        • interval: 查询间隔
        • s|ms
        • count: 查询次数
      • 示范: jstat -class 12984 100 20
    • jinfo: Java配置信息
      • 格式: jinfo [option] pid
      • option:
        • -flag : 查询指定参数
        • -flag [+|-]: 添加/删除 参数
        • -flag =: 修改参数
        • -flags: 显示全部参数
        • -sysprops: 打印System.getProperties()的内容
        • <no option>
      • 示范: jinfo -flag OldSize 12984
    • jmap: Java内存映射
      • 格式: jmap [option] vmid
      • option:
        • -dump: 生成Java堆转储快照, 格式: -dump:[live,]format=b,file=, live参数表示是否dump出存活对象
        • -finalizerinfo: 在F-Queue中等待Finalizer线程执行finalize方法的对象
        • -heap: Java堆详情
        • -histo: Java堆中对象的统计信息
        • -F: 强制生成dump快照 (Linux有效/Windows无效)
      • 示范: jmap -dump:format=b,file=javamemory.bin 12984
    • jhat: dump快照分析工具 (不要在服务器上直接分析)
      • 使用: 执行 jhat C:\Users\LZLuz\javamemory.bin => 看到Server is ready.后,在浏览器中输入http://localhost:7000 => 分析内存泄露主要看Show heap histogram
    • jstack: 线程堆栈跟踪, 生成当前线程快照 (主要用于定位线程长时间卡顿的原因)
      • 格式: jstack [option] vmid
      • option:
        • -F: 强制输出线程堆栈
        • -l: 显示所有信息(附加锁信息)
        • -m: 显示Native堆栈
      • 示范: jstack -l 12984
  • GUI工具

    • JConsole: Java监视和管理控制台
      • Java自带工具, 命令行运行jconsole即可
    • VisualVM: Java监视和故障处理工具
      • Java6+自带工具, 命令行运行jvisualvm即可, 强大而值得推荐

优化建议

  • 尽量避免有大量的生存时间长的大对象产生, 这样才能减少full gc, 保障老年代空间的稳定.
  • 扩展堆空间固定(-Xmx, -Xms)到合理的大小, 已避免扩展带来性能浪费, 编写合理的代码, 尽量没有full gc, 这样程序的响应速度才能有保障.
  • 扩大(-Xmn)新生代空间到合理的大小, 已减少新生代GC
  • 代码优化: 减少创建非必要的对象; 非线程安全需要, 尽量使用非线程安全的数据结构存储数据
原创粉丝点击