zoukankan      html  css  js  c++  java
  • JVM内存分配和垃圾收集策略

    java内存区域

    1)程序计数器

      因为java可以多线程并发执行,因此,为了线程切换后能恢复到正确的执行位置,每个线程都需要一个独立的程序计数器。记录正在执行的虚拟机字节码指令的地址。

      这个区域不会产生内存溢出异常。

    2)栈

      java虚拟机栈

        栈中主要存放了编译期可知的四类八种基本数据类型存(逻辑型 boolean、文本型char、整数型byte、short、int、float、浮点数型double、long),对象引用类型,和对象引用类型(reference)。

      本地方法栈

        本地方法栈和java虚拟机栈所发挥的作用非常相似,他们之前的区别是虚拟机栈为虚拟机执行java方法服务。而本地方法栈是为虚拟机使用到的Native方法服务。

        -Xss参数可以设置本地方法栈的内存上限。

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

        程序中 递归如果找不到出口(或者递归过深)也会抛出此异常(不断的进行入栈操作)

    3)堆

      用于存放对象的实列

      可通过-Xms和Xmx来扩展堆的大小。因为现在的收集器基本都采用分代收集算法,所以java堆中还可以细分:新生代和老年代

      如果在堆中没有内存完成实列分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

        -Xmx可以设置堆内存的上限 -Xms 可以设置内存初始化的大小(-X代表非标参数 m代表memory,x代表max)

    4)方法区MethodArea是一个逻辑上的概念 (也叫非堆 ) 1.8以前是Perm Space实现 1.8以后是Meta Space实现

      用于储存已被虚拟机加载的类的信息(编译后的class 包括动态代理产生的Class)、常量、静态变量、即时编译器编译后的代码等数据,Class在被Loader时就会被放到方法区中,加载类的类加载器本身也存在这里。

      垃圾收集行为在这个区比较少出现的,有点类似它的名字,永久代(1.8以后叫元数据区)

      当方法区无法满足内存分配需求时,将抛出OutOfMemoryError(后面会跟PermGen(MetaSpace) space字符串)异常。

    PermSpace(<1.8)

    字符串常量池位于PermSpace

    FGC不会清理

    大小启动的时候指定,不能变

    MetaSpace(>=1.8)  

    字符串常量池位于堆。(一直创建字符串常量,观察堆和Mataspace) 观察内存变化

    会触发FGC清理

    不设定的话 最大就是物理内存

    -XX:MaxPermSize可以设置方法区的上限。

    5)常量池(方法区的一部分JDK1.8以后 已经移动到了堆中)

      用于存放编译期生成的各种字面量和符号的引用

    以上

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

    如果虚拟机在扩展时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。可通过-Xmx和-XX:MaxPermSize可以设置堆内存和方法区上限。-Xmx2048m  -XX:MaxPermSize=512m

    把-Xmx 和-Xms大小设置一样内存大小,可以提高JVM的运行性能(取消掉伸缩区,因扩容时会发生内存抖动)

    -Xms等价于-XX:InitialHeapSize 初始化堆

    -Xmx等价于-XX:MaxlHeapSize 最大堆

    对象的finalize()方法简介

    当垃圾回收器要释放无用对象的内存时,会先调用该对象的finalize()方法。对象的finalize()方法有以下特点

    1)垃圾回收是否执行该方法,以及何时执行该方法都是不确定的。

    换句话说 以下这种情况是有可能的:一个程序只占用了少量内存,没有造成严重的内存需求,于是垃圾回收器没有释放那些无用对象的内存,因此这些对象的finalize()方法还没有被调用,程序就终止了。

    2)finalize()方法有可能使对象复活,使它恢复到可触及状态。

    可触及状态:当一个对象 假定A对象被创建后,只要程序中还有引用变量在引用它,那么它就始终处于可触及状态。

    可复活状态:当程序不在有任何变量引用A对象时,那么它就进入可复活状态。在这个状态中,垃圾回收期会准备释放它的内存,在释放之前,会调用其他处于可复活状态的finalize()方法,这些finalize()方法有可能使A对象重新转到可触及状态。

    不可触及状态:当java虚拟机执行完所有可复活对象的finalize()方法后,假如这些方法都没有使A对象转到可触及状态,那么A对象就进入不可触及状态,垃圾回收才会真正的回收它的内存。

    3)垃圾回收器在执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常执行。

    判断对象是否需要回收

    引用计数算法

    给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就加1;当引用失效时,计数器值就减一;任何时刻计数器为0的对象就是不可能在被使用的。

    这种方法实现简单,判定效率也很高,但是主流的java虚拟机里面没有采用这种方法,因为它很难解决循环引用的问题。如图:

    ABC三个对象循环引用,但是没有其他引用指向它们。ABC一团垃圾,无法回收

    根可达性分析算法(主流的商业语言所采用的算法)

    这个算法的基本思路就是通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,则证明此对象是不可用的。

    在JAVA语言中,可作为GC Roots的对象包括以下几种:

    1. 虚拟机栈中引用的对象(main()方法栈帧开始引用)
    2. 方法区中类静态变量引用的对象(class load到内存之后 会立刻对静态变量初始化,所以静态变量能够访问的到的对象 叫根对象)
    3. 常量池引用的对象(这个class会用到其他class那些类的对象,这些事根对象)
    4. 本地方法栈中JNI引用的对象(C C++本地方法用到的类、对象)

    JVM垃圾收集算法

    标记——清除算法(Mark-Sweep)

      你把它标出来,然后清掉 就这么简单

    缺点:两遍扫描。产生大量不连续的内存碎片。以后程序运行过程中产生较大的对象时,无法找到足够连续的内存 而不得不提前触发另一次垃圾收集动作。

    存活对象比较多的情况下,效率比较高

    复制算法(Copying)

      将内存按容量划分大小相等的两块,每次只使用一块。当这一块的内存用完了,就将还存活的对象复制到另外一块内存上面。

    缺点:没有碎片,但将内存的使用率缩小了一半

    适用于存活对象比较少的情况

    标记——整理算法(Mark-Compact)

      首先标记出所有需要回收的对象,然后让所有存活的对象都向一端移动。然后直接清理掉端边界以外的内存

    缺点:没有碎片,扫描两次,需要移动对象,效率偏低

    分代收集算法(Generational Collection)

      我们jdk采用的应该就是分代收集算法。根据对象存活周期的不同将内存划分为几块,针对每一块使用不同的算法

      新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要对少量存活的对象复制就可以进行收集。

      老年代中(新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象),因为对象存货率高,没有额外空间对它进行分配担保,就需要使用 标记——整理或者标记——清除算法。

    演示堆内存溢出和解决办法

    @Controller
    public class OutOfMemoryController {
    
        List<TestVO> list  = new ArrayList<>();
        
        @RequestMapping(value="/heapMemory",method=RequestMethod.GET)
        @ResponseBody
        public void heapMemory() {
            while(true) {
                list.add(new TestVO(UUID.randomUUID().toString()));
            }
        }
        
    }

    因为new出来的对象是放在堆上面的,所以这样死循环的时候 会报错堆内存溢出(建议通过-Xmx和-Xms把堆内存设置的小一点 容易报错)

    Exception in thread "ContainerBackgroundProcessor[StandardEngine[Catalina]]" java.lang.OutOfMemoryError: GC overhead limit exceeded

    1.jps(虚拟机进程状况工具)查到进程的id

    这个命令是不是很像linux的ps -ef

    jsl命令只有在jdk的bin目录下才生效暂时不知为啥(可能是没path没配置好 不过不影响这篇博客的学习)

    2.根据进程id使用jmap(java内存映像工具)命令导出内存

    jmap(Memory Map for java)可以用于生成堆存储快照

    上面打印了导出内存的路径和名称(默认当前目录)

    3.使用MAT分析内存存储快照

    https://www.cnblogs.com/onelikeone/p/7131793.html 这里写的比较简单 附上一篇MAT下载和使用教程

    上面怀疑的OutOfMemoryController和我们测试的类名一样

    4.也可以正则匹配搜索一下我们的类,进一步定位

    思考

    以上测试代码是演示了堆内存溢出,怎么演示非堆内存溢出(也就是方法区)

    记得有个面试题是要快速填满方法区。 这个就要先看上面 方法区存放的是什么东西了

    上文提到了方法区:用于储存已被虚拟机加载的类的信息(编译后的class)、常量、静态变量、即时编译器编译后的代码等数据

    动态代理会不断的产生新的class对象,可以快速填满方法区。

    基于JVisualVM的可视化监控

    jdk的安装目录bin文件下有jvisualvm.exe可执行文件。可以用来监控虚拟机的性能和故障处理。

    这款JDK自带用来监控内存的可视化工具的详细使用,更新中。。。。。。

  • 相关阅读:
    HTML DOM教程 9HTML DOM Window 对象
    HTML DOM教程 11HTML DOM Screen 对象
    Android项目中把bin文件夹里面的.apk文件删除,怎么让它再生成
    Qt把ping www.baidu.com之后的内容的内容输出到一个名为output的文档
    Qt之QProcess 和 c语言对比
    Git的Windows版本Msysgit的中文乱码解决
    多系统 grub之ubuntu的 grub
    QT中调用外部程序:QProcess的使用
    repo的小结
    恢复Ubuntu默认的面板
  • 原文地址:https://www.cnblogs.com/ssskkk/p/9339180.html
Copyright © 2011-2022 走看看