JAVA基础面试题:JVM的内存区及其GC算法,以及如何避免内存泄露和内存溢出(HotSport)

下面主要为大家介绍JVM的运行时数据区(有错误请指正)

虚拟机分为三部分

    1.内加载子系统 (JVM Classloading System)
    2.java运行时数据区。(Java Runtime Data Area)
    3.执行引擎。(JVM Execution Engine System)

1 . Java运行时数据区

           java运行时数据区主要分为三部分,分别为方法区(Method Area JDK1.8后称为元数据区,以下称为元数据区),堆区(Heap),虚拟机栈(VM Stack)
JAVA基础面试题:JVM的内存区及其GC算法,以及如何避免内存泄露和内存溢出(HotSport)
                                                                  上图为JVM虚拟机

1)元数据区

            全局共享存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

2)堆区(Heap)堆区又分为年轻代和老年代

            线程共享,主要是存放对象实例和数组

a)年轻代
            如上JVM结构图所示,年轻代又分为了一个伊甸园区(eden)和两块幸存区(s0和s1),伊甸园和幸存区内存比例8:1:1

  •        很多新创建的对象会默认存在伊甸园区,当伊甸园区内存满的时候底层启动GC机制,GC对垃圾对象进行扫描标记,标记完后将标记内存区对象擦掉,擦除后对内存区进行整理,GC回收伊甸园区时可能会存在没有被回收的对象。这些对象被称为幸存对象。GC会把幸存对象复制到幸存区,幸存区可能有放不下的情况,直接放入老年代
  •        当伊甸园存满,GC标记-复制到幸存区S0,伊甸园清空,反复复制多次后,幸存区S0存满,幸存区S0标记复制到幸存区S1,并清空幸存区S0,伊甸园标记复制到幸存区S1,反复多次,幸存区S1满了,向幸存区S0复制。S0,S1两块幸存区来回复制多次后仍然存活的对象,进入老年代(默认复制15次)

b) 老年代
       一般存储多次回收没有回收掉的对象,假如伊甸园区没有足够的空间存储大对象,有可能直接分配到老年代。

       堆区对象创建的频率远远大于GC频率或者对象内存泄露导致对象不能被回收,创建新对象没空间时,可能会出现内存溢出的现象,JVM给元数据区设置大小,当类的结构信息比较多超过了这个大小的时候也会造成内存溢出,创建对象时可能有一些没有逃逸的小对象分配在栈上。

3) 虚拟机栈(VM Stack) (线程私有区)

       虚拟机栈占用的是操作系统内存,每个线程对应一个虚拟机栈,它是线程私有的,生命周期和线程一样(一同创建,一同销毁)每个方法被执行时产生一个栈帧(Statck Frame),栈帧用于存储局部变量表、动态链接、操作数和方法出口等信息,当方法被调用时,栈帧入栈,当方法调用结束时,栈帧出栈,

a) 分为Java方法栈和本地方法栈(Native Method Stack)

  •        方法栈里存放数据的单元叫栈帧,栈内存无限递归时会出现内存溢出

b) 程序计数器(Program Counter Register)

  •        记录当前线程执行的位置,不会出现内存溢出

2.GC算法

-         概念: GC是垃圾回收的自动内存管理机制

GC算法大致分为以下几种:

1)标记-清除算法: (Mark-Sweep)

  •             递归每个能访问的对象的指针数组,将每个活动对象打上mark; 将不能活动的对象进行回收; 速度较快但产生碎片,效率降低

2)标记-整理算法:

  •             标记-清除的升级版; 将标记的对象移入一侧,然后清掉端边界外的内存效率不高没有碎片(在存活率较高的情况下更为高效, 且耗空间更小)

以上方法适用于存活率高的对象

3)标记-复制算法

  •             对于以上两种算法,引出了复制算法,使用这种方式可以避免出现空间碎片,不过会浪费一半的内存,降低空间的使用率。

3)引用计数法:

  •             引入计数器,用增减计数器的值来进行内存管理。扫描的时候看对象有多少个引用指向它,当没有任何引用指向他时先做个标记,标记完后回收,java一般不采用引用计数法,会产生循环依赖,导致对象不会被回收。

3. 如何避免内存泄漏和内存溢出

1) 程序进行字符串处理时,尽量避免使用String,而应使用StringBuffer,因为每一个String对象都会独立占用内存一块区域

2) 尽量少用静态变量。因为静态变量是全局的,GC不会回收。

2)避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。

3)尽量运用对象池技术以提高系统性能。

4)生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。

5)不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。

6)优化配置。
        jvm需要优化的主战场是堆内存,垃圾回收主要是针对堆内存,jvm垃圾回收的频率和时间是程序运行好坏的重要指标,因为垃圾回收要终止其它正常的工作线程,导致程序停顿。影响Jvm垃圾回收的频率和时间因素,除了垃圾收集器自身的实现机制外,堆内存的大小或各代比例设置等也是重要的因素。调节堆内存的参数如下:

  • -Xms 1024M //表示堆内存初始化大小,默认是物理内存1/64;
    -Xmx 1024M //表示堆内存最大值,默认是物理内存1/4;
    -Xmn 512M //表示堆中新生代大小;
    -XX:NewRadio //表示新生代与老年代的比例,-XX:NewRadio =2表示比例为1:2
    -XX:SurvivorRadio //表示新生代中eden区与survivor区的比例,默认比例为8:1
    -XX:MaxTenuringThreshold //表示经过多少次回收,对象进入老年代,默认是15
    -XX:PretenureSizeThreshold //直接进入老年代对象的值的阈值,-XX:PretenureSizeThreshold=3M,表示大于3M的对象可以直接进入老年代