zoukankan      html  css  js  c++  java
  • JVM

    JVM

    0.JVM 常见的启动参数。

    -Xms: 设置堆的最小值。
         -Xmx: 设置堆的最大值。
         -Xmn: 设置新生代的大小。
         -Xss: 设置每个线程的栈大小。
         -XX:NewSize: 设置新生代的初始值。
         -XX:MaxNewSize: 设置新生代的最大值。
         -XX:PermSize: 设置永久代的初始值。
         -XX:MaxPermSize: 设置永久代的最大值。
         -XX:SurvivorRatio: 年轻代中 Eden 区与 Survivor 区的大小比值。
         -XX:PretenureSizeThreshold: 令大于这个设置值的对象直接在老年代分配。

    1.说一个java的垃圾回收机制

    1.哪些内存需要回收

    通过将内存的划分,程序计数器,虚拟机栈,本地方法栈,堆,方法区

    java堆上,无引用的内存

    2.什么时候回收

    判断对象是否死亡

    根搜索算法

    3.如何回收

    回收的算法

    整理-清除
    整理-标记
    复制(Eden,Survivor)

    分代收集算法(新生代/老年代)

    垃圾收集器的具体实现 serial/serial old、ParNew 、Parallel Scavenge、Parallel old、CMS、GI

    它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。垃圾回收器 通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经 死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃 圾回收器对某个对象或所有对象进行垃圾回收。程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。 
         垃圾回收机制可以用3个词来概括:where,when和how? 
         Where:运行时的内存分布情况。见下一题。
         When:对象何时需要被回收的?也就是何时回收无效对象,已死对象的? 
         这里涉及到两种做法:引用计数法和可达性分析算法。这里还涉及到java 中4种引用方式:强引用,软引用,弱引用和虚引用,其引用强度越来越来低,意味着引用越弱的对象越容易被垃圾回收的。
         how:对象如何被回收的?4 种垃圾回收算法
    

    2.JVM内存布局/内存模型

    程序计数器:记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)

    java虚拟机栈:创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息

    本地方法栈:本地方法栈为本地方法服务。 本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。

    :对象的实例和数组

    方法区:被加载的类信息,常量、静态变量、编译后代码等数据

    3.JVM 的4种引用和使用场景?

    这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
    强引用(StrongReference)
         强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝 不会回收它。当内存空间不足, Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问 题。 ps:强引用其实也就是我们平时 A a = new A()这个意思。
    软引用(SoftReference)
         如果一个对象只具有软引用, 则内存空间足够, 垃圾回收器就不会回收它; 如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它, 该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
         软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所 引用的对象被垃圾回收器回收,Java 虚拟机就会把这个软引用加入到与之关联 的引用队列中。
         示例:实现学生信息查询操作时有两套数据操作的方案。
         一、将得到的信息存放在内存中, 后续查询则直接读取内存信息(优点: 读取速度快; 缺点: 内存空间一直被占, 若资源访问量不高,则浪费内存空间)。
         二、每次查询均从数据库读取, 然后填充到 TO 返回。(优点: 内存空间 将被 GC 回收, 不会一直被占用;缺点: 在 GC 发生之前已有的 TO 依然存在, 但还是执行了一次数据库查询,浪费 IO)。可以通过软引用来解决。
    (3)弱引用(WeakReference)
         弱引用与软引用的区别在于: 只具有弱引用的对象拥有更短暂的生命周期。 在垃圾回收器线程扫描它所管辖的内存区域的过程中, 一旦发现了只具有弱引 用的对象, 不管当前内存空间足够与否, 都会回收它的内存。不过,由于垃圾 回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
         弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所 引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用 队列中。
    (4)虚引用(PhantomReference)
         “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会 决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用 一样,在任何时候都可能被垃圾回收器回收。
         虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱 引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象 的内存之前,把这个虚引用加入到与之关联的引用队列中。
    ReferenceQueue queue = new ReferenceQueue ();
    PhantomReference pr = new PhantomReference (object, queue);

    比较容易理解的是 Java 垃圾回收器会优先清理可达强度低的对象。
    那现在问题来了,若一个对象的引用类型有多个,那到底如何判断它的可 达性呢?其实规则如下:

    (“单弱多强”)
    单条引用链的可达性以最弱的一个引用类型来决定;
    多条引用链的可达性以最强的一个引用类型来决定;

    4.说一下引用计数法与可达性分析算法

    引用计数法

    为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被 回收。
    在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正是因为循环引用的存在,
    因此 Java 虚拟机不使用引用计数算法

    可达性分析算法
    也叫根搜索算法(GC roots tracing)

    GC Roots为起点,从这些结点向下搜索,对象不可达,就需要回收

    可以作为GC Roots的对象:

    • 1.虚拟机栈(栈帧的本地变量表)中引用的对象
    • 2.方法区中类静态属性引用的对象
    • 3.方法区中常量引用的对象
    • 4.本地方法栈中JNI(native)的引用对象

    5.堆里面的分区和各自的特点

    年轻代:
         年轻代又进一步可以划分为一个伊甸园(Eden)和两个存活区 (Survivor space),伊甸园是进行内存分配的地方,是一块连续的空闲内存区域, 在里面进行内存分配速度非常快,因为不需要进行可用内存块的查找。新对象 是总是在伊甸园中生成,只有经受住了一定的考验后才能后顺利地进入到存活 区中,这种考验是什么在后面会讲到。把存活区划分为两块,其实也是为了满 足垃圾回收的需要,因为在年轻代中经历了“回收大劫”未必就能够进入到年 老代中。系统总是把对象放在伊甸园和一个存活区(任意的一个),在垃圾回收时, 根据其存活时间被复制到另一个存活区或者年老代中,则之前的存活区和伊甸 园中剩下的都是需要被回收的对象,只对这两个区域进行清除即可,两个存活 区是交替使用,循环往复,在下一次垃圾回收时,之前被清除的存活区又用来 放置存活下来的对象了。一般来说,年轻代区域较小,而且大部分对象是需要 进行清除的,采用“复制算法”进行垃圾回收。

    年老代:
         在年轻代中经历了 N 次回收后仍然没有被清除的对象,就会被放 到年老代中,都是生命周期较长的对象。对于年老代和永久代,采用一种称为 “标记-清除-压缩(Mark-Sweep-Compact)”的算法。标记的过程是找出当前 还存活的对象,并进行标记;清除则是遍历整个年老区,找到已标记的对象并 进行清除;而压缩则是把存活的对象移动到整个内存区的一端,使得另一端是 一块连续的空间,方便进行内存分配和复制

    6.内存分配规则 MinorGC 和 FullGC

    Minor GC:
         当新对象生成,但在Eden申请空间失败时就会触发 Minor GC,对 Enden 区 进行 GC,清除掉非存活的对象,并且把存活的对象移动到 Survivor 区中的其 中一个区中。前面的提到考验就是 Minor GC,也就是说对象经过了 Minor GC 才能 够进入到存活区中。这种形式的 GC 只会在年轻代中进行,因为大部分对象都是从 Eden 区开始的,同时 Eden 区不会分配得太大,所以对 Eden 区的 GC 会非常地频繁。
    Full GC:
         对整个对进行整理,包括了年轻代、年老代和持久代。Full GC 要对整个 块进行回收,所以要比 Minor GC 慢得多,因此应该尽可能减少 Full GC 的次数。

    内存分配规则

    • 1.对象优先分配在 Eden 区,如果 Eden 区没有足够的空间时,虚拟机执行一次 Minor GC。
    • 2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是 避免在 Eden 区和两个 Survivor 区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
    • 3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过 了 1 次 Minor GC 那么对象会进入 Survivor 区,之后每经过一次 Minor GC 那么对象 的年龄加 1,直到达到阀值,对象进入老年区。
    • 4.动态判断对象的年龄。如果 Survivor 区中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
    • 5.空间分配担保。每次进行 Minor GC 时,JVM 会计算 Survivor 区移至老年区的对象的 平均大小,如果这个值大于老年区的剩余值大小则进行一次 Full GC,如果小于检查 HandlePromotionFailure 设置,如果 true 则只进行 Monitor GC,如果 false 则进 行 Full GC。

    7.对象创建方法

    四种不同的方法创建 java 对象。

    1.用 new 语句创建对象,这是最常用的创建对象的方式

    2.调用对象的 clone()方法

    MyObject anotherObject = new MyObject();
    MyObject object = anotherObject.clone();
    //使用 clone()方法克隆一个对象的步骤:
    //1.被克隆的类要实现 Cloneable 接口。
    //2.被克隆的类要重写 clone()方法。
    

    3.运用反射手段, 使用 Class.forName()

    4.运用反序列化手段,调用 java.io.ObjectInputStream 对象的 readObject()方法

    8.垃圾收集算法

    1.标记-清除

    在标记阶段,程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
    在清除阶段,会进行对象回收并取消标志位,另外,还会判断回收后的分块与前一个空闲分块是否连续,若连续,会 合并这两个分块。回收对象就是把对象作为分块,连接到被称为 “空闲链表” 的单向链表,之后进行分配时只需要遍 历这个空闲链表,就可以找到分块。
    在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size,会直接返 回这个分块;如果找到的块大于 size,会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分 块,并把大小为 (block - size) 的块返回给空闲链表。

    不足:

    • 标记和清除过程效率都不高;
    • 会产生大量不连续的内存碎片,导致无法给大对象分配内存。

    2.标记-整理

    让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

    不会产生内存碎片,但是需要移动大量对象,处理效率比较低

    3.复制

    将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然 后再把使用过的内存空间进行一次清理。
    主要不足是只使用了内存的一半。

    现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间 和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的 对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。
    HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的 空间存储放不下的对象

    4.分代收集算法

    优化复制算法:
    由于每次执行复制算法的时候,所有存活的对象都要被复 制,这样效率很低。由于程序中创建的大部分对象的生命周期都很短,只有一 部分对象有较长的生命周期,因此可以针对这个特点对复制算法进行优化,采 用分代垃圾回收算法。

    9.GC收集器

    serial/serial old、ParNew 、Parallel Scavenge、Parallel old、CMS、GI

    CMS(Concurrent Mark Sweep)
    Mark Sweep 指的是标记 - 清除算法。 分为以下四个流程:

    • 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
    • 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
    • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要 停顿。
    • 并发清除:不需要停顿。

    在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。 具有以下缺点:

    • 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
    • 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行 而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分 内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃 圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
    • 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得 不提前触发一次 Full GC。

    G1 Grabge first

    堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1可以直接对新生代和老年代一起回收

    G1 收集器的运作大致可划分为以下几个步骤:

    • 初始标记
    • 并发标记
    • 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将 这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
    • 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计 划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的, 而且停顿用户线程将大幅度提高收集效率。

    具备如下特点:

    • 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复 制”算法实现的,这意味着运行期间不会产生内存空间碎片。
    • 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫 秒。

    10.内存泄漏和内存溢出

    内存泄漏

    • 1.在堆中申请的空间没有被释放
    • 2.对象不在被使用,却还在内存中保留

    静态集合类,各种数据库连接,监听器,变量不合理的作用域

    内存泄漏的典型例子是一个没有重写 hashCode 和 equals 方法的 Key 类在 HashMap 中保存的情况,最后会生成很多重复的对象。所有的内存泄露最后都 会抛出OutOfMemoryError异 常 ( Exceptionjava.lang.OutOfMemoryError: Java heap space)

    内存泄漏解决方案:

    • 1、避免在循环中创建对象。
    • 2、尽早释放无用对象的引用。(最基本的建议)
    • 3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收。
    • 4、使用字符串处理,避免使用 String,应大量使用 StringBuffer,每一个 String对象都得独立占用内存一块区域

    内存溢出

    OutOfMemoryError异常(OOM异常)

    除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。

    • 1.虚拟机栈和本地方法栈溢出
           如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError 异常。
           如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError 异常。
    • 2.堆 溢出
           一般的异常信息:java.lang.OutOfMemoryError:Java heap spaces。
           出现这种异常,一般手段是先通过内存映像分析工具(如 Eclipse Memory Analyzer)对 dump 出来的堆转存快照进行分析,重点是确认内存中的对象是否 是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(MemoryOverflow)。
           如果是内存泄漏,可进一步通过工具查看泄漏对象到 GC Roots 的引用链。 于是就能找到泄漏对象是通过怎样的路径与 GC Roots 相关联并导致垃圾收集器 无法自动回收。
           如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx 与-Xms)的设置是否适当。
    • 3.方法区溢出
           异常信息:java.lang.OutOfMemoryError:PermGen space。
    • 4.运行时常量池溢出
           异常信息:java.lang.OutOfMemoryError:PermGen space。
           如果要向运行时常量池中添加内容,最简单的做法就是使用 String.intern()这个 Native 方法。该方法的作用是:如果池中已经包含一个 等于此 String 的字符串,则返回代表池中这个字符串的 String 对象;否则, 将此 String 对象包含的字符串添加到常量池中,并且返回此 String 对象的引 用 。 由于常量池分配在方法区内, 我们可以通过-XX:PermSize 和 -XX:MaxPermSize 限制方法区的大小,从而间接限制其中常量池的容量

    11.减少GC出现次数

    (1)对象不用时最好显式置为 Null
         一般而言,为 Null 的对象都会被作为垃圾处理,所以将不用的对象显式地设 为 Null,有利于 GC 收集器判定垃圾,从而提高了 GC 的效率。
    (2)尽量少用 System.gc()
         此函数建议 JVM 进行主 GC,虽然只是建议而非一定,但很多情况下它会触发 主 GC,从而增加主 GC 的频率,也即增加了间歇性停顿的次数。
    (3)尽量少用静态变量
         静态变量属于全局变量,不会被 GC 回收,它们会一直占用内存。
    (4)尽量使用 StringBuffer,而不用 String 来累加字符串。
         由于 String 是固定长的字符串对象,累加 String 对象时,并非在一个 String 对象中扩增,而是重新创建新的 String 对象,如 Str5=Str1+Str2+Str3+Str4,这条 语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新 的 String 对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多 的垃圾。避免这种情况可以改用 StringBuffer 来累加字符串,因 StringBuffer 是可变长的,它在原有基础上进行扩增,不会产生中间对象。
    (5)分散对象创建或删除的时间
         集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内 存,JVM 在面临这种情况时,只能进行主 GC,以回收内存或整合内存碎片,从 而增加主 GC 的频率。
         集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空 闲空间必然减少,从而大大增加了下一次创建新对象时强制主 GC 的机会。
    (6)尽量少用 finalize 函数。因为它会加大 GC 的工作量,因此尽量少用finalize 方式回收资源。
    (7)如果需要使用经常用到的图片,可以使用软引用类型,它可以尽可能将图片保存在内存中,供程序调用,而不引起 OutOfMemory。
    (8)能用基本类型如 int,long,就不用 Integer,Long 对象
         基本类型变量占用的内存资源比相应包装类对象占用的少得多,如果没有必要,最好使用基本变量。
    (9)增大-Xmx 的值。

  • 相关阅读:
    python中使用scikit-learn和pandas决策树进行iris鸢尾花数据分类建模和交叉验证
    CNN+ Auto-Encoder 实现无监督Sentence Embedding ( 基于Tensorflow)
    R语言数据可视化分析案例:探索BRFSS数据
    1.微信小程序里如何设置当地时间?
    63.1拓展之纯 CSS 创作一个摇摇晃晃的 loader
    63.(原65)纯 CSS 创作一个摇摇晃晃的 loader
    6.HTML+CSS制作一双眼睛
    62.纯 CSS 创作一只蒸锅(感觉不好看呀)
    61.纯 CSS 创作一只咖啡壶(这个不好看)
    60.纯 CSS 创作一块乐高积木
  • 原文地址:https://www.cnblogs.com/GeekDanny/p/11858047.html
Copyright © 2011-2022 走看看