精华内容
下载资源
问答
  • Java 读取tomcat使用JVM内存信息

    千次阅读 2018-06-16 10:29:48
    Java代码如下:public static void main(String[] args) {  Runtime runtime = Runtime.getRuntime();  double freeMemory = (double)runtime.freeMemory()/(1024*1024);  double totalMemory = ...

    Java代码如下:

    public static void main(String[] args) {
            Runtime runtime = Runtime.getRuntime();
            double freeMemory = (double)runtime.freeMemory()/(1024*1024);
            double totalMemory = (double)runtime.totalMemory()/(1024*1024);
            double usedMemory = totalMemory - freeMemory;
            double percentFree = (usedMemory/totalMemory)*100.0;
            System.out.println(ToolsUtils.formatTosepara(percentFree));
        }
    

    很简单,只需要将值传入前端即可查看。

    效果如上图所示

    博主也找了一份jsp中的使用方法,如下所示:

    <%  
            double total = (Runtime.getRuntime().totalMemory()) / (1024.0 * 1024);  
            double max = (Runtime.getRuntime().maxMemory()) / (1024.0 * 1024);  
            double free = (Runtime.getRuntime().freeMemory()) / (1024.0 * 1024);  
            out.println("Java 虚拟机试图使用的最大内存量(当前JVM的最大可用内存)maxMemory(): " + max + "MB<br/>");  
            out.println("Java 虚拟机中的内存总量(当前JVM占用的内存总数)totalMemory(): " + total + "MB<br/>");  
            out.println("Java 虚拟机中的空闲内存量(当前JVM空闲内存)freeMemory(): " + free + "MB<br/>");  
            out.println("因为JVM只有在需要内存时才占用物理内存使用,所以freeMemory()的值一般情况下都很小,<br/>" +  
            "而JVM实际可用内存并不等于freeMemory(),而应该等于 maxMemory()-totalMemory()+freeMemory()。<br/>");  
            out.println("JVM实际可用内存: " + (max - total + free) + "MB<br/>");  
        %>
    


    版权属于: 技术客

    原文地址: https://www.sunjs.com/article/detail/0b56c0908eb54fec8a0ab04031a01bd1.html

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。




    展开全文
  • 下面小编就为大家带来一篇完美解决java读取大文件内存溢出的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • JVM内存结构和Java内存模型别再傻傻分不清了

    万次阅读 多人点赞 2020-03-04 20:53:30
    JVM内存结构和Java内存模型都是面试的热点问题,名字看感觉都差不多,网上有些博客也都把这两个概念混着用,实际上他们之间差别还是挺大的。 通俗点说,JVM内存结构是与JVM的内部存储结构相关,而Java内存模型是与多...

    JVM内存结构和Java内存模型都是面试的热点问题,名字看感觉都差不多,网上有些博客也都把这两个概念混着用,实际上他们之间差别还是挺大的。
    通俗点说,JVM内存结构是与JVM的内部存储结构相关,而Java内存模型是与多线程编程相关,本文针对这两个总是被混用的概念展开讲解。

    JVM内存结构

    JVM构成

    说到JVM内存结构,就不会只是说内存结构的5个分区,而是会延展到整个JVM相关的问题,所以先了解下JVM的构成。

    在这里插入图片描述

    • Java源代码编译成Java Class文件后通过类加载器ClassLoader加载到JVM中
      • 类存放在方法区
      • 类创建的对象存放在
      • 堆中对象的调用方法时会使用到虚拟机栈,本地方法栈,程序计数器
      • 方法执行时每行代码由解释器逐行执行
      • 热点代码由JIT编译器即时编译
      • 垃圾回收机制回收堆中资源
      • 和操作系统打交道需要调用本地方法接口

    JVM内存结构

    程序计数器

    在这里插入图片描述
    (通过移位寄存器实现)

    • 程序计数器是线程私有的,每个线程单独持有一个程序计数器
    • 程序计数器不会内存溢出

    虚拟机栈

    • 栈:线程运行需要的内存空间

    • 栈帧:每一个方法运行需要的内存(包括参数,局部变量,返回地址等信息)

    • 每个线程只有一 个活动栈帧(栈顶的栈帧),对应着正在执行的代码
      在这里插入图片描述

    • 常见问题解析

      • 垃圾回收是否涉及栈内存:不涉及,垃圾回收只涉及堆内存

      • 栈内存分配越大越好吗:内存一定时,栈内存越大,线程数就越少,所以不应该过大

      • 方法内的局部变量是否是线程安全的:

        • 普通局部变量是安全的
        • 静态的局部变量是不安全的
        • 对象类型的局部变量被返回了是不安全的
        • 基本数据类型局部变量被返回时安全的
        • 参数传入对象类型变量是不安全的
        • 参数传入基本数据类型变量时安全的
      • 栈内存溢出(StackOverflowError)

        • 栈帧过多

          • 如递归调用没有正确设置结束条件
        • 栈帧过大

          • json数据转换 对象嵌套对象 (用户类有部门类属性,部门类由用户类属性)
        • 线程运行诊断

          • CPU占用过高(定位问题)

            • ‘top’命令获取进程编号,查找占用高的进程
            • ‘ps H -eo pid,tid,%cpu | grep 进程号’ 命令获取线程的进程id,线程id,cpu占用
            • 将查看到的占用高的线程的线程号转化成16进制的数 :如6626->19E2
            • ‘ jstack 进程id ’获取进程栈信息, 查找‘nid=0X19E2’的线程
            • 问题线程的最开始‘#数字’表示出现问题的行数,回到代码查看
          • 程序运行很长时间没有结果(死锁问题)

            • ‘ jstack 进程id ’获取进程栈信息
            • 查看最后20行左右有无‘Fount one Java-level deadlock’
            • 查看下面的死锁的详细信息描述和问题定位
            • 回到代码中定位代码进行解决

    本地方法栈

    • 本地方法栈为虚拟机使用到的 Native 方法服务
    • Native 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口
    • 如notify,hashcode,wait等都是native方法

    • 通过new关键字创建的对象都会使用堆内存

    • 堆是线程共享的

    • 堆中有垃圾回收机制

    • 堆内存溢出(OutOfMemoryError)

      • 死循环创建对象
    • 堆内存诊断

      • 命令行方式

        • ‘jps’获取运行进程号
        • ‘jmap -heap 进程号’查看当前时刻的堆内存信息
      • jconsole

        • 命令行输入jconsole打开可视化的界面连接上进程
        • 可视化的检测连续的堆内存信息
      • jvisualvm

        • 命令行输入jvisualvm打开可视化界面选择进程
        • 可视化的查看堆内存信息

    方法区

    • 方法区只是一种概念上的规范,具体的实现各种虚拟机和不同版本不相同
      • HotSpot1.6 使用永久代作为方法区的实现
      • HotSpot1.8使用本地内存的元空间作为方法区的实现(但StringTable还是放在堆中)
        在这里插入图片描述
    • 常见问题
      • StringTable特性

        • 常量池中的字符串仅是字符,第一次使用时才变为对象

        • 利用串池机制,避免重复创建字符串

        • 字符串常量拼接原理是StringBuilder(1.8)

        • 字符串常量拼接原理是编译器优化

        • StringTable在1.6中存放在永久代,在1.8中存放在堆空间

        • intern方法主动将串池中没有的字符串对象放入串池

          • 1.8中:尝试放入串池,如果有就不放入,只返回一个引用;如果没有就放入串池,同时返回常量池中对象引用

          • 1.6中:尝试放入串池,如果有就不放入,只返回一个引用;如果没有就复制一个放进去(本身不放入),同时返回常量池中的对象引用

          • 字符串常量池分析(1.8环境)

            String s1 = "a";
            String s2 = "b";
            String s3 = "a"+"b";
            String s4 = s1+s2;
            String s5 = "ab";
            String s6 = s4.intern();
            
            
            System.out.println(s3==s4);// s3在常量池中,s4在堆上(intern尝试s4放入常量池,因为ab存在了就拒绝放入返回ab引用给s6,s4还是堆上的)
            System.out.println(s3==s5);// s3在常量池中,s4也在常量池中(字符串编译期优化)
            System.out.println(s3==s6);// s3在常量池中,s6是s4的intern返回常量池中ab的引用,所以也在常量池中
            
            
            String x2 = new String("c")+new String("d");
            String x1 = "cd";
            x2.intern();
            
            System.out.println(x1==x2);//x2调用intern尝试放入常量池,但常量池中已经有cd了,所以只是返回一个cd的引用,而x2还是堆上的引用
            
      • JVM调优三大参数(如: java -Xms128m -Xmx128m -Xss256k -jar xxxx.jar)

        • -Xss:规定了每个线程虚拟机栈的大小(影响并发线程数大小)
        • -Xms:堆大小的初始值(超过初始值会扩容到最大值)
        • -Xmx:堆大小的最大值(通常初始值和最大值一样,因为扩容会导致内存抖动,影响程序运行稳定性)
      • JVM内存结构中堆和栈的区别

        • 管理方式:栈自动释放,堆需要GC
        • 空间大小:栈比堆小
        • 碎片:栈产生的碎片远少于堆
        • 分配方式:栈支持静态分配和动态分配,堆只支持动态分配
        • 效率:栈的效率比堆高

    GC垃圾回收机制

    1. 垃圾判别方法

    引用计数算法
    • 判断对象的引用数量来决定对象是否可以被回收

    • 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1

    • 优点:执行效率高,程序执行受影响小

    • 缺点:无法检测出循环引用的情况,导致内存泄露

    可达性分析算法
    • Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活对象

    • 扫描堆中的对象,看是否能沿着GC Root对象为起点的引用链找到该对象,找不到则可以回收

    • 哪些对象可以作为GC Root

    • 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root

      • 虚拟机栈中的引用的对象

      • 本地方法栈中JNI(natice方法)的引用的对象

      • 方法区中的常量引用的对象

      • 方法区中的类静态属性引用的对象

      • 处于激活状态的线程

      • 正在被用于同步的各种锁对象

      • GC保留的对象,比如系统类加载器等。

    2. 垃圾回收算法

    标记清除法
    • 标记没有被GC Root引用的对象
    • 清除被标记位置的内存
    • 优点:处理速度快
    • 缺点:造成空间不连续,产生内存碎片
      在这里插入图片描述
    标记整理法
    • 标记没有被GC Root引用的对象
    • 整理被引用的对象
    • 优点:空间连续,没有内存碎片
    • 缺点:整理导致效率较低

    在这里插入图片描述

    复制算法
    • 分配同等大小的内存空间
    • 标记被GC Root引用的对象
    • 将引用的对象连续的复制到新的内存空间
    • 清除原来的内存空间
    • 交换FROM空间和TO空间
    • 优点:空间连续,没有内存碎片
    • 缺点:占用双倍的内存空间
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    3. 分代垃圾回收机制

    • 分代垃圾回收流程
      在这里插入图片描述

      • 对象首先分配在伊甸园区域
      • 新生代空间不足时,触发Minor GC,伊甸园和from存活的对象使用【复制算法】复制到to中,存活的对象年龄加一,并且交换from区和to区
      • Minor GC会引发Stop the world(STW)现象,暂停其他用户的线程。垃圾回收结束后,用户线程才恢复运行
      • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4位二进制)
      • 当老年代空间不足,会先尝试触发Minor GC,如果之后空间仍不足,会触发Full GC(STW时间更长,老年代可能使用标签清除或标记整理算法)
      • 当存放大对象新生代放不下而老年代可以放下,大文件会直接晋升到老年代
      • 当存放大对象新生代和老年代都放不下时,抛出OOM异常
    • 默认堆内存分配
      在这里插入图片描述

      • 新生代占1/3,老年代占2/3
      • -XX:NewRatio:老年代和年轻代内存大小的比例
      • 新生代中按8 1 1进行分配,两个幸存区大小需要保持一致
      • -XX:SurvivorRatio: Eden和Survivor的比值,默认是8(8:1)
    • GC相关VM参数
      在这里插入图片描述

    4. 垃圾回收器

    • 安全点(SafePoint)

      • 分析过程中对象引用关系不会发生改变的点

      • 产生安全点的地方:

        • 方法调用
        • 循环跳转
        • 异常跳转
      • 安全点的数量应该设置适中

    • 串行(SerialGC)

      • 单线程的垃圾回收器
      • 堆内存较小,CPU核数少,适合个人电脑
      • SerialGC收集器 (-XX:+UseSerialGC 复制算法) Client模式下默认的年轻代收集器
      • SerialGC Old收集器 (-XX:+UseSerialOldGC 标记-整理算法)Client模式下默认的老年代收集器
        在这里插入图片描述
    • 吞吐量优先(ParallelGC)

      • 多线程的垃圾回收器
      • 堆内存较大,多核CPU,适合服务器
      • 尽可能让单位时间内STW暂停时间最短(吞吐量=运行代码时间/(运行代码时间+垃圾回收时间))
      • 并行的执行
      • ParallelGC收集器(-XX:+UseParallelGC 复制算法) Server模式下默认的年轻代垃圾回收器
      • ParallelGC Old收集器(-XX:+UseParallelOldGC 复制算法)

      在这里插入图片描述

    • 响应时间优先(CMS -XX:+UseConcMarkSweepGC 标记清除算法)

      • 多线程的垃圾回收器

      • 堆内存较大,多核CPU,Server模式下默认的老年代垃圾回收器

      • 尽可能让单次STW暂停时间最短

      • 部分时期内可以并发执行

      • 执行流程

        • 初始标记:stop-the-world
        • 并发标记:并发追溯标记,程序不会停顿
        • 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
        • 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象
        • 并发清理:清理垃圾对象,程序不会停顿
        • 并发重置:重置CMS收集器的数据结构

    在这里插入图片描述

    • G1(-XX:+UseG1GC 复制+标记清除算法)

      • G1l垃圾回收器简介
      • 定义:Garbage First (2017 jdk9 默认)
      • 特点
        • 并发和并行
        • 分代收集
        • 空间整合
        • 可预测的停顿
      • 使用场景
        • 同时注重吞吐量和低延迟,默认暂停目标是200ms
        • 超大堆内存,会将整个堆划分为多个大小相等的Region(新生代和老年代不再物理隔离了)
        • 整体上是标记整理算法,两个区域之间是复制算法
    • 垃圾回收阶段

      • 新生代垃圾收集

        • 会发生STW
      • 新生代垃圾收集+并发标记

        • 在Young GC时会进行GC Root的初始标记
        • 老年代占用堆内存空间比例达到阈值时,进行并发标记(不会STW)
      • 混合收集,对新生代,幸存区和老年代都进行收集

        • 最终标记,会STW
        • 拷贝存活,会STW
        • 三种阶段循环交替
          在这里插入图片描述
    • Full GC

      • SerialGC

        • 新生代内存不足发生的垃圾收集:minor GC
        • 老年代内存不足发生的垃圾收集:full GC
      • ParallelGC

        • 新生代内存不足发生的垃圾收集:minor GC
        • 老年代内存不足发生的垃圾收集:full GC
      • CMS

        • 新生代内存不足发生的垃圾收集:minor GC

        • 老年代内存不足

          • 并发收集成功:并发的垃圾收集
          • 并发收集失败:串行的full GC
      • G1

        • 新生代内存不足发生的垃圾收集:minor GC

        • 老年代内存不足,达到阈值时进入并发标记和混合收集阶段

          • 如果回收速度>新产生垃圾的速度 :并发垃圾收集
          • 如果回收速度<新产生垃圾的速度:串行的full GC

    5. 四种引用

    在这里插入图片描述

    • 强引用

      • 最常见的对象:通过new关键字创建,通过GC Root能找到的对象。
      • 当所有的GC Root都不通过【强引用】引用该对象时,对象才能被垃圾回收
    • 软引用

      • 仅有【软引用】引用该对象时,在垃圾回收后,内存仍不足时会再次发起垃圾回收,回收软引用对象

      • 可以配合引用队列来释放软引用自身

      • 创建一个软引用:SoftReference ref = new SoftReference<>(new Object());

      • 软引用被回收后,仍然还保留一个null,如将软引用加入集合,回收后遍历集合仍然还存在一个null

        • 解决:使用引用队列,软引用关联的对象被回收时,软引用自身会被加入到引用队列中,通过queue.poll()取得对象进行删除
        • 创建一个而引用队列:ReferenceQueue queue = new ReferenceQueue<>();
        • 创建加入了引用队列的软引用:SoftReference ref = new SoftReference<>(new Object(),queue);
    • 弱引用

      • 仅有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
      • 可以配合引用队列来释放弱引用自身
      • 创建一个弱引用:WeakReference ref = new WeakReference<>(new Object());
      • 引用队列使用同软引用
    • 虚引用

      • 必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将【虚引用】入队,由Reference Hanler线程调用虚引用相关方法释放【直接内存】(unsafe类中方法)
    • 终结器引用

      • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用队列入队(引用对象暂未回收),再由Finalizer线程通过终结器引用找到被引用对象并调用他的finalize方法,第二次gc时回收被引用对象

    类加载

    类加载器的分类

    在这里插入图片描述

    类加载过程

    在这里插入图片描述

    • 加载

      • 通过ClassLoader加载Class文件字节码,生成Class对象
    • 链接

      • 校验:检查加载的的Class的正确性和安全性

      • 准备:为类变量分配存储空间并设置类变量初始值

      • 解析:JVM将常量池内的符号引用转换为直接引用

    • 初始化

      • 执行类变量赋值和静态代码块

    LoadClass和forName的区别

    • Class.ForName得到的class是已经初始化完成的
    • ClassLoader.loadClass得到的class是还没有链接的

    双亲委派机制

    在这里插入图片描述

    • 什么是双亲委派机制
      • 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
    • 为什么要使用双亲委派机制
      • 防止重复加载同一个.class文件,通过委托去向上级问,加载过了就不用加载了。
      • 保证核心.class文件不会被串改,即使篡改也不会加载,即使加载也不会是同一个对象,因为不同加载器加载同一个.class文件也不是同一个class对象,从而保证了class执行安全

    自定义类加载器

    • 需求场景

      • 想要加载非classpath的随意路径的类文件
      • 通过接口来使用实现,希望解耦合
    • 步骤

      • 继承Classloader父类
      • 遵循双亲委派机制,重写findClass方法(不能重写loadClass,重写了就不符合双亲委派了)
      • 读取类的字节码
      • 调用父类的defineClass方法加载类
      • 使用者调用类加载的loadClass方法
    • 案例演示

    创建自定义类加载器

    public class MyClassLoader extends ClassLoader {
        private String path;
        private String classLoaderName;
    
        public MyClassLoader(String path, String classLoaderName) {
            this.path = path;
            this.classLoaderName = classLoaderName;
        }
    
        //用于寻找类文件
        @Override
        public Class findClass(String name) {
            byte[] b = loadClassData(name);
            return defineClass(name, b, 0, b.length);
        }
    
    
        //用于加载类文件
        private byte[] loadClassData(String name) {
            name = path + name + ".class";
    
            try (InputStream in = new FileInputStream(new File(name));
                 ByteArrayOutputStream out = new ByteArrayOutputStream();) {
                int i = 0;
                while ((i = in.read()) != -1) {
                    out.write(i);
                }
                return out.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    调用自定义类加载器加载类

    public class MyClassLoaderChecker {
        public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
            MyClassLoader m = new MyClassLoader("C:\\Users\\73787\\Desktop\\","myClassLoader");
            Class<?> c = m.loadClass("Robot");
            System.out.println(c.getClassLoader());
            c.newInstance();
        }
    }
    

    反射机制

    反射的定义

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

    反射的常用场景

    第三方应用开发过程中,会需要某个类的某个成员变量、方法或是属性是私有的或者只对系统应用开放,就可以通过Java的反射机制来获取所需的私有成员或者方法

    反射相关的类

    在这里插入图片描述

    Class类:

    代表类的实体,在运行的Java应用程序中表示类和接口

    • 获得类的方法

    在这里插入图片描述

    • 获得类中属性的方法

    在这里插入图片描述

    • 获得类中方法的方法
      在这里插入图片描述
    • 获取类中构造器的方法
      在这里插入图片描述
    Filed类

    Filed代表类的成员变量(属性)

    在这里插入图片描述

    Method类

    在这里插入图片描述

    Constructor类

    在这里插入图片描述

    案例

    定义一个Robot类

    public class Robot {
        //私有属性
        private String name;
        //公有方法
        public void sayHi(String hello){
            System.out.println(hello+" "+name);
        }
        //私有方法
        private String thorwHello(String tag){
            return "hello "+tag;
        }
    }
    

    编写一个反射应用类,针对私有的属性和方法必须设置setAccessible(true)才能进行访问

    public class ReflectSample {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
            //加载类
            Class<?> rc = Class.forName("leetcode.Robot");
            //获取类实例
            Robot r = (Robot)rc.newInstance();
            //打印类名
            System.out.println(rc.getName());
            
            //加载一个私有方法
            Method getHello = rc.getDeclaredMethod("thorwHello",String.class);
            getHello.setAccessible(true);
            Object bob = getHello.invoke(r, "bob");
            System.out.println(bob);
           
             //加载一个公有方法
            Method sayHi = rc.getMethod("sayHi",String.class);
            Object welcome = sayHi.invoke(r,"welcome");
           
             //加载一个私有属性
            Field name = rc.getDeclaredField("name");
            name.setAccessible(true);
            name.set(r,"tom");
            sayHi.invoke(r,"welcome");
        }
    }
    

    Java内存模型

    什么是Java内存模型(JMM)

    • 通俗来说,JMM是一套多线程读写共享数据时,对数据的可见性,有序性和原子性的规则

    为什么会有Java内存模型

    JVM实现不同会造成“翻译”的效果不同,不同CPU平台的机器指令有千差万别,无法保证同一份代码并发下的效果一致。所以需要一套统一的规范来约束JVM的翻译过程,保证并发效果一致性

    原子性

    • 什么是原子性
      • 原子性指一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分的。
    • 原子性怎么实现
      • 使用synchronized或Lock加锁实现,保证任一时刻只有一个线程访问该代码块
      • 使用原子操作
    • Java中的原子操作有哪些
      • 除long和double之外的基本类型的赋值操作(64位值,当成两次32位的进行操作)
      • 所有引用reference的赋值操作
      • java.concurrent.Atomic.*包中所有类的原子操作
    • 创建对象的过程是否是原子操作(常应用于双重检查+volatile创建单例场景)
      • 创建对象实际上有3个步骤,并不是原子性的
        • 创建一个空对象
        • 调用构造方法
        • 创建好的实例赋值给引用

    可见性

    • 什么是可见性问题
      • 可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。
    • 为什么会有可见性问题、
      • 对于单线程程序来说,可见性是不存在的,因为我们在任何一个操作中修改了某个变量的值,后续的操作中都能读取这个变量值,并且是修改过的新值。
      • 对于多线程程序而言。由于线程对共享变量的操作都是线程拷贝到各自的工作内存进行操作后才写回到主内存中的,这就可能存在一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题
        在这里插入图片描述
    • 如何解决可见性问题
      • 解决方法1:加volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值
      • 解决方法2:使用synchronized和Lock保证可见性。因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中
    • 案例
    /**
    * 〈可见性问题分析〉
    *
    * @author Chkl
    * @create 2020/3/4
    * @since 1.0.0
    */
    public class FieldVisibility {
        int a = 1;
        int b = 2;
    
        private void change() {
            a = 3;
            b = a;
        }
        private void print() {
            System.out.println("b=" + b + ";a=" + a);
        }
        public static void main(String[] args) {
            while (true) {
                FieldVisibility test = new FieldVisibility();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        test.change();
                    }
                }).start();
    
    
    
    
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        test.print();
                    }
                }).start();
            }
        }
    }
    

    循环创建两类线程,一个线程用于做值的交换,一个线程用于打印值

    比较直观的三种结果

    • 打印线程先执行:b = 2, a = 1
    • 交换线程先执行:b = 3, a = 3
    • 交换线程执行到一半就切出去打印了,只执行了a=3赋值操作:b = 2 , a =3

    实际上除了很容易想到的三种情况外还有一种特殊情况:b = 3 , a = 1

    • 这种情况就是可见性问题
    • a的值在线程1(执行交换线程)的本地缓存中进行了更新,但是并没有同步到共享缓存,而b的值成功的更新到了共享缓存,导致线程2(执行打印线程)从共享缓存中获取到的数据并不是实时的最新数据
      -在这里插入图片描述

    有序性(重排序)

    • 什么是重排序
      • 在线程内部的两行代码的实际执行顺序和代码在Java文件中的逻辑顺序不一致,代码指令并不是严格按照代码语句顺序执行的,他们的顺序被改变了,这就是重排序。
    • 重排序的意义
      • JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。
      • 案例
        计算:
        a = 3;
        b = 2;
        a = a + 1;
        重排序优化前的instructions
        
        load a
        set to 3
        store 3
        
        load b
        set to 2
        store b
        
        load a
        set to 4
        store a
        
        经过重排序处理后
        
        load a
        set to 3
        set to 4
        store a
        
        
        load b
        set to 2
        store b
        
        上述少了两个指令,优化了性能
        
    • 重排序的3种情况
      • 编译器优化( JVM,JIT编辑器等): 编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序
      • 指令级并行的重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
      • 内存系统的重排序: 由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
        在这里插入图片描述

    volatile

    • 什么是volatile

      • volatile是一种同步机制,比synchronized或者Lock相关类更轻量级,因为使用volacile并不会发生上下文切换等开销很大的行为
      • volatile是无锁的,并且只能修饰单个属性
    • 什么时候适合用vilatile

      • 一个共享变量始终只被各个线程赋值,没有其他操作
      • 作为刷新的触发器,引用刷新之后使修改内容对其他线程可见(如CopyOnRightArrayList底层动态数组通过volatile修饰,保证修改完成后通过引用变化触发volatile刷新,使其他线程可见)
    • volatile的作用

      • 可见性保障:修改一个volatile修饰变量之后,会立即将修改同步到主内存,使用一个volatile修饰的变量之前,会立即从主内存中刷新数据。保证读取的数据都是最新的,之前的修改都是可见的。
      • 有序性保障(禁止指令重排序优化):有volatile修饰的变量,赋值后多了一个“内存屏障“( 指令重排序时不能把后面的指令重排序到内存屏障之前的位置)
    • volatile的性能

      • volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

    happens-before规则

    什么是happens-before规则:前一个操作的结果可以被后续的操作获取。

    • 程序的顺序性规则:在一个线程内一段代码的执行结果是有序的。虽然还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变!
    • volatile规则: 就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。
    • 传递性规则:happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C。
    • 管程锁定规则:无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)
    • 线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。
    • 线程终止规则: 在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
    • 线程中断规则: 对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
    • 对象终结规则:一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。

    如果有用,点个赞再走吧

    展开全文
  • jvm内存模型和java内存模型

    千次阅读 多人点赞 2018-12-03 21:46:29
    初识java虚拟机,就碰到一系列不懂的问题。我们以前常说java把局部变量放在栈里,new出来的变量放在堆里,然后堆里的数据不定时就给回收了。...直至今天,我看了java线程通讯之java内存模型,出现了主内存、...

    初识java虚拟机,就碰到一系列不懂的问题。我们以前常说java把局部变量放在栈里,new出来的变量放在堆里,然后堆里的数据不定时就给回收了。然后,如果是多线程的话,每个线程自己都有会一个私有的虚拟机栈,运行每个方法时都会创建一个栈帧,栈帧里存储着局部变量啦等信息,方法结束栈帧就出栈。这好像就是我们对java内存模型的第一次理解吧。直至今天,我看了java线程通讯之java内存模型,出现了主内存、工作内存等相关的字眼,说什么工作内存从主内存拷贝一份共享变量做高速缓存,每个线程拥有一个工作内存,这什么内存模型,又解决了什么问题呢。甚是不解,搜索了半天。原来一开始我们就搞混乱了。我们之前说的栈堆那个是JVM内存模型啊。

    区别:JVM内存模型则是指JVM的内存分区,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。

    我们可以看到,Java堆和方法区的区域是多个线程共享的数据区域。也就是说,多个线程可以操作保存在堆或者方法区中的同一个数据。这也就是我们常说的“Java的线程间通过共享内存进行通信”。那么java虚拟机从底层就怎么操作的呢。

    JMM并不像JVM内存结构一样是真实存在的,只是一个抽象的概念。JMM是和多线程相关的,描述了一组规则或规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的。简单总结下,Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。所以,我理解的就是JMM就是为了解决Java多线程对共享数据的读写一致性问题而产生的一种模型!

     

    下图是反映了主内存与线程工作内存(即是本地内存)之间的关系:

    JMM的主要目标是实现在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程里面的变量有所不同步,它包含了实例字段、静态字段和构成数组对象的元素,但不包含局部变量和方法参数,因为后者是线程私有的,不会共享,当然不存在数据竞争问题。所以,我理解的是上图中的共享变量只是堆中的部分变量。而JMM规定了所有的变量都存储在主内存中,这个变量我想都是堆变量,如果硬是要跟JVM做一下匹配,那么我想主内存就相当于堆内存和方法区。然后线程copy的共享变量只是对部分堆变量的copy。那么,可想而知,如果硬是要匹配,我想工作内存里是包含两部分:线程私有的栈(这里不共享,也就没啥事),对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓冲区)。

    看张图就清晰了,

    每个线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。

    分析上图:

    1.线程A把从工作内存更新过的共享变量刷新到主内存中去。

    2.线程B到主内存中去读取线程A刷新过的共享变量,然后copy一份到自己的工作内存里去。

    虽然是这样,并没有解决并发问题啊,不可见?不具备原子性。这时候JMM定义了一些语法集,就是volatile、synchronized啦。

    例如可见性吧,一个线程对共享变量做了修改之后,其他的线程立即能够看到该变量这种变化。Java内存模型是对共享变量进行volatile修饰,然后一个线程通过将在工作内存中的变量修改后的值同步到主内存,另一个线程会立马会看到变化,然后从主内存刷新最新值到工作内存中,这种依赖主内存的方式来实现可见性的。

     

    总之,JVM内存模型是真的内存结构管理,Java内存模型只是为了适应和解决多线程通信而产生的一种模型,通过一些关键字修饰就可以实现并发。

     

    展开全文
  • javajvm内存模型解析以及GC回收机制 jvm是什么 Java Virtual Machine,也就是Java虚拟机,java程序的跨平台特性主要是指字节码文件可以在任何具有java虚拟机的计算机或设备上运行。 jvm内存模型结构 jvm内存模型...

    java中jvm内存模型解析以及GC回收机制

    jvm是什么

    Java Virtual Machine,也就是Java虚拟机,java程序的跨平台特性主要是指字节码文件可以在任何具有java虚拟机的计算机或设备上运行。
    在这里插入图片描述

    jvm内存模型结构

    jvm内存模型结构包括五大区域,分别为栈(jvm栈)、堆、本地方法栈、程序计数器、方法区。
    在这里插入图片描述
    栈:Java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表,操作数栈,动态链接,方法出口等信息)不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致。

    堆:Java虚拟机管理的最大的一块内存区域,Java堆是线程共享的,用于存放对象实例。也就是说对象的出生和回收都是在这个区域进行的。

    本地方法栈:和栈作用很相似,区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行native方法服务。登记native方法,在Execution Engine执行时加载本地方法库。

    程序计数器:就是一个指针,指向方法区中的方法字节码(用来存储指向下一个指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

    方法区:线程共享,用于存储已经被虚拟机加载的类信息、常量、静态变量等数据。
    JDK1.8与1.7最大的区别是在1.8中方法区是由元空间(元数据区)来实现。常量池移到堆中。

    其中栈、本地方法栈、程序计数器是线程私有,也就意味着有多少线程就会有多少栈区,本地方法栈区、程序计数器区。而堆区和方法区是全局共享的。

    GC垃圾回收机制

    当一个函数执行时,对应着jvm栈中会开辟一块空间,对应着存放函数中创建的对象在堆中的地址值,当函数执行完毕,随着线程结束,栈中区域会自动清除,但是堆中开辟的存放对象内容的空间不会随之清除,因为无法确定其他线程是否也引用了堆中此对象,因而用到了GC回收机制。

    堆区空间结构

    堆中的空间是用来存储我们new出来的对象的,每一个对象都会在堆区占用一块空间,当堆区空间满了之后内存就会爆掉,程序会挂掉。

    堆区空间结构

    所以在进行回收时,我们应该判断这个对象是否应该被清除,那么判断的标准是什么呢?这时会采取一些算法进行相应的清理操作。
    **1.标记清理算法:**对一个对象需要被删除时进行标记处理,然后将标记的对象全部清理掉,幸存下来的对象进入到S0或S1区,但是此清除算法在对象被删除之后会在内存中产生内存碎片,内存碎片例如两个对象大小分别是1k,其被清理后会产生一个2k的内存区域,此时有一个2k的内存对象,是不能够进行存放的,所以清理后的2k内存空间不能被合理的利用起来,所以会有另外一种算法进行应对,也就是标记整理算法。
    **2.标记整理算法:**此算法和标记清理算法相似,但是当内存对象被清理后产生空间,所有存在的对象会往前顶,占用掉被清理的空间,提高内存空间利用率减少内存碎片,但此算法缺点是代价较大,所有的内存对象都会前移。
    **3.复制算法:**此算法将内存分为两个区域,当标记的对象需要清理时,将不需要清理的对象全部紧凑复制到另一个区,需要清理的就不进行复制,这样会减少开销,但是复制算法缺点是需要两倍的内存。

    堆区内存实际进行GC算法时要复杂得多。

    **4.分代收集算法:**现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代(Young)和老年代(Tenure)。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。(如上图)

    本人第一次写博客,技术较差,如有错误欢迎指正。

    展开全文
  • 区分-JVM内存分区和Java内存模型(Java Memory Model)

    千次阅读 多人点赞 2019-05-08 17:55:13
    JVM内存分区具体指的是JVM中运行时数据区的分区。 JMM是一种规范,是抽象的概念,目的是解决由于多线程并发编程通过内存共享进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序...
  • java JVM内存结构之PC寄存器 本篇自己学习记录,如有错误,请各位大佬指正! JVM 内存模型图 一 ,PC寄存器介绍(PC Register) ​ JVM中的PC程序计数寄存器(Program Counter Register)中,Register 的命名源于cpu中的...
  • 一、JAVA内存结构 1.1 JVM启动流程: 1.2 JVM基本结构 1.2.1基本结构图 1.2.2 Java中的内存分配 二、Java内存模型 2.1 主内存和工作内存 2.2 内存间交互操作 2.3 java内存模型对并发提供的保障:原子性、...
  • 比如本文我们要讨论的JVM内存结构、Java内存模型和Java对象模型,这就是三个截然不同的概念,但是很多人容易弄混。 可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、Java内存模型和Java
  • 一文搞懂JVM内存结构

    万次阅读 多人点赞 2019-04-11 20:30:23
    Java 虚拟机是中、高级开发人员必须修炼的知识,有着较高的学习门槛,很多人都不情愿去接触它。可能是觉得学习成本较高又或者是感觉没什么实用性,所以干脆懒得“搭理”它了。其实这种想法是错误的。举个最简单的...
  • Java内存模型和JVM内存管理

    千次阅读 2017-09-11 15:56:18
    Java内存模型和JVM内存管理   一、Java内存模型: 1、主内存和工作内存(即是本地内存):  Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量...
  • JVMJava内存区域和Java内存模型

    千次阅读 2018-03-12 15:56:28
    第一遍读这本书的时候感觉能看懂,但是抓不住重点,关键就记了下以下的概念: 1.Java内存区域。 2.判断对象是否可被回收。 3.垃圾回收算法。 4.类加载机制、双亲委派模型。 5.静态分派和动态分派(实现多态的...
  • 1)JVM内存结构; 2)Java内存模型
  • JDK8 JVM内存模型

    千次阅读 2019-09-04 08:50:22
    JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行 不同的JVM对于内存的划分方式和管理机制存在着部分差异 结合JVM虚拟机规范,来探讨经典的JVM内存布局 运行时内存: 1 ...
  • 29、JVM内存模型(区别Java内存模型)   JVM内存主要分为:程序计数器,Java虚拟机栈,本地方法栈,Java堆,方法区。   1、程序计数器: 为了线程切换能恢复到正确的执行位置,每条线程都需要一个独立的程序...
  • JVM内存模型和JVM内存结构

    千次阅读 2019-05-20 16:03:05
    Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问...
  • JVMJava内存模型

    2018-04-22 15:51:18
    JVM内部,Java内存模型把内存分成了两部分:线程栈区和堆区,下图展示了Java内存模型在JVM中的逻辑视图: JVM中运行的每个线程都拥有自己的线程栈,线程栈包含了当前线程执行的方法调用相关信息,我们也把...
  • 详细介绍了JVM运行时数据区域,包括方法区、堆空间、栈空间、本地方法栈、程序计数器、常量池、直接内存、字面量、符号引用、直接引用。
  • 深入理解JVM - Java内存模型与线程

    千次阅读 2018-02-07 16:55:09
    1、硬件的效率与一致性由于计算机的存储设备与处理器的运算速度有好几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存来...,这样处理器就无须等待缓慢的内存读写了...
  • javajvm内存详解

    2015-01-28 20:54:25
    在一些规模稍大的应用中,Java虚拟机(JVM)的内存设置尤为重要,想在项目中取得好的效率,GC(垃圾回收)的设置是第一步。 PermGen space:全称是Permanent Generation space.就是说是永久保存的区域,用于存放...
  • 文章目录操作系统内存与JVM内存模型寄存器的价值高速缓存高速缓存的价值高速缓存运作原理高速缓存的回收机制高速缓存与命中率缓存的写策略缓存一致性协议多线程与多核Jvm、Dalvik和Art的区别Java内存模型工作内存...
  • Java虚拟机(JVM)你只要看这一篇就够了!

    万次阅读 多人点赞 2018-08-14 12:55:02
    1. Java 内存区域与内存溢出异常 1.1 运行时数据区域 根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。 1.1.1 程序计数器 内存空间小,线程私有。字节码解释器工作是...
  • 与其他高级语言不一样,在Java中基本上不会显示地调用分配内存的函数,我们甚至不用关心到底哪些程序指令需要分配内存,哪些不需要分配内存。 我们首先需要从操作系统层面理解物理内存的分配和Java运行的内存分配...
  • JVM(十一)Java 内存模型

    千次阅读 2018-06-11 12:24:26
    Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。在此之前,主流程序语言(如C/C++等)...
  • 注意:java内存模型和jvm内存结构(堆、栈、方法区。。。)别搞混了 jvm内存结构是这个啊: JMM(Java Memory Model): 共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能...
  • 1、JVM内存模型和JMM(Java内存模型)不是一回事,JMM来源于JSR-133:memory_model-1_0-pfd-spec.pdf. JMM的目的是为了解决Java多线程对共享数据的读写一致性问题,通过Happens-Before语义定义了Java程序对数据的...
  • JVM内存模型

    万次阅读 多人点赞 2019-04-07 16:21:07
    •介绍下 Java 内存区域(运行时数据区)•Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)•对象的访问定位的两种方式(句柄和直接指针两种方式) 拓展问题 •String类和常量池•8种...
  • Java面试之深入理解JVM一JVM内存模型

    千次阅读 2019-07-05 10:52:28
    JVM一直是java知识里面进阶阶段的重要部分,如果希望在java领域研究的更深入,则JVM则是如论如何也避开不了的话题,本系列试图通过简洁易读的方式,讲解JVM必要的知识点。 一、运行流程 我们都知道java一直宣传的...
  • 怎么说呢,本人菜鸟一枚,费了几天时间,终于做了一个用java获取JVM的CPU占用率、内存占用率、线程数及服务器的网口吞吐率、磁盘读写速率的实现。 其中windows环境下获取jvm 的cpu占用率这里是参考网上别人的东西...
  • 003-Java虚拟机JVM内存模型

    千次阅读 2020-05-14 17:08:12
    Java知识点总结系列目录 类加载器将Class文件读取后,放到运行时数据区,然后执行引擎执行或调用本地接口、本地库。 1、方法区(元空间) 线程共享 JDK1.8后叫元空间Metaspace,存储在本地内存中 JDK1.8前叫永久代...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 166,602
精华内容 66,640
关键字:

java读取jvm内存

java 订阅