精华内容
下载资源
问答
  • 为什么新生代内存需要有个Survivor区

    万次阅读 多人点赞 2016-05-16 15:34:55
    内存分为新生代老年代,其中新生代分为Eden和两块Survivor,本文探讨了为什么要设置块Survivor区

    在我的上一篇博客中,介绍了JVM堆内存的结构以及在堆中进行的GC机制,链接是浅谈JAVA GC机制与性能优化

    那么,在JVM的新生代内存中,为什么除了Eden区,还要设置两个Survivor区?

    1 为什么要有Survivor区

    先不去想为什么有两个Survivor区,第一个问题是,设置Survivor区的意义在哪里?
    堆内存分类

    如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。你也许会问,执行时间长有什么坏处?频发的Full GC消耗的时间是非常可观的,这一点会影响大型程序的执行和响应速度,更不要说某些连接会因为超时发生连接错误了。

    好,那我们来想想在没有Survivor的情况下,有没有什么解决办法,可以避免上述情况:

    方案优点缺点
    增加老年代空间更多存活对象才能填满老年代。降低Full GC频率随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长
    减少老年代空间Full GC所需时间减少老年代很快被存活对象填满,Full GC频率增加

    显而易见,没有Survivor的话,上述两种解决方案都不能从根本上解决问题。

    我们可以得到第一条结论:Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

    2 为什么要设置两个Survivor区

    设置两个Survivor区最大的好处就是解决了碎片化,下面我们来分析一下。

    为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。假设现在只有一个survivor区,我们来模拟一下流程:
    刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化
    我绘制了一幅图来表明这个过程。其中色块代表对象,白色框分别代表Eden区(大)和Survivor区(小)。Eden区理所当然大一些,否则新建对象很快就导致Eden区满,进而触发Minor GC,有悖于初衷。
    一个Survivor区带来碎片化

    碎片化带来的风险是极大的,严重影响JAVA程序的性能。堆空间被散布的对象占据不连续的内存,最直接的结果就是,堆中没有足够大的连续内存空间,接下去如果程序需要给一个内存需求很大的对象分配内存。。。画面太美不敢看。。。这就好比我们爬山的时候,背包里所有东西紧挨着放,最后就可能省出一块完整的空间放相机。如果每件行李之间隔一点空隙乱放,很可能最后就要一路把相机挂在脖子上了。

    那么,顺理成章的,应该建立两块Survivor区,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。下图中每部分的意义和上一张图一样,就不加注释了。
    两块Survivor避免碎片化
    上述机制最大的好处就是,整个过程中,永远有一个survivor space是空的,另一个非空的survivor space无碎片

    那么,Survivor为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果Survivor区再细分下去,每一块的空间就会比较小,很容易导致Survivor区满,因此,我认为两块Survivor区是经过权衡之后的最佳方案。

    说明
    本人水平有限,不当之处希望各位高手指正。另外,文中的插图都是我自己在word的smart art中绘制的,看起来不精致请见谅。
    如有转载请注明出处
    http://blog.csdn.net/antony9118/article/details/51425581

    展开全文
  • linux共享内存两种方式

    千次阅读 2017-05-04 11:45:28
    linux共享内存两种方式: 第一种:mmap方式,适用场景:父子进程之间 第二种:shmget方式,适用场景:同一台电脑上不同进程之间 两种方式的文档网上都有很多,随便一抓一把。 通病:共享内存没有自带的同步机制,...

    共享内存是进程间通信(Inter Process Communication)的最快方式。linux共享内存有两种方式:

    第一种:mmap方式,适用场景:父子进程之间,创建的内存非常大时

    第二种:shmget方式,适用场景:同一台电脑上不同进程之间,创建的内存相对较小时

    两种方式的文档网上都有很多,随便一抓一把。

    通病:共享内存没有自带的同步机制,需要借助其他方式来进行同步。


    nginx使用了mmap方式,并且对windows和linux都做了共享内存简单封装,需要使用可以参照学习一下(linux:\src\os\unix\ngx_shmem.h和\src\os\unix\ngx_shmem.c windows:\src\os\win32\ngx_shmem.h和windows:\src\os\win32\ngx_shmem.c

    展开全文
  • 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()方法。

    如果有用,点个赞再走吧

    展开全文
  • 1.内存为什么要分段? 分成多少段? 段与段寄存器的区别? 8086CPU有20根地址线,最大可寻址内存空间为1MB。而8086的寄存器只有16位,指令指针(IP)变址寄存器(SI、DI)也是16位的。用16位的地址寻址1MB...

    1.内存为什么要分段? 分成多少种段? 段与段寄存器的区别?


    8086CPU有20根地址线,最大可寻址内存空间为1MB。而8086的寄存器只有16位,指令指针(IP)和变址寄存器(SI、DI)也是16位的。用16位的地址寻址1MB空间是不可能的。所以就要把内存分段,也就是把1MB空间分为若干个段,每段不超过64KB,在8086中设置4个16位的段寄存器,用于管理4种段:CS是代码段,DS是数据段,SS是堆栈段,ES是附加段。

    把内存分段后,每一个段就有一个段基址,段寄存器保存的就是这个段基址的高16位,这个16位的地址左移四位(后面加上4个0)就可构成20位的段基址。
    一个是早期实模式下,寄存器16位,地址线20位。为了用16位的寄存器寻址20位的地址,引入了段(segment)的概念,所有的段都在一个地址空间。

    第二个是保护模式下,段(segmentation)强调的是分割,用来把内存分成不同的地址空间,每个段一个空间,而后通过CPU的MMU转换成实际物理地址。由于程序运行在不同的段里,根本上保护了CPU保护模式下的各个不相关的代码,所谓进程或者作业。

    注意,x86 CPU的段是永远存在的,不论哪个模式,不能禁止,参看Intel开发手册。
    2.访问内存空间与寄存器有啥关系啊?为什么16不能寻到1M?还有内存是不是分成四种段啊?(CS/DS/SS/ES)?段寄存器就是其管理作用吗?
    寄存器是CPU内部的存储部件与内存空间没有关系,设置寄存器的原因是为了减少CPU与内存交换数据的次数,以提高计算机的工作速度。
    内存空间和地址线的关系为:存储容量=2^n(n是地址线的数量),所以16根地址线只能访问2^16=64K的存储空间。
    内存是分成四种段,这是考虑到程序执行时需要的四个部分。但是,内存不止四个段,只是同时最多只有四个段在工作,其他的在“睡眠”,需要时再“唤醒”。
    内存分段后,内存的地址(又称物理地址)就由两部分组成:段地址和段内偏移地址,段寄存器管理的是段地址。
    展开全文
  • 面试官:说说什么是 Java 内存模型(JMM)?

    万次阅读 多人点赞 2021-05-05 23:23:20
    1. 为什么要有内存模型? 1.1. 硬件内存架构 1.2. 缓存一致性问题 1.3. 处理器优化指令重排序 2. 并发编程的问题 3. Java 内存模型 3.1. Java 运行时内存区域与硬件内存的关系 3.2. Java 线程与主内存的关系 ...
  • 操作系统的内存管理单元(Memory Management Unit,MMU)只能完成一次虚拟地址到物理地址的映射,但获得的物理地址只是虚拟机物理地址而不是机器物理地址,所以需要VMM参与,以获得总线上可以使用的机器地址实现...
  • 在C语言中,指针变量的值就是一个内存地址,&运算符的作用也是取变量的内存地址,请看下面的代码: #include #include int a = 1, b = 255;int main(){ int *pa = &a; printf("pa = %#X, &b = %#X\n", pa, &b);...
  • 内存上的2rx8、1rx8、2rx4什么意思

    千次阅读 2021-01-14 05:16:20
    展开全部2rx8表示memory(内存)的内存颗粒的个数16个。1rx8表示memory的内存颗粒的个数8个。2rx4表示memory的内存颗粒的个数8个。内存计算方法如636f707962616964757a686964616f31333431363061下:CPU数据总线...
  • 一、外设都是通过读写设备上的寄存器来进行访问的,外设寄存器也成为“”I/O端口“”,而IO端口有两种编址方式:独立编址统一编址。 1)统一编址:寄存器参与内存统一编址。外设接口中的IO寄存器(即IO端口)与主存...
  • 什么是虚拟内存

    万次阅读 多人点赞 2019-11-09 15:33:02
    什么是虚拟内存呢?先查一下维基百科: 虚拟内存是计算机系统内存管理的一技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时...
  • Java内存划分分配

    千次阅读 2018-10-18 15:03:41
    最后学习一下Java堆内存代划分和内存分配。 Java内存区域划分 首先通过一张图来看一下Java虚拟机是如何划分内存空间的。 程序计数器:是一块较小内存,可以看作是当前线程所执行的字节码的行号指示...
  • 电脑内存条C14C16的区别是什么

    万次阅读 2019-09-12 09:47:59
    也就是响应时间不同,单位时钟周期。 2、性能不同 其他条件相同的情况下,数字越小性能越好。 3、价格不同 其他条件相同的情况下,数字越小价格越高。 4、颗粒不同 内存中使用的颗粒不同,数字越小颗粒越好...
  • jemalloc 内存分配器 是什么

    万次阅读 2021-08-04 13:39:40
    jemalloc 内存分配器 是什么? 内存池 所谓内存池,是指应用程序向操作系统(或 JVM)申请一块内存,自己管理这一块内存,对象的创建销毁都从这块内存中分配回收,这么一块内存就可以称作内存池, 对应地,管理...
  • 全面理解Java内存模型(JMM)及volatile关键字

    万次阅读 多人点赞 2017-06-12 11:25:05
    这里之所以简要说明这部分内容,注意是为了区别Java内存模型与Java内存区域的划分,毕竟这两种划分是属于不同层次的概念。 Java内存模型概述 Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的...
  • 栈的区别 (stack and heap)一般认为在c中分为这几个存储区 1栈 - 有编译器自动分配释放 2堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3全局区(静态区),全局变量静态变量的存储...
  • 就是说32根地址总线,每根总线上两种状态0或1,那么就可以有2^32个地址。 然后我看很多文章说因为2^32等于4G,所以最大就4G。但是每个地址不都是4个字节。那不就是2^34B,就是16G么?
  • 在计算机底层,只有01两种标识,所以就用2进制,由于2太小了,所以就用了2^10作为进制。 大胆推测,假如哪天计算机出现了3种标识,那么就要用3^n进制了。 进制换算 1T=1024GB1G=1024M1M=1024KlK=1024B 1B=8b ...
  • C语言中的指针和内存泄漏几情况

    万次阅读 多人点赞 2016-12-14 16:30:19
    C语言中的指针和内存泄漏几情况
  • SQL Server 2008 R2占用内存越来越大两种解决方法 [非程序sql语句的问题] 事件缘由:本人在开发sql server数据库项目的过程中发现了这么一个问题,SQL Server 2008 R2运行越久,占用内存会越来越大。因为...
  • 如何查看进程发生缺页中断的次数?  用ps -o majflt,minflt -C program命令查看。  majflt代表major fault,... 这个数值表示一个进程自启动以来所发生的缺页中断的次数。 发成缺页中断后,执行了那些操作?
  • 介绍深度学习中内存的开销来源,以及降低内存需求的几解决方案。
  • 区分-JVM内存分区Java内存模型(Java Memory Model)

    千次阅读 多人点赞 2019-05-08 17:55:13
    也是最近被问到了Java内存模型,学识浅薄,一直以为内存分区和内存模型是一个东西,现在做一下笔记整理一下以区分学习这个概念及其延伸的一些知识点。 开门见山 解决问题 JVM内存分区具体指的是JVM中运行时...
  • 内存泄漏和内存溢出

    万次阅读 多人点赞 2018-08-22 15:28:58
    内存溢出:(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。 内存泄漏:(Memory Leak)是指程序中己动态分配...
  • Jvm内存溢出的几情况

    千次阅读 2017-07-27 23:43:24
    关于Jvm内存溢出的几情况
  • 内存中的rank跟bank有什么区别

    万次阅读 2018-07-18 17:20:06
    倘若系统资料位元宽度是64bit,则每一个Rank就必须是64bit,当内存模组上有第二组64bit内存区块时,就称此模组DoubleRank,在实务上此模组的运作与条SingleRank模组相当。 转载 ...
  • 内存地址的概念理解

    万次阅读 多人点赞 2019-05-29 09:07:24
    都是表示的编号为1的内存地址,为什么一个是4位16进制表示,另外一个又是用8位16进制表示呢? 首先,必须要知道内存地址只是一个编号,代表一个内存空间。那么这个空间是多大呢?原来在计算机中存储器的容量是以字节...
  • JVM内存分配与管理详解

    万次阅读 2018-01-23 16:17:58
    内存管理领域,都是由程序员维护与管理,程序员用于最高的管理权限,但对于java程序员来说,在内存管理领域,程序员不必去关心内存的分配以及回收,在jvm自动内存管理机制的帮助下,不需要想C++一样每一个new...
  • JavaScript内存泄露的4方式及如何避免

    万次阅读 多人点赞 2018-05-16 15:33:17
    本质上,内存泄露可以定义:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相 同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。一些...
  • 【Linux】Linux的内核空间(低端内存、高端内存

    万次阅读 多人点赞 2018-07-20 16:50:01
    内核也是程序,也应该具有自己的虚存空间,但是作为一种为用户程序服务的程序,内核空间有它自己的特点。   内核空间与用户空间的关系 在一个32位系统中,一个程序的虚拟空间最大可以是4GB,那么最直接的做法...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,198,798
精华内容 879,519
关键字:

内存分为什么和什么两种