精华内容
下载资源
问答
  • 文章目录JVM 内存模型线程私有区域程序计数器虚拟机栈栈帧的组成StackOverflowError本地方法栈逃逸分析优化线程私有部分的回收问题参考 JVM 内存模型 .java 源文件 -> javac 工具编译 -> .class 文件 -> ...

    JVM 内存模型

    JVM 内存模型

    .java 源文件 -> javac 工具编译 -> .class 文件 -> JVM 解析 -> 010101 机器码 -跑在不同的操作系统上。

    基于上面的流程可以看出,java 是一个跨平台语言。

    本节来分析 Java 对象如何进行分配回收

    JVM 运行时数据区主要由线程私有区域线程共享区域组成。

    1. 线程私有区域:
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器

    2.线程共享区域:

    • 方法区

    下面绘制一个草图来描述 JVM 运行数据区的组成:

    JVM 运行数据区

    线程私有区域

    线程私有区域组成为:

    • 程序计数器
    • 虚拟机栈
    • 本地方法栈

    程序计数器

    什么是程序计数器呢?

    因为 Java 本身就是一个多线程的,每一个线程都有一个程序计数器, CPU 在对线程上下文切换时,会使用程序计数器记录下当前线程正在执行的字节码指令的地址(行号),这样线程再次回来工作时,就知道执行到哪个位置了。

    为了更加深入的理解程序计数器,下面来看这样一段代码:

    反编译
    通过 javap -verbose JMMDemo.class 得到对应字节码:

    程序计数器

    上图红色标出的 Code 对应的这些数就是程序计数器了。

    虚拟机栈

    在了解虚拟机栈之前,先来看看栈这个概念

    栈是一种数据结构,入口只有一个。
    栈的特点:FILO,也就是先进后出。

    **面试题:**为什么虚拟机需要使用栈?

    非常符合JAVA中方法间的调用。

    例如以下方法的调用过程,就是方法的入栈和出栈过程。

    private void methodA(){
        methodB();
        println("methodA");
    }
    
    private void methodB() {
        methodC();
        println("methodB");
    
    }
    
    private void methodC() {
        println("methodC");
    }
    

    虚拟栈也是属于线程私有部分,在线程内部中一般会调用很多方法,而每一个方法使用一个栈帧来描述。

    下面用一个草图来描述一下栈帧虚拟机栈的关系:

    虚拟机栈是由多个栈帧组成,每调用一个方法就相当于有一个栈帧入栈到虚拟机栈中。

    栈帧

    栈帧的组成

    在前面描述过,在线程中,一个方法被调用就会一个栈帧被压入虚拟机栈中。栈帧就是用来描述这个方法,一个栈帧是由局部变量表操作数栈返回值地址动态链接组成。

    下面还是回到上面示例,结合草图,看它们之间的关系:

    虚拟机栈-栈帧

    局部变量表:

    存放方法内部变量表

    32位地址,寻址空间为 4G 。如果需要存放64位的数据,需要使用高位和地位表示。

    下面是 method() 生成的局部变量表:

    局部变量表

    • this //表示当前对象
    • o //new Object()
    • count //int count = 0;

    操作数栈:

    对局部变量表中的变量进行出栈入栈的操作。

    返回值地址:

    一个方法被执行之后,有一个返回值,返回给对应的调用处。

    动态链接:

    主要对应的多态,只有代码执行时才知道具体的实现类是那个对象。

    StackOverflowError

    这个异常想必很多人都遇过,字面意思就是栈溢出。我们通过上面的分析我们知道,虚拟机栈如果不断出现栈帧入栈,当虚拟机栈空间达到上限,那么就会出现 StackOverflowError

    下面来模拟这个错误的产生:

    public class StackOverflowError {
        private static int count = 0;
    
        public static void main(String[] args) {
            try {
                recursion();
            } catch (Throwable e) {
                System.out.println("deep of calling = " + count);
                e.printStackTrace();
            }
        }
    
        public static void recursion() {
            count++;
            recursion();
        }
    }
    

    StackOverflowError

    虚拟机栈空间大小可以通过 -Xss来设置,例如-Xss164K,缺省情况下是 1m 。

    如果是死循环出现这样的错误StackOverflowError,那么通过 -Xss 参数的设置也是没有用。

    本地方法栈

    虚拟机栈对应的方法是 Java 方法,而本地方法栈对应的是 native 方法。其他方面应该和虚拟机栈差不多。

    虚拟机规范无强制规定,各版本虚拟机自由实现,HotSpot直接把本地方法栈和虚拟机栈合二为一,当一个 JVM 创建的线程调用 native 方法后,JVM 不再为其在虚拟机栈中创建栈帧,JVM 只是简单地动态链接并直接调用native方法

    逃逸分析优化

    逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。

    public static StringBuffer craeteStringBuffer(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb;
    }
     
    public static String createStringBuffer(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }
    

    第一段代码中的sb就逃逸了,而第二段代码中的sb就没有逃逸。

    默认情况下,java 虚拟机是开启逃逸分析的选项 -XX:+DoEscapeAnalysis

    public class EscapeDemo  {
        public static void main(String[] args) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                generate();
            }
            System.out.println((System.currentTimeMillis() - start));
        }
        
    
        private static void generate() {
            User user = new User();//开始-XX:+DoEscapeAnalysis之后,该对象是在栈上分配
            user.age = 10;
            user.name = "hello";
        }
    
        private static class User{
            public String name;
            public int age;
        }
    }
    

    开启逃逸分析选项的输出结果:

    创建1亿个对象的时间:6
    

    从输出结果来看创建1亿个 User 对象的时间是非常短的,因为Java 虚拟机默认开始逃逸分析的选项。

    现在将逃逸分析选项关闭,并在控制台输出打印结果,同时开启 gc 日志。

    -XX:-DoEscapeAnalysis -XX:+PrintGC
    
    [GC (Allocation Failure)  65536K->680K(251392K), 0.0007842 secs]
    [GC (Allocation Failure)  66216K->696K(251392K), 0.0007729 secs]
    [GC (Allocation Failure)  66232K->648K(251392K), 0.0005273 secs]
    [GC (Allocation Failure)  66184K->608K(316928K), 0.0006086 secs]
    [GC (Allocation Failure)  131680K->688K(316928K), 0.0021467 secs]
    [GC (Allocation Failure)  131760K->624K(438272K), 0.0007702 secs]
    [GC (Allocation Failure)  262768K->529K(438272K), 0.0011675 secs]
    [GC (Allocation Failure)  262673K->529K(700416K), 0.0004699 secs]
    [GC (Allocation Failure)  524817K->529K(700416K), 0.0008970 secs]
    [GC (Allocation Failure)  524817K->529K(1015296K), 0.0003706 secs]
    创建1亿个对象的时间:717
    

    两者在创建1亿个对象的时间对比是几百倍的差距。

    线程私有部分的回收问题

    线程私有部分的内存空间是随线程产生而产生,随线程死亡而自动释放的。所以不需要像堆空间那样过多的考虑内存释放问题。

    参考

    https://blog.csdn.net/w372426096/article/details/80938788

    本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。

    记录于2019年4月15日

    展开全文
  • 一、 Java 虚拟机内存模型、 二、 程序计数器 ( 线程私有区 )、 三、 虚拟机栈 ( 线程私有区 )、 四、 本地方法栈 ( 线程私有区 )、 五、 方法区 ( 共享数据区 )、 1. 方法区、 2. 运行时常量池、 六、 堆区 ...





    一、 Java 虚拟机内存模型



    Java 内存优化 , 首当其冲就是处理 Java 内存泄漏问题 , 这是 Java 程序最主要的内存问题 , 大量的内存泄漏会导致内存溢出 ;



    Java 虚拟机内存机制 : Java 虚拟机中内存分为两部分 , 线程私有部分 , 共享数据区 ;


    ① 共享数据区 : 方法区 ( Method Area ) , 堆区 ( Heap Area ) ; 其中方法区中包含常量池 ;

    ② 线程私有数据区 : 程序计数器 ( PC ) , 虚拟机栈 ( VM Stack ) , 本地方法栈 ( Native Method Stack ) ;


    这是 Java 虚拟机规范定义的内存分区 , 但是具体的厂家实现可能不完全一致 , 如 Sun JDK , Open JDK 等 ;

    Android 中的 Java 虚拟机 跟上述 Java 规范有很大不同 ;





    二、 程序计数器 ( 线程私有区 )



    程序计数器 :


    ① 作用 : 该内存空间很小 , 主要用于指示执行的代码行 , 程序计数器指向的代码行 , 就是下一行将要执行的代码 ;

    ② 线程切换运行 : Java 多线程是抢占式执行的 , 经常出现线程 A A A 执行时 , 切换到线程 B B B , 如果线程 B B B 执行完毕回到线程 A A A , 这里就需要记住线程 A A A 之前执行到哪了 , 这就需要用到线程私有的数据区的程序计数器 ( PC ) ;

    ③ 执行 Java 代码 : 线程执行 Java 代码时 , 程序计数器记录的是虚拟机字节码地址 ;

    ④ 执行 Native C/C++ 代码 : 线程执行 native 代码时 , 程序计数器记录的 值是空值 null ;


    程序计数器 区域没有定义 内存溢出 异常 , 这个区域很小 ;





    三、 虚拟机栈 ( 线程私有区 )



    1. 虚拟机栈 ( VM Stack ) : 其生命周期与线程相同 , 描述的是 Java 方法执行的内存模型 , 该区域就是栈区 , 与堆区相对应 ;


    2. 虚拟机栈中保存的数据 :

    • 局部变量表
    • 操作栈
    • 方法返回地址
    • 动态链接
    • 额外附加信息




    四、 本地方法栈 ( 线程私有区 )



    本地方法栈 ( Native Method Stack ) : 这是 Native 层 C/C++ 提供的栈内存空间 , 该内存的类型与虚拟机栈内存类型一样 , 只是语言不同 , 一个 Java 方法的额栈 , 一个是 C/C++ 方法的栈 ;


    Hotspot VM 虚拟机中 , 虚拟机栈 与 本地方法栈是一块内存 , 二者合二为一 ;





    五、 方法区 ( 共享数据区 )





    1. 方法区


    方法区 : 存储以下内容 ;

    • 类信息 , 如 ClassLoader 加载的 Class
    • 常量 , 存放在运行时常量池中 , 该常量池也是方法区的一部分 ;
    • 静态变量 , static 变量
    • 即时编译器( JIT compiler ) 编译后的代码

    不同的虚拟机 , 实现不同 ;

    该区域一般不进行 GC 垃圾回收 ;




    2. 运行时常量池


    运行时常量池 :

    • 编译中的 Java 常量 ( public static final )
    • 字符串常量 ( String )
    • final 修饰的常量 ;
    • 符号引用 , 如 类或接口完整名称 ( 带包名 ) , 字段名 , 方法名 , 描述符 ;




    六、 堆区 ( 共享数据区 )



    Java 堆区 :


    ① 最大区域 : 该内存区是 Java 虚拟机管理的内存中最大的部分 , 是垃圾回收算法 GC 的主要操作区域 ;

    ② 内存溢出 : OOM ( OutOfMemory ) 内存溢出就是该区域内存被全部占用 , 无法为新的内存申请更多空间 ;





    七、 内存溢出类型



    内存溢出 :


    ① 栈内存溢出 : 在 Java 的栈区内存溢出 , 就是 StackOverflowException 栈溢出异常 , 在递归的时候 , 如果没有控制好 , 就会报该异常 ;

    ② 堆内存溢出 : 在 Java 堆内存中的溢出 , 就是 OutOfMemoryError 堆内存溢出 , 在加载大量数据到内存时 , 会出现该异常 ;





    八、 引用计数算法回收内存



    引用计数是早期的 GC 回收 Java 对象机制 , 有一定弊端 ;


    1. 引用计数简介 : 使用对象的引用计数 , 确定 Java 对象是否存活 , 确定是否应该被回收 ;


    2. 引用计数垃圾回收算法示例说明 :


    ① 创建对象 : 创建一个 O O O 类型对象 o o o , 此时引用计数为 0 , 如果不将其赋值给一个变量 , 那么很快就会被回收 ;

    ② 变量 A A A 赋值 : 创建一个 O O O 类型对象 o o o , 将对象 o o o赋值 给变量 A A A , 此时该对象 A A A 引用计数为 1 1 1 ;

    ③ 变量 B B B 赋值 : 创建一个 O O O 类型对象 o o o , 将对象 o o o赋值 给变量 B B B , 此时该对象 B B B 引用计数为 1 1 1 ;

    B B B 引用 A A A : 变量 B B B 中有 O O O 类型成员变量 , 将 A A A 赋值 给该成员变量 , 此时对象 B B B 引用计数变成 2 2 2 ;

    A A A 引用 B B B : 变量 A A A 中有 O O O 类型成员变量 , 将 B B B 赋值 给该成员变量 , 此时对象 A A A 引用计数变成 2 2 2 ;


    此时即使把 A , B A , B A,B 两个变量都设置成 null , 每个变量的引用计数都减一 , 也无法将引用计数减为 0 0 0 , 该对象永远无法回收 ;


    引用计数弊端 : 如果两个变量之间互相引用 , 引用计数永远不能变为 0 0 0 ;





    九、 可达性分析算法回收内存



    1. 可达性分析算法 : 以 GC Root 为分析的起点 , 查找对象的引用 , 如果找到一个对象 , 无法被 GC Root 直接或间接引用到 , 那么该对象就可以被回收了 ;


    2. GC Root 对象 : GC Root 是一个对象 , 可以是如下对象 ;

    • 虚拟机栈正在运行的引用
    • 静态属性
    • 常量
    • JNI 中的对象

    GC Root 就是不会被回收的那些的变量 , Android 中就是 Application , 单例类 , 运行中的 Activity 等 ;


    3. 第一次扫描回调 finalize 方法 : 对象经过可达性分析后 , 发现没有引用链可以达到 GC Root , 此时就会调用该对象的 finalize() 方法进行标记 , 开发者可以实现该方法 , 进行一些逻辑处理 :

    • ① 释放资源 : 可以执行一些资源释放方法 , 一面出现内存泄漏 ;
    • ② 引用自救 : 将对象赋值给指定变量 , 这样可以避免被 GC 回收内存 ;

    4. 可达性分析中对对象的两次扫描 : 可达性分析时 , 需要对指定对象标记两次 , 第一次被标记时会调用该对象 finalize() 方法 , 相当于判了死缓 , 此时可以通过添加引用的方式自救 , 如果没有进行任何干预 , 第 2 2 2 次扫描到该对象还没有到 GCRoot 的引用链 , 此时不会调用 finalize() 方法 , 直接就被回收了 ;

    展开全文
  • JAVA内存模型规定所有变量都存储在主内存 主内存是共享内存区域 所有线程都可以访问 但线程对变量的操作必须在工作内存进行 首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量操作,操作完成后再将...
  • 垃圾回收线程的意义在于实现虚拟机内存区域的自管理。 C++应用:堆内存的使用周期完全由应用本身来决定,其遵循“谁申请、谁释放,谁创建、谁销毁”的原则。 Java应用:堆内存的使用周期由应用和虚拟机共...

    声明:

    Author:赵志乾

    Date:2018-6-17

    Declaration:All Right Reserved!!!


    背景知识:

    1、jvm在启动时会拉起一些守护线程,其中包括垃圾回收线程。垃圾回收线程的意义在于实现虚拟机内存区域的自管理,尤其是在堆内存的管理上。下面是C++应用和Java应用在使用堆内存上的对比:

        C++应用:堆内存的使用周期完全由应用本身来决定,其遵循“谁申请、谁释放,谁创建、谁销毁”的原则。

        Java应用:堆内存的使用周期由应用和虚拟机共同完成,应用负责堆内存的申请和使用,而虚拟机则负责堆内存的回收。

    注:虚拟机中的gc(垃圾回收)线程作为守护线程,其本身也会占用CPU和内存。其所涉及的gc算法也是应用性能优化的重点。

    2、虚拟机为了便于管理从操作系统拿到的内存,其将所占用的内存从概念上划分出多个不同区域。这些区域各司其职、相互协作,支撑java应用的整个生命周期。

    区域:堆、虚拟机栈、本地方法栈、方法区、程序计数器(PC)。其中,虚拟机栈、本地方法栈和PC为线程私有,而堆和方法区为线程间共享。

    虚拟机线程私有化内存区域:

    1、程序计数器(PC)

        PC的概念源于CPU中的一个寄存器,如8086芯片中的IP寄存器,又称为指令指针。其作用是指向当前时刻需要获取指令的存放地址。

        虚拟机是个逻辑概念,其通过运行相应代码来模拟物理设备的行为。所以将物理设备中的IP寄存器的理念迁移到内存中,使用PC来表示。故PC的内容也是当前要获取指令的地址。

        虚拟机中通常会运行多个线程,如应用线程和守护线程。为了便于线程之间的快速切换,其将PC线程私有化。

    注:由于PC存放的仅仅是一条指令的地址,所以其本身不会出现OutOfMemoryError异常。这也是虚拟机内存中唯一不会出现OutOfMemoryError异常的区域。

    2、虚拟机栈

        微处器中最核心的两个概念:栈和中断。其中,栈的存在实现了方法调用,其依据当前线程的调用层级进行构建,位于栈顶的栈帧一定是当前方法所对应的栈帧。每个栈帧通常会包含以下几个部分:返回值、返回信息(地址)、入参、局部变量。

         C/C++中,允许将数组的完整内容以局部变量的形式存放在栈帧上。其面临的一个风险就是数组越界覆写返回信息,进而遭受攻击。当前某些型号设备号称加入了栈帧覆写校验,能够在一定程度上降低栈帧覆写攻击的风险。

        虚拟机则通过将数组、对象的实体存放于堆中,来避免栈帧覆写风险。即栈帧上存放的每一项所占空间大小非常明确,故其在编译时期就能够确定方法对应栈帧所占的最大槽数。由于虚拟机栈依据当前线程的调用层级进行构建,所以它也是线程私有的。

    3、本地方法栈

    本地方法栈和虚拟机栈的作用一样,只不过其服务于Native方法。


    邮箱:zhaozhiqian001@163.com

    参考资料:《深入理解java虚拟机》--周志明






    展开全文
  • Java虚拟机的多线程是通过各个线程的轮流切换并分配处理器的执行时间(cpu时间片)来实现的,同一时间处理器 只能执行一条线程,为了线程切换后可以恢复到原来执行的位置,每条线程需要一个独立的程序计数器,各个线程...

    java-运行时数据区域-程序计数器

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

    Java虚拟机的多线程是通过各个线程的轮流切换并分配处理器的执行时间(cpu时间片)来实现的,同一时间处理器 只能执行一条线程,为了线程切换后可以恢复到原来执行的位置,每条线程需要一个独立的程序计数器,各个线程的程序计数器互不影响,独立存储,这类内存区域为线程私有的内存。

    展开全文
  • Java内存模型与Java线程的实现原理

    万次阅读 2016-07-15 23:52:18
    Java内存模型与Java线程的实现原理
  • 线程共享区有: 堆Heap 如:对象new出来的对象实例,数组。 方法区:注意:jdk8之后叫元空间,里面主要存放类的信息,常量,静态...线程私有区有: 虚拟机栈Stack,本地方法栈Native,程序计数器,执行引擎。 ...
  • java堆区是用于存储对象实例的一块内存,同时也是垃圾回收器执行的重点区域,因为堆区是垃圾回收器的重点回收区域,所以gc极有可能会在大内存的使用和回收工作上称为性能瓶颈。为了解决这个问题,JVM考虑是否一定会
  • 1.程序计数器 是当前线程所执行的字节码的行号指示器,字节码解释器工作时...每个Java方法执行时都会创建一个栈帧到虚拟机栈。 一个栈帧包括局部变量表,操作栈,动态链接,方法出口等信息。 写递归程序出错时时...
  • Java内存模型即途中的Runtime Data Area区域 其中,一部分是线程私有的部分,另外一部分是线程共享的部分。 文章目录Java内存模型之线程私有部分Java内存模型之线程共享部分 Java内存模型之线程私有部分 私有部分为...
  • 一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数 器,这类内存也称为“线程私有”...这个内存区域是唯一一个在虚拟机没有规定任何 OutOfMemoryError 情况的区域。 ...
  • 此处的变量(Variable)与Java编程的变量略有区别,它包括实例变量/静态字段和构成数组对象的元素,不包括局部变量和方法参数(线程私有)。为获得较好的执行效能,Java内存模型并没有限制执行引擎使用处理器的...
  • 属于线程私有内存区域」 栈帧(Stack Frame) 栈帧(Stack Frame)是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack) 的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一...
  • Java内存模型与线程

    2020-05-12 11:36:39
    Java内存模型的主要目定是定义程序各种变量的访问规则,即关注在虚拟机把变量值存储到内存和从内存取出变量值这样的底层细节。此处的变量包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与...
  • 线程私有区域: 程序计数器: 程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。 Java虚拟机栈: 每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、...
  • Jvm运行时,内存区域可以划分为两大部分 1.线程私有(程序计数器,虚拟机栈,本地方法栈) 2.线程共享(堆与方法区) 程序计数器:因为cpu会划分为时间片给多个线程执行,所以需要程序计数器记录下指令执行到具体...
  • 背景知识:[java虚拟机]--(1)java内存区域--(1)线程私有区域虚拟机内存之线程间共享区域:1、堆 谈及内存区域的划分,一般会将内存区域简单的划分为堆内存和栈内存。当然,这只是一种粗粒度的划分,详细的划分见...
  • 栈也叫方法栈,是线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。 本地方法栈与栈类似,也是用来保存...
  • java线程内存模型 线程、工作内存、主内存三者之间的交互关系图: key edeas 所有线程共享主内存,每个线程有自己的工作内存 refreshing local memory to/from main memory must comply to JMM rules 产生...
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

    万次阅读 多人点赞 2020-01-03 21:42:24
    文章目录概述运行时数据区域程序计数器(线程私有Java虚拟机栈(线程私有)局部变量表操作数栈动态链接方法返回地址小结本地方法栈(线程私有Java堆(全局共享)方法区(全局共享)运行时常量池直接内存HotSpot...
  • java内存模型与线程

    千次阅读 2016-05-15 23:20:23
    Java内存模型规定了所有的变量都存储在主内存(此处的主内存仅仅指虚拟机内存),每条线程还有自己的工作内存,线程的工作内存保存了被线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存...
  • java内存区域

    2020-12-22 14:48:34
    了解Java GC机制,必须先清楚在JVM中内存区域的划分。在Java运行时的数据区里,由JVM管理的内存区域分为下图几个模块:1,程序计数器(Program Counter Register):程序计数器是一个比较小的内存区域,用于指示当前...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,936
精华内容 20,774
关键字:

java内存区域中属于线程私有的是

java 订阅