JVM区域划分

Java虚拟机所管理的内存将会包括以下几个运行时数据区域:

  • 程序计数器(线程隔离)
  • 虚拟机栈(线程隔离)
  • 本地方法栈(线程隔离)
  • JAVA堆(线程共享)
  • 方法区(线程共享)

(1)程序计数器
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

  • 如果线程正在执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
  • 如果正在执行的是Native方法,这个计数器值为空;
  • 此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域

(2)虚拟机栈
虚拟机栈的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型;每个方法在执行的同时都会创建一个栈帧用于存储局:

  • 局部变量表(存放编译器可知的各种基本数据类型,对象引用类型)
  • 操作数栈
  • 动态链接
  • 方法出口 等

对于虚拟机栈这个区域规定了两种异常情况:

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  • OutOfMemoryError:虚拟机栈在拓展时无法申请到足够的内存空间

(3)本地方法栈(与虚拟机栈所发挥的作用相似)

  • 虚拟机栈:执行JAVA方法(字节码)服务
  • 本地方法栈:执行为虚拟机使用到的Native方法服务
  • 本地方法栈也会抛出StackOverflowErrorOutOfMemoryError异常

(4)JAVA堆

JAVA堆是JAVA虚拟机中内存最大的一块。JAVA堆在虚拟机启动时创建,被所有线程共享。
- 存放几乎所有的对象实例
- 垃圾收集主要管理的就是JAVA堆
- 物理上不连续,逻辑上连续

(5)方法区
方法区域java堆一样,是各个线程共享的内存区域,它用于存储:

  • 加载的类信息
  • 常量
  • 静态变量
  • 即时编译器编译后的代码数据

运行时常量池位于方法区,存放运行时产生的常量,比如String对象。方法区有两个值注意的地方:

  • 物理上不需要连续的内存,逻辑上连续即可
  • 可选择固定大小的内存区域
  • 可选择不实现垃圾收集

实战:outofmemoryerror异常
(1)在单线程下,无论是由于栈帧太大还是由于虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是java.lang.StackOverflowError异常。

package cn.test.two;

public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

    /**
     * 运行结果表明;在单线程下,无论是由于栈帧太大还是由于虚拟机栈容量太小,当
     * 内存无法分配时,虚拟机抛出的都是java.lang.StackOverflowError异常
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try{
            oo`这里写代码片`m.stackLeak();
        }catch (Throwable e) {
            System.out.println(oom.stackLength);
            throw e;
        }

    }
}

(2)不断创建多线程导致发生OutOfMemory异常。

package cn.test.two;

/**
 * 别运行,会死机
 * 多线程导致内存溢出
 * @author cjc
 *
 */
public class JavaVMStackOOM {
    private void dontStop(){
        while(true){

        }
    }

    public void stackLeakByThread(){
        while(true){
            Thread thread = new Thread(new Runnable() {

                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

(3)方法区的运行时常量池内存溢出

package cn.test.two;

import java.util.ArrayList;
import java.util.List;

/**
 * 方法区的运行时常量池内存溢出
 * @author cjc
 *
 */
public class RuntimeConstraintPoolOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

在内存溢出还是栈溢出方面,关注点在于java栈和java堆的常量池。

JVM是如何判定对象是否应该被回收

  1. 引用计数法

    原理:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
    缺点:这种算法不能解决对象之间相互循环引用的问题。所以主流的JAVA虚拟机没有选用引用计数算法来管理内存。(比如:obja.obj = objb; objb.obj = obja;)

  2. 可达性分析

    原理:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即不可达)时,则证明此对象不可用。如图:
    这里写图片描述
    在Java中,可作为GC Roots的对象包括:
    (1)虚拟机栈(栈帧中的本地变量表)
    (2)方法区中类静态属性引用的对象
    (3)方法区中常量引用的对象
    (4)本地方法栈中JNI

再谈引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值