精华内容
下载资源
问答
  • 基于类型划分的我国区域水资源利用效率比较,陈刚,刘凤朝,本文在研究我国水资源分布和配置情况基础上,结合各省经济水平、水资源禀赋以及水资源开发利用程度,对我国省级行政区进行分类
  • Java内存区域怎么划分的

    万次阅读 2020-09-18 15:12:57
    Java内存区域怎么划分的? 运行时数据区域包含以下五个区域:程序计数器,Java虚拟机栈,本地方法栈,堆,方法区(其中前三个区域各线程私有,相互独立,后面两个区域所有线程共享) 线程私用部分(Java虚拟机栈,...

    Java内存区域怎么划分的?

    运行时数据区域包含以下五个区域:程序计数器,Java虚拟机栈,本地方法栈,堆,方法区(其中前三个区域各线程私有,相互独立,后面两个区域所有线程共享) 

    线程私用的部分(Java虚拟机栈,本地方法栈,程序计数器)

    Java虚拟机栈

    执行一个Java方法时,虚拟机都会创建一个栈帧,来存储局部变量表,操作数栈等,方法调用完毕后会对栈帧从虚拟机栈中移除。

    局部变量表中存储了Java基本类型,对象引用(可以是对象的存储地址,也可以是代表对象的句柄等)和returnAddress类型(存储了一条字节码指令的地址)。

    本地方法栈

    本地方法栈与Java虚拟机栈类似,只不过是执行Native方法(C++方法等)。

    程序计数器

    计数器存储了当前线程正在执行的字节码指令的地址(如果是当前执行的是Native方法,那么计数器为空),字节码解释器就是通过改变计数器的值来选取下一条需要执行的字节码指令。程序计数器是线程私有的,便于各个线程切换后,可以恢复到正确的执行位置。

    线程共享的部分(堆,方法区)

    Java 堆

    堆存储了几乎所有对象实例和数组,是被所有线程进行共享的区域。在逻辑上是连续的,在物理上可以是不连续的内存空间(在存储一些类似于数组的这种大对象时,基于简单和性能考虑会使用连续的内存空间)。

    方法区

    存储了被虚拟机加载的类型信息,常量,静态变量等数据,在JDK8以后,存储在元空间中(以前是存储在堆中的永久代中,JDK8以后已经没有永久代了)。

    运行时常量池是方法区的一部分,会存储各种字面量和符号引用。具备动态性,运行时也可以添加新的常量入池(例如调用String的intern()方法时,如果常量池没有相应的字符串,会将它添加到常量池)。

    直接内存区(不属于虚拟机运行时数据区)

    直接内存区不属于虚拟机运行时数据区的一部分。它指的是使用Native方法直接分配堆外内存,然后通过Java堆中的DirectByteBuffer来对内存的引用进行操作(可以避免Java堆与Native堆之间的数据复制,提升性能)。

    Java中对象的创建过程是怎么样的?

    这是网上看到的一张流程图:

    java对象创建流程

    1.类加载检查

    首先代码中new关键字在编译后,会生成一条字节码new指令,当虚拟机遇到一条字节码new指令时,会根据类名去方法区运行时常量池找类的符号引用,检查符号引用代表的类是否已加载,解析和初始化过。如果没有就执行相应的类加载过程。

    2.分配内存

    虚拟机从Java堆中分配一块大小确定的内存(因为类加载时,创建一个此类的实例对象的所需的内存大小就确定了),并且初始化为零值。内存分配的方式有指针碰撞空闲列表两种,取决于虚拟机采用的垃圾回收期是否带有空间压缩整理的功能。

    指针碰撞

    如果垃圾收集器是Serial,ParNew等带有空间压缩整理的功能时,Java堆是规整的,此时通过移动内存分界点的指针,就可以分配空闲内存。

    空闲列表

    如果垃圾收集器是CMS这种基于清除算法的收集器时,Java堆中的空闲内存和已使用内存是相互交错的,虚拟机会维护一个列表,记录哪些可用,哪些不可用,分配时从表中找到一块足够大的空闲内存分配给实例对象,并且更新表。

    3.对象初始化(虚拟机层面)

    虚拟机会对对象进行必要的设置,将对象的一些信息存储在Obeject header 中。

    4.对象初始化(Java程序层面)

    在构造一个类的实例对象时,遵循的原则是先静后动,先父后子,先变量,后代码块,构造器。在Java程序层面会依次进行以下操作:

    • 初始化父类的静态变量(如果是首次使用此类)

    • 初始化子类的静态变量(如果是首次使用此类)

    • 执行父类的静态代码块(如果是首次使用此类)

    • 执行子类的静态代码块(如果是首次使用此类)

    • 初始化父类的实例变量

    • 初始化子类的实例变量

    • 执行父类的普通代码块

    • 执行子类的普通代码块

    • 执行父类的构造器

    • 执行子类的构造器

    PS:如何解决内存分配时的多线程并发竞争问题?

    内存分配不是一个线程安全的操作,在多个线程进行内存分配是,可能会存在数据不同步的问题。所以有两种方法解决:

    添加CAS锁

    对内存分配的操作进行同步处理,添加CAS锁,配上失败重试的方式来保证原子性。(默认使用这种方式)。

    预先给各线程分配TLAB

    预先在Java堆中给各个线程分配一块TLAB(本地线程缓冲区)内存,每个线程先在各自的缓冲区中分配内存,使用完了再通过第一种添加CAS锁的方式来分配内存。(是否启动取决于-XX:+/-UseTLAB参数)。

    Java对象的内存布局是怎么样的?

    对象在内存中存储布局主要分为对象头,实例数据和对齐填充三部分。

    Serial收集器中,新生代与老年代的内存分配是1:2,然后新生代分为Eden区,From区,To区,比例是8:1:1。

    新生代

    分为Eden,From Survivor,To Survivor,8:1:1

    Eden用来分配新对象,满了时会触发Minor GC。

    From Survivor是上次Minor GC后存活的对象。

    To Survivor是用于下次Minor GC时存放存活的对象。

    老年代

    用于存放存活时间比较长的对象,大的对象,当容量满时会触发Major GC(Full GC)

    内存分配策略:

    1. 对象优先在Eden分配

    当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。现在的商业虚拟机一般都采用复制算法来回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。 当进行垃圾回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后处理掉Eden和刚才的Survivor空间。(HotSpot虚拟机默认Eden和Survivor的大小比例是8:1)当Survivor空间不够用时,需要依赖老年代进行分配担保。

    1. 大对象直接进入老年代

    所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组,为了避免大对象在Eden和两个Survivor区之间进行来回复制,所以当对象超过-XX:+PrintTenuringDistribution参数设置的大小时,直接从老年代分配

    1. 长期存活的对象将进入老年代。

    当对象在新生代中经历过一定次数(XX:MaxTenuringThreshold参数设置的次数,默认为15)的Minor GC后,就会被晋升到老年代中。

    1. 动态对象年龄判定。

    为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

    MinorGC和FullGC是什么?

    Minor GC:对新生代进行回收,不会影响到年老代。因为新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。

    Full GC:也叫 Major GC,对整个堆进行回收,包括新生代和老年代。由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数,导致Full GC的原因包括:老年代被写满和System.gc()被显式调用等。

    触发Minor GC的条件有哪些?

    1.为新对象分配内存时,新生代的Eden区空间不足。 新生代回收日志:

    2020-05-12T16:15:10.736+0800: 7.803: [GC (Allocation Failure) 2020-05-12T16:15:10.736+0800: 7.803: [ParNew: 838912K->22016K(943744K), 0.0982676 secs] 838912K->22016K(1992320K), 0.0983280 secs] [Times: user=0.19 sys=0.01, real=0.10 secs]
    

    触发Full GC的条件有哪些?

    主要分为三种:

    1.system.gc()

    代码中调用system.gc()方法,建议JVM进行垃圾回收。

    2.方法区空间不足

    方法区中存放的是一些类的信息,当系统中要加载的类、反射的类和调用的方法较多时,方法区可能会被占满,触发 Full GC

    3.老年代空间不足

    而老年代空间不足又有很多种情况: 3.1 Promotion Failed 老年代存放不下晋升对象 在进行 MinorGC 时, Survivor Space 放不下存活的对象,此时会让这些对象晋升,只能将它们放入老年代,而此时老年代也放不下时造成的。 还有一些情况也会导致新生代对象晋升,例如存活对象经历的垃圾回收次数超过一定次数(XX:MaxTenuringThreshold参数设置的次数,默认为15),那么会导致晋升, 或者在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。 3.2 Concurrent Mode Failure 在执行 CMS GC 的过程中,同时有对象要放入老年代,而此时老年代空间不足造成的。 3.3 历次晋升的对象平均大小>老年代的剩余空间 这是一个较为复杂的触发情况, HotSpot为了避免由于新生代对象晋升到老年代导致老年代空间不足的现象, 在进行 Minor GC时,做了一个判断,如果之前统计所得到的 MinorGC 晋升到老年代的平均大小大于老年代的剩余空间,那么就直接触发 Full GC。 3.4 老年代空间不足以为大对象分配内存 因为超过阀值(-XX:+PrintTenuringDistribution参数设置的大小时)的大对象,会直接分配到老年代,如果老年代空间不足,会触发Full GC。

    垃圾收集器有哪些?

    一般老年代使用的就是标记-整理,或者标记-清除+标记-整理结合(例如CMS)

    新生代就是标记-复制算法

    垃圾收集器 特点 算法 适用内存区域
    Serial 单个GC线程进行垃圾回收,简单高效 标记-复制 新生代
    Serial Old 单个GC线程进行垃圾回收 标记-整理 老年代
    ParNew 是Serial的改进版,就是可以多个GC线程一起进行垃圾回收 标记-复制 新生代
    Parallel Scanvenge收集器(吞吐量优先收集器) 高吞吐量,吞吐量=执行用户线程的时间/CPU执行总时间 标记-复制 新生代
    Parallel Old收集器 支持多线程收集 标记-整理 老年代
    CMS收集器(并发低停顿收集器) 低停顿 标记-清除+标记-整理 老年代
    G1收集器 低停顿,高吞吐量 标记-复制算法 老年代,新生代

    Serial收集器(标记-复制算法)

    就是最简单的垃圾收集器,也是目前 JVM 在 Client 模式默认的垃圾收集器,在进行垃圾收集时会停止用户线程,然后使用一个收集线程进行垃圾收集。主要用于新生代,使用标记-复制算法。

    优点是简单高效(与其他收集器的单线程比),内存占用小,因为垃圾回收时就暂停所有用户线程,然后使用一个单线程进行垃圾回收,不用进行线程切换。

    缺点是收集时必须停止其他用户线程。

    Serial Old收集器(标记-整理算法)

    跟Serial收集器一样,不过是应用于老年代,使用标记-整理算法。

    ParNew收集器(标记-复制算法)

    ParNew收集器是Serial收集器的多线程并行版本,在进行垃圾收集时可以使用多个线程进行垃圾收集。

    与Serial收集器主要区别就是支持多线程收集,ParNew收集器应用广泛(JDK9以前,服务端模式垃圾收集组合官方推荐的是ParNew+CMS),因为只有Serial和ParNew才能配合CMS收集器(应用于老年代的并发收集器)一起工作。

    image-20200228185412716.pngimage-20200228185412716

    Parallel Scanvenge收集器(吞吐量优先收集器)

    也支持多线程收集,它的目标是达到一个可控制的吞吐量,就是运行用户代码的时间/CPU消耗的总时间的比值。高吞吐量可以最高效率地利用处理器资源,尽快完成程序运算任务,适合不需要太多的交互分析任务。不支持并发收集,进行垃圾回收时会暂停用户线程,使用多个垃圾回收线程进行垃圾回收。

    Parallel Old收集器

    是Parallel Scanvenge老年代版本,支持多线程收集,使用标记整理法实现的,

      

    G1对象分配策略

    说起对象的分配,我们不得不谈谈对象的分配策略。它分为4个阶段:

    1. 栈上分配
    2. TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
    3. 共享Eden区中分配
    4. Humongous区分配(超过Region大小50%的对象)

    对象在分配之前会做逃逸分析,如果该对象只会被本线程使用,那么就将该对象在栈上分配。这样对象可以在函数调用后销毁,减轻堆的压力,避免不必要的gc。 如果对象在栈是上分配不成功,就会使用TLAB来分配。TLAB为线程本地分配缓冲区,它的目的为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配,我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间不再需要进行任何的同步。

    对TLAB空间中无法分配的对象,JVM会尝试在共享Eden空间中进行分配。如果是大对象,则直接在Humongous区分配。

    最后,G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。下面我们将分别介绍一下这2种模式。

    G1 Young GC

    Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

    Region如何解决跨代指针?

    因为老年代old区也会存在对新生代Eden区的引用,如果只是为了收集Eden区而对整个老年代进行扫描,那样开销太大了,所以G1其实会将每个Region分为很多个区,每个区有一个下标,当这个区有对象被其他Region引用时,那么CardTable对应下标下值为0,然后使用一个Rset来存储其他Region对当前Region的引用,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。对跨代引用的扫描,只需要扫描RSet就行了。

    在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。

    但在G1中,并没有使用point-out(就是记录当前Region对其他Region中对象的引用),这是由于一个Region太小,Region数量太多,如果是用point-out的话,如果需要计算一个Region的可回收的对象数量,需要把所有Region都是扫描一遍会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些Region引用了当前Region中的对象。这样只需要将当前Region中这些对象当做初始标记时的根对象来扫描就可以扫描出因为有跨代引用需要存活的对象,避免了无效的扫描。

    由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代的Region对新生代的这个Region之间的引用即可。

    需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。CardTable通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未引用。当一个地址空间有引用时,这个地址空间对应的数组索引的值被标记为"0",即标记为脏引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table集合(每个线程对应一个Hash Table,主要是为了减少多线程并发更新RSet的竞争),每个哈希表的Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

    如果Rset是记录每个外来Region对当前Region中对象的引用,这样数量就太多了,所以Card Table只是有很多Byte字节,每个字节记录了Region对应的一个内存区域(卡页)是否是dirty的,为1代表dirty,也就是有其他Region对这个卡页中的对象进行引用。

     

    • 阶段1:根扫描 表态和本地对象被扫描
    • 阶段2:更新RS 处理dirty card队列更新RS
    • 阶段3:处理RS 检测从新生代指向老年代的对象
    • 阶段4:对象拷贝 拷贝存活的对象到survivor/old区域
    • 阶段5:处理引用队列 软引用、弱引用、虚引用处理

    G1 MixGC

    MixGC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。Young GC回收是把新生代活着的对象都拷贝到Survivor的特定区域(Survivor to),剩下的Eden和Survivor from就可以全部回收清理了。那么,mixed GC就是把一部分老年区的region加到Eden和Survivor from的后面,合起来称为collection set, 就是将被回收的集合,下次mixed GC evacuation把他们所有都一并清理。选old region的顺序是垃圾多的(存活对象少)优先。

    它的GC步骤分2步:

    1. 全局并发标记(global concurrent marking)
    2. 拷贝存活对象(evacuation)

    在进行Mix GC之前,会先进行global concurrent marking(全局并发标记)。 global concurrent marking的执行过程是怎样的呢?

    在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为五个步骤:

    • 初始标记(initial mark,STW) 在此阶段,G1 GC 对根进行标记。该阶段与常规的(STW) 新生代垃圾回收密切相关。
    • 根区域扫描(root region scan) G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 新生代垃圾回收。
    • 并发标记(Concurrent Marking) G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 新生代垃圾回收中断
    • 最终标记(Remark,STW) 该阶段是 STW 回收,帮助完成标记周期。G1 GC清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
    • 清除垃圾(Cleanup,STW) 在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

    收集步骤:

    image-20200302181639409.pngimage-20200302181639409

    初始标记 只标记GC Roots直接引用的对象

    并发标记 从GC Roots开始对堆中对象进行可达性分析,扫描整个对象图,找出要回收的对象。(可以与用户线程并发执行)

    最终标记 对并发标记阶段,由于用户线程执行造成的改动进行修正,使用原始快照方法。

    筛选回收 对Region进行排序,根据回收价值,选择任意多个Region构成回收集,将存活对象复制到空的Region中去,因为涉及到存活对象的移动,所以是暂停用户线程的。

    垃圾收集器相关的参数

    -XX:+UseSerialGC, 虚拟机运行在Client 模式下的默认值,打开此开关后,使用Serial + Serial Old 的收集器组合进行内存回收

    -XX:+UseConcMarkSweepGC,打开此开关后,使用ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为CMS 收集器出现Concurrent Mode Failure失败后的后备收集器使用。(我们的线上服务用的都是这个)

    -XX:+UseParallelGC,虚拟机运行在Server 模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收。

    -XX:+UseParallelOldGC,打开此开关后,使用Parallel Scavenge + Parallel Old 的收集器组合进行内存回收。

    -XX:+UseG1GC,打开此开关后,采用 Garbage First (G1) 收集器

    -XX:+UseParNewGC,在JDK1.8被废弃,在JDK1.7还可以使用。打开此开关后,使用ParNew + Serial Old 的收集器组合进行内存回收

    目前通常使用的是什么垃圾收集器?

    怎么查询当前JVM使用的垃圾收集器?

    使用这个命令可以查询当前使用的垃圾收集器 java -XX:+PrintCommandLineFlags -version,

    另外这个命令可以查询到更加详细的信息

    java -XX:+PrintFlagsFinal -version | grep GC

    我们在IDEA中启动的一个Springboot的项目,默认使用的垃圾收集器参数是 -XX:+UseParallelGC

    -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
    java version "1.8.0_73"
    Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
    Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)
    

    Parallel Scavenge+Serial Old

    JDK8默认情况下服务端模式下JVM垃圾回收参数是-XX:+UseParallelGC参数,也就是会使用Parallel Scavenge+Serial Old的收集器组合,进行内存回收。

    ParNew+CMS

    但是一般如果我们的后端应用不是那种需要进行大量计算的应用,基于低延迟的考虑,可以考虑使用-XX:+UseConcMarkSweepGC进行垃圾收集,这种配置下会使用ParNew来收集新生代内存,CMS垃圾回收器收集老年代内存。

    G1

    在JDK9时,默认的垃圾收集器是G1收集器,也可以使用-XX:+UseG1GC参数来启动G1垃圾收集器。

     

    容器的内存和 jvm 的内存有什么关系?参数怎么配置?

    在JDK8以后,JVM增加了容器感知功能,就是如果不显示指定-Xmx2048m 最大堆内存大小, -Xms2048m最小堆内存大小,会取容器所在的物理机的内存的25%作为最大堆内存大小, 也可以通过这几个参数来设置堆内存占容器内存的比例 -XX:MinRAMPercentage -XX:MaxRAMPercentage -XX:InitialRAMPercentage

    如何大体估算java进程使用的内存呢?

    Max memory = [-Xmx] + [-XX:MaxPermSize] + number_of_threads * [-Xss]

    -Xss128k:设置每个线程的堆栈大小.JDK5.0以后每个线程的栈大小为1M。

    -Xms 堆内存的初始大小,默认为物理内存的1/64 -Xmx 堆内存的最大大小,默认为物理内存的1/4 -Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn

    -Xss 设置每个线程可使用的内存大小,即栈的大小。在相同物理内存下,减小这个值能生成更多的线程,当然操作系统对一个进程内的线程数还是有限制的,不能无限生成。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

    怎么获取 dump 文件?怎么分析?

    1. 启动时配置,出现OOM问题时自动生成 JVM启动时增加两个参数:
    //出现 OOME 时生成堆 dump: 
    -XX:+HeapDumpOnOutOfMemoryError
    //生成堆文件地址:
    -XX:HeapDumpPath=/home/liuke/jvmlogs/
    

    2.执行jmap命令立即生成 发现程序异常前通过执行指令,直接生成当前JVM的dmp文件,6214是指JVM的进程号

    jmap -dump:format=b,file=/home/admin/logs/heap.hprof 6214
    

    获得heap.hprof以后,执行jvisualvm命令打开使用Java自带的工具Java VisualVM来打开heap.hprof文件,就可以分析你的Java线程里面对象占用堆内存的情况了

    由于第一种方式是一种事后方式,需要等待当前JVM出现问题后才能生成dmp文件,实时性不高,第二种方式在执行时,JVM是暂停服务的,所以对线上的运行会产生影响。所以建议第一种方式。

    gc日志怎么看?

    这是一条Minor GC的回收日志

    2020-05-07T16:28:02.845+0800: 78210.469: [GC (Allocation Failure) 2020-05-07T16:28:02.845+0800: 78210.469: [ParNew: 68553K->466K(76672K), 0.0221963 secs] 131148K->63062K(2088640K), 0.0223082 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
    

    GC (Allocation Failure)

    代表Eden区分配内存失败触发Minor GC。 2020-05-07T16:28:02.845+0800: 78210.469

    是发生的时间。 68553K->466K(76672K)

    代表垃圾回收前新生代使用内存是68MB,剩余0.4MB,总内存是76MB。 0.0221963 secs

    是垃圾回收耗时。 131148K->63062K(2088640K)

    代表堆区回收前使用131MB,63MB,总内存是2088MB。

    [Times: user=0.02 sys=0.00, real=0.02 secs]

    用户态耗时0.02s,内核态耗时0s,总耗时0.02s

    cpu 使用率特别高,怎么排查?通用方法?定位代码?cpu高的原因?

    CPU飙高,频繁GC,怎么排查?

    jstack命令:教你如何排查多线程问题

     jstat -gcutil 29530 1000 10
     垃圾回收信息统计,29530是pid,1000是每1秒打印一次最新信息,10是最多打印10次
    

    怎么排查CPU占用率过高的问题?

    1.首先使用top命令查看CPU占用率高的进程的pid。

    top - 15:10:32 up 523 days,  3:47,  1 user,  load average: 0.00, 0.01, 0.05
    Tasks:  95 total,   1 running,  94 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  1.7 us,  0.5 sy,  0.0 ni, 95.7 id,  2.2 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem : 16267904 total,  6940648 free,  2025316 used,  7301940 buff/cache
    KiB Swap: 16777212 total, 16776604 free,      608 used. 13312484 avail Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    14103 hadoop    20   0 2832812 203724  18392 S   3.7  1.3 977:08.04 java
    14010 hadoop    20   0 2897344 285392  18660 S   0.3  1.8 513:30.49 java
    14284 hadoop    20   0 3052556 340436  18636 S   0.3  2.1   1584:47 java
    14393 hadoop    20   0 2912460 504112  18632 S   0.3  3.1 506:43.68 java
        1 root      20   0  190676   3404   2084 S   0.0  0.0   4:31.47 systemd
        2 root      20   0       0      0      0 S   0.0  0.0   0:04.77 kthreadd
        3 root      20   0       0      0      0 S   0.0  0.0   0:10.16 ksoftirqd/0
    

    2.使用top -Hp 进程id获得该进程下各个线程的CPU占用情况,找到占用率最高的线程的pid2, 使用printf "%x\n" pid2命令将pid2转换为16进制的数number。

    top - 15:11:01 up 523 days,  3:48,  1 user,  load average: 0.00, 0.01, 0.05
    Threads:  69 total,   0 running,  69 sleeping,   0 stopped,   0 zombie
    %Cpu(s): 12.8 us,  0.1 sy,  0.0 ni, 87.0 id,  0.1 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem : 16267904 total,  6941352 free,  2024612 used,  7301940 buff/cache
    KiB Swap: 16777212 total, 16776604 free,      608 used. 13313188 avail Mem
    
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    14393 hadoop    20   0 2912460 504112  18632 S  0.0  3.1   0:00.01 java
    14411 hadoop    20   0 2912460 504112  18632 S  0.0  3.1   0:01.95 java
    14412 hadoop    20   0 2912460 504112  18632 S  0.0  3.1   0:16.18 java
    14413 hadoop    20   0 2912460 504112  18632 S  0.0  3.1   0:12.79 java
    14414 hadoop    20   0 2912460 504112  18632 S  0.0  3.1   8:09.10 java
    

    3.使用jstack pid获得进程下各线程的堆栈信息,nid=0xnumber的线程即为占用率高的线程,查看它是在执行什么操作。(jstack 5521 | grep -20 0x1596可以获得堆栈信息中,会打印匹配到0x1596的上下20行的信息。)

    例如这个线程是在执行垃圾回收:

    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f338c01f000 nid=0x1593 runnable
    

    JVM相关的异常

    1.stackoverflow

    这种就是栈的空间不足,就会抛出这个异常,一般是递归执行一个方法时,执行方法深度太深时出现。Java执行一个方法时,会创建一个栈帧来存放局部变量表,操作数栈,如果分配栈帧时,栈空间不足,那么就会抛出这个异常。

    (栈空间可以设置-Xss参数实现,默认为1M,如果参数)

    双亲委派机制是什么?

     

    就是类加载器一共有三种:

    启动类加载器:主要是在加载JAVA_HOME/lib目录下的特定名称jar包,例如rt.jar包,像java.lang就在这个jar包中。

    扩展加载器:主要是加载JAVA_HOME/lib/ext目录下的具备通用性的类库。

    应用程序加载器:加载用户类路径下所有的类库,也就是程序中默认的类加载器。

    工作流程:

    除启动类加载器以外,所有类加载器都有自己的父类加载器,类加载器收到一个类加载请求时,首先会判断类是否已经加载过了,没有的话会调用父类加载器的的loadClass方法,将请求委派为父加载器,当父类加载器无法完成类加载请求时,子加载器才尝试去加载这个类。 目的是为了保证每个类只加载一次,并且是由特定的类加载器进行加载(都是首先让启动类来进行加载)。

    public abstract class ClassLoader {
        ...
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
        protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) {
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    ...
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                    }
    
                    if (c == null) {
                        ...
                        c = findClass(name);
                        // do some stats
                        ...
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }
        ...
    }

    怎么自定义一个类加载器?

    加载一个类时,一般是调用类加载器的loadClass()方法来加载一个类,loadClass()方法的工作流程如下:

    1.先调用findLoadedClass(className)来获取这个类,判断类是否已加载。

    2.如果未加载,如果父类加载器不为空,调用父类加载器的loadClass()来加载这个类,父类加载器为空,就调用父类加载器加载这个类。

    3.父类加载器加载失败,那么调用该类加载器findClass(className)方法来加载这个类。

    所以我们我们一般自定义类加载器都是继承ClassLoader,来重新findClass()方法,来实现类加载。

    public class DelegationClassLoader extends ClassLoader {
      private String classpath;
    
      public DelegationClassLoader(String classpath, ClassLoader parent) {
        super(parent);
        this.classpath = classpath;
      }
    
      @Override
      protected Class<?> findClass(String name) throws ClassNotFoundException {
        InputStream is = null;
        try {
          String classFilePath = this.classpath + name.replace(".", "/") + ".class";
          is = new FileInputStream(classFilePath);
          byte[] buf = new byte[is.available()];
          is.read(buf);
          return defineClass(name, buf, 0, buf.length);
        } catch (IOException e) {
          throw new ClassNotFoundException(name);
        } finally {
          if (is != null) {
            try {
              is.close();
            } catch (IOException e) {
              throw new IOError(e);
            }
          }
        }
      }
    
      public static void main(String[] args)
          throws ClassNotFoundException, IllegalAccessException, InstantiationException,
          MalformedURLException {
        sun.applet.Main main1 = new sun.applet.Main();
    
        DelegationClassLoader cl = new DelegationClassLoader("java-study/target/classes/",
            getSystemClassLoader());
        String name = "sun.applet.Main";
        Class<?> clz = cl.loadClass(name);
        Object main2 = clz.newInstance();
    
        System.out.println("main1 class: " + main1.getClass());
        System.out.println("main2 class: " + main2.getClass());
        System.out.println("main1 classloader: " + main1.getClass().getClassLoader());
        System.out.println("main2 classloader: " + main2.getClass().getClassLoader());
        ClassLoader itrCl = cl;
        while (itrCl != null) {
          System.out.println(itrCl);
          itrCl = itrCl.getParent();
        }
      }
    }

    类加载的过程是什么样的?

    类加载器

    类加载器是 Java 运行时环境(Java Runtime Environment)的一部分,负责动态加载 Java 类到 Java 虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。 由于有了类加载器,Java 运行时系统不需要知道文件与文件系统。每个 Java 类必须由某个类加载器装入到内存。

     

    类装载器除了要定位和导入二进制 class 文件外,还必须负责验证被导入类的正确性,为变量分配初始化内存,以及帮助解析符号引用。这些动作必须严格按一下顺序完成:

    1. 装载:查找并装载类型的二进制数据。
    2. 链接:执行验证、准备以及解析(可选) - -验证:确保被导入类型的正确性 -准备:为类变量分配内存,并将其初始化为默认值。
    • 解析:把类型中的符号引用转换为直接引用。
    1. 初始化:把类变量初始化为正确的初始值。

    装载

    类加载器分类

    在Java虚拟机中存在多个类装载器,Java应用程序可以使用两种类装载器:

    • Bootstrap ClassLoader:此装载器是 Java 虚拟机实现的一部分。由原生代码(如C语言)编写,不继承自 java.lang.ClassLoader 。负责加载核心 Java 库,启动类装载器通常使用某种默认的方式从本地磁盘中加载类,包括 Java API。
    • Extention Classloader:用来在<JAVA_HOME>/jre/lib/ext ,或 java.ext.dirs 中指明的目录中加载 Java 的扩展库。 Java 虚拟机的实现会提供一个扩展库目录。
    • Application Classloader:根据 Java应用程序的类路径( java.class.path 或 CLASSPATH 环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。
    • 自定义类加载器:可以通过继承 java.lang.ClassLoader 类的方式实现自己的类加载器,以满足一些特殊的需求而不需要完全了解 Java 虚拟机的类加载的细节。

    全盘负责双亲委托机制

    在一个 JVM 系统中,至少有 3 种类加载器,那么这些类加载器如何配合工作?在 JVM 种类加载器通过 全盘负责双亲委托机制 来协调类加载器。

    • 全盘负责:指当一个 ClassLoader 装载一个类的时,除非显式地使用另一个 ClassLoader ,该类所依赖及引用的类也由这个 ClassLoader 载入。
    • 双亲委托机制:指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。

    全盘负责双亲委托机制只是 Java 推荐的机制,并不是强制的机制。实现自己的类加载器时,如果想保持双亲委派模型,就应该重写 findClass(name) 方法;如果想破坏双亲委派模型,可以重写 loadClass(name) 方法。

    装载入口

    所有Java虚拟机实现必须在每个类或接口首次主动使用时初始化。以下六种情况符合主动使用的要求:

    • 当创建某个类的新实例时(new、反射、克隆、序列化)
    • 调用某个类的静态方法
    • 使用某个类或接口的静态字段,或对该字段赋值(用final修饰的静态字段除外,它被初始化为一个编译时常量表达式)
    • 当调用Java API的某些反射方法时。
    • 初始化某个类的子类时。
    • 当虚拟机启动时被标明为启动类的类。

    除以上六种情况,所有其他使用Java类型的方式都是被动的,它们不会导致Java类型的初始化。 当类是被动引用时,不会触发初始化:

    1.通过子类去调用父类的静态变量,不会触发子类的初始化,只会触发包含这个静态变量的类初始化,例如执行这样的代码SubClass.fatherStaticValue只会触发FatherClass的初始化,不会触发SubClass的初始化,因为fatherStaticValue是FatherClass的变量

    2.通过数组定义类引用类,SuperClass[] array = new SuperClass[10];

    不会触发SuperClass类的初始化,但是执行字节码指令newarray会触发另外一个类[Lorg.fenixsoft.classloading.SuperClass的初始化,这个类继承于Object类,是一个包装类,里面包含了访问数组的所有方法,

    3.只引用类的常量不会触发初始化,因为常量在编译阶段进入常量池

    class SuperClass {
    		public static final String str = "hello";
    }
    
    //引用常量编译时会直接存入常量池
    System.out.println(SuperClass.str);
    

    对于接口来说,只有在某个接口声明的非常量字段被使用时,该接口才会初始化,而不会因为事先这个接口的子接口或类要初始化而被初始化。

    父类需要在子类初始化之前被初始化。当实现了接口的类被初始化的时候,不需要初始化父接口。然而,当实现了父接口的子类(或者是扩展了父接口的子接口)被装载时,父接口也要被装载。(只是被装载,没有初始化)

    验证

    确认装载后的类型符合Java语言的语义,并且不会危及虚拟机的完整性。

    • 装载时验证:检查二进制数据以确保数据全部是预期格式、确保除 Object 之外的每个类都有父类、确保该类的所有父类都已经被装载。
    • 正式验证阶段:检查 final 类不能有子类、确保 final 方法不被覆盖、确保在类型和超类型之间没有不兼容的方法声明(比如拥有两个名字相同的方法,参数在数量、顺序、类型上都相同,但返回类型不同)。
    • 符号引用的验证:当虚拟机搜寻一个被符号引用的元素(类型、字段或方法)时,必须首先确认该元素存在。如果虚拟机发现元素存在,则必须进一步检查引用类型有访问该元素的权限。

    准备

    在准备阶段,Java虚拟机为类变量分配内存,设置默认初始值。但在到到初始化阶段之前,类变量都没有被初始化为真正的初始值。

    类型 默认值
    int 0
    long 0L
    short (short)0
    char ’\u0000’
    byte (byte)0
    blooean false
    float 0.0f
    double 0.0d
    reference null

    解析的过程就是在类型的常量池总寻找类、接口、字段和方法的符号引用,把这些符号引用替换为直接引用的过程

    • 类或接口的解析:判断所要转化成的直接引用是数组类型,还是普通的对象类型的引用,从而进行不同的解析。
    • 字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束,

    初始化

    所有的类变量(即静态量)初始化语句和类型的静态初始化器都被Java编译器收集在一起,放到一个特殊的方法中,这个步骤就是初始化类静态变量和执行静态代码块。 对于类来说,这个方法被称作类初始化方法;对于接口来说,它被称为接口初始化方法。在类和接口的 class 文件中,这个方法被称为<clinit>

    1. 如果存在直接父类,且直接父类没有被初始化,先初始化直接父类。
    2. 如果类存在一个类初始化方法,执行此方法。

    这个步骤是递归执行的,即第一个初始化的类一定是Object

    Java虚拟机必须确保初始化过程被正确地同步。 如果多个线程需要初始化一个类,仅仅允许一个线程来进行初始化,其他线程需等待。

    这个特性可以用来写单例模式。

    Clinit 方法

    • 对于静态变量和静态初始化语句来说:执行的顺序和它们在类或接口中出现的顺序有关。
    • 并非所有的类都需要在它们的class文件中拥有()方法, 如果类没有声明任何类变量,也没有静态初始化语句,那么它就不会有<clinit>()方法。如果类声明了类变量,但没有明确的使用类变量初始化语句或者静态代码块来初始化它们,也不会有<clinit>()方法。如果类仅包含静态final常量的类变量初始化语句,而且这些类变量初始化语句采用编译时常量表达式,类也不会有<clinit>()方法。只有那些需要执行Java代码来赋值的类才会有()
    • final常量:Java虚拟机在使用它们的任何类的常量池或字节码中直接存放的是它们表示的常量值。

    JVM调优有哪些工具?

    jstat

    jstat可以打印出当前JVM运行的各种状态信息,例如新生代内存使用情况,老年代内存使用情况,Minor GC发生总次数,总耗时,Full GC发生总次数,总耗时。

    //5828是java进程id,1000是打印间隔,每1000毫秒打印一次,100是总共打印100次
    jstat -gc 5828 1000 100
    

    打印结果如下:

    各个参数的含义如下:

    S0C 新生代中第一个survivor(幸存区)的总容量 (字节)

    S1C 新生代中第二个survivor(幸存区)的总容量 (字节)

    S0U 新生代中第一个survivor(幸存区)目前已使用空间 (字节)

    S1U 新生代中第二个survivor(幸存区)目前已使用空间 (字节)

    EC 新生代中Eden(伊甸园)的总容量 (字节)

    EU 新生代中Eden(伊甸园)目前已使用空间 (字节)

    OC 老年代的总容量 (字节)

    OU 老年代代目前已使用空间 (字节)

    YGC 目前新生代垃圾回收总次数

    YGCT 目前新生代垃圾回收总消耗时间

    FGC 目前full gc次数总次数

    FGCT 目前full gc次数总耗时,单位是秒

    GCT 垃圾回收总耗时

    一般还可以使用jstat -gcutil <pid>:统计gc信息,这样打印出来的结果是百分比,而不是实际使用的空间,例如jstat -gcutil 1 1000 100

    例如,S0代表 新生代中第一个survivor区的空间使用了73.19%,E代表新生代Eden区使用了51%,O代表老年代食堂了98%

     

    参数 描述
    S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
    s1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
    E 年轻代中Eden已使用的占当前容量百分比
    O old代已使用的占当前容量百分比
    M 元空间(MetaspaceSize)已使用的占当前容量百分比
    CCS 压缩使用比例
    YGC 年轻代垃圾回收次数
    FGC 老年代垃圾回收次数
    FGCT 老年代垃圾回收消耗时间
    GCT 垃圾回收消耗总时间

     

    jstack

    jstack可以生成当前JVM的线程快照,也就是当前每个线程当前的状态及正在执行的方法,锁相关的信息。jstack -l 进程id ,-l代表除了堆栈信息外,还会打印锁的附加信息。jstack还会检测出死锁信息。一般可以用于定位线程长时间停顿,线程间死锁等问题。

    例如在下面的例子中,第一个线程获取到lock1,再去获取lock2,第二个线程先获取到lock2,然后再去获取lock1。每个线程都只获得了一个锁,同时在获取另外一个锁,就会进入死锁状态。

    public static void main(String[] args) {
            final Integer lock1 = new Integer(1);
            final String  lock2 = new String();
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock1) {
                        System.out.println("线程1获得了lock1");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("线程1休眠结束");
                        System.out.println("线程1开始尝试获取lock2");
                        synchronized (lock2) {
                            System.out.println("线程1获得了lock2");
                        }
                    }
                }
            });
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock2) {
                        System.out.println("线程2获得了lock2");
                        System.out.println("线程2开始尝试获取lock1");
                        synchronized (lock1) {
                            System.out.println("线程2获得了lock2");
                        }
                    }
                }
            });
        }

    使用jstack -l 进程id就可以打印出当前的线程信息

     

    以及各个线程的状态,执行的方法(pool-1-thread-1和pool-1-thread-2分别代表线程池的第一个线程和第二个线程):

     

    jmap

    一般可以生成当前堆栈快照。使用 jmap -heap可以打印出当前各个分区的内存使用情况,使用jmap -dump:format=b,file=dump.hprof 进程id可以生成当前的堆栈快照,堆快照和对象统计信息,对生成的堆快照进行分析,可以分析堆中对象所占用内存的情况,检查大对象等。执行jvisualvm命令打开使用Java自带的工具Java VisualVM来打开堆栈快照文件,进行分析。可以用于排查内存溢出,内存泄露问题。

    也可以配置启动时的JVM参数,让发送内存溢出时,自动生成堆栈快照文件。

    //出现 OOM 时生成堆 dump: 
    -XX:+HeapDumpOnOutOfMemoryError
    //生成堆文件地址:
    -XX:HeapDumpPath=/home/liuke/jvmlogs/
    

    查看内存使用情况

     

    jmap -histo打印出当前堆中的对象统计信息,包括类名,每个类的实例数量,总占用内存大小。

    instances列:表示当前类有多少个实例。
    bytes列:说明当前类的实例总共占用了多少个字节
    class name列:表示的就是当前类的名称,class name 对于基本数据类型,使用的是缩写。解读:B代表byte ,C代表char ,D代表double, F代表float,I代表int,J代表long,Z代表boolean 
    前边有[代表数组,[I 就相当于int[] 
    对象数组用`[L+类名`表示 

     

    使用jmap -dump:format=b,file=/存放路径/heapdump.hprof 进程id就可以得到堆转储文件,然后执行jvisualvm命令就可以打开JDK自带的jvisualvm软件。

    例如在这个例子中会造成OOM问题,通过生成heapdump.hprof文件,可以使用jvisualvm查看造成OOM问题的具体代码位置。

    public class Test018 {
    
        ArrayList<TestObject> arrayList = new ArrayList<TestObject>();
    
        public static void main(String[] args) {
            Test018 test018 =new Test018();
            Random random = new Random();
            for (int i = 0; i < 10000000; i++) {
                TestObject testObject = new TestObject();
                test018.arrayList.add(testObject);
            }
        }
        private static class TestObject {
            public byte[] placeholder = new byte[64 * 1024];//每个变量是64k
        }
    }
    
    -Xms20m -Xmx20m -verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/存放路径/heapdump.hprof
    

    造成OOM问题的代码位置:

     

    然后在主页点击Histogram,进入Histogram页面可以看到对象列表,with incomming references 也就是可以查看所有对这个对象的引用(思路一般优先看占用内存最大对象;其次看数量最多的对象。)。我们这个例子中主要是byte[]数组分配了占用了大量的内存空间,而byte[]主要来自于Test018类的静态变量arrayList的每个TestObject类型的元素的placeholder属性。

     

    同时可以点击 内存快照对比 功能对两个dump文件进行对比,判断两个dump文件生成间隔期间,各个对象的数量变化,以此来判断内存泄露问题。 

     

    展开全文
  • OSPF 有五种类型的协议报文: 1、Hello 报文:周期性发送,用来发现和维持 OSPF 邻居关系,以及进行 DR(Designated Router,指定路由器)/BDR(Backup Designated Router,备份指定路由器)的选举。 2、DD...

    一、OSPF报文

    OSPF 协议报文直接封装为 IP 报文,协议号为 89。

    OSPF 有五种类型的协议报文:

    1、Hello 报文:周期性发送,用来发现和维持 OSPF 邻居关系,以及进行 DR(Designated

    Router,指定路由器)/BDR(Backup Designated Router,备份指定路由器)的选举。

    2、DD(Database Description,数据库描述)报文:描述了本地 LSDB(Link State DataBase,

    链路状态数据库)中每一条 LSA(Link State Advertisement,链路状态通告)的摘要信息,

    用于两台路由器进行数据库同步。

    3、 LSR(Link State Request,链路状态请求)报文:向对方请求所需的 LSA。两台路由器互相

    交换 DD 报文之后,得知对端的路由器有哪些 LSA 是本地的 LSDB 所缺少的,这时需要发送

    LSR 报文向对方请求所需的 LSA。

    4、 LSU(Link State Update,链路状态更新)报文:向对方发送其所需要的 LSA。

    5、 LSAck(Link State Acknowledgment,链路状态确认)报文:用来对收到的 LSA 进行确认。

    二、LSA类型

    OSPF 中对链路状态信息的描述都是封装在 LSA 中发布出去,常用的 LSA 有以下几种类型:

    1、 Router LSA(Type-1):由每个路由器产生,描述路由器的链路状态和开销,在其始发的区域

    内传播。

    2、 Network LSA(Type-2):由 DR 产生,描述本网段所有路由器的链路状态,在其始发的区域

    内传播。

    3、 Network Summary LSA(Type-3):由 ABR(Area Border Router,区域边界路由器)产生,

    描述区域内某个网段的路由,并通告给其他区域。

    4、 ASBR Summary LSA(Type-4):由 ABR 产生,描述到 ASBR(Autonomous System

    Boundary Router,自治系统边界路由器)的路由,通告给相关区域。

    5、AS External LSA(Type-5):由 ASBR 产生,描述到 AS(Autonomous System,自治系统)

    外部的路由,通告到所有的区域(除了 Stub 区域和 NSSA 区域)。

    6、 NSSA External LSA(Type-7):由 NSSA(Not-So-Stubby Area)区域内的 ASBR 产生,描

    述到 AS 外部的路由,仅在 NSSA 区域内传播。

    7、 Opaque LSA:用于 OSPF 的扩展通用机制,目前有 Type-9、Type-10 和 Type-11 三种。其

    中,Type-9 LSA 仅在本地链路范围进行泛洪,用于支持 GR(Graceful Restart,平滑重启)

    的 Grace LSA 就是 Type-9 的一种类型;Type-10 LSA 仅在区域范围进行泛洪,用于支持

    MPLS TE 的 LSA 就是 Type-10 的一种类型;Type-11 LSA 可以在一个自治系统范围进行泛

    洪。

    三、OSPF区域

    OSPF基础,报文类型,区域划分,LSA类型,一分钟了解下

     

    1、区域的边界是路由器,而不是链路。一个路由器可以属于不同的区域,但是一个网段(链路)只能

    属于一个区域,或者说每个运行 OSPF 的接口必须指明属于哪一个区域。划分区域后,可以在区域

    边界路由器上进行路由聚合,以减少通告到其他区域的 LSA 数量,还可以将网络拓扑变化带来的影

    响最小化。

    2、骨干区域与虚连接

    1) 骨干区域(Backbone Area)

    OSPF 划分区域之后,并非所有的区域都是平等的关系。其中有一个区域是与众不同的,它的区域

    号是 0,通常被称为骨干区域。骨干区域负责区域之间的路由,非骨干区域之间的路由信息必须通

    过骨干区域来转发。对此,OSPF 有两个规定:

    所有非骨干区域必须与骨干区域保持连通;

    骨干区域自身也必须保持连通。

    在实际应用中,可能会因为各方面条件的限制,无法满足上面的要求。这时可以通过配置 OSPF 虚

    连接予以解决。

    2) 虚连接(Virtual Link)

    虚连接是指在两台 ABR 之间通过一个非骨干区域而建立的一条逻辑上的连接通道。它的两端必须

    是 ABR,而且必须在两端同时配置方可生效。为虚连接两端提供一条非骨干区域内部路由的区域称

    为传输区(Transit Area)。

    3、Stub区域和Totally Stub区域

    Stub 区域是一些特定的区域,该区域的 ABR 会将区域间的路由信息传递到本区域,但不会引入自

    治系统外部路由,区域中路由器的路由表规模以及 LSA 数量都会大大减少。为保证到自治系统外的

    路由依旧可达,该区域的 ABR 将生成一条缺省路由 Type-3 LSA,发布给本区域中的其他非 ABR

    路由器。

    为了进一步减少 Stub 区域中路由器的路由表规模以及 LSA 数量,可以将区域配置为 Totally Stub

    (完全 Stub)区域,该区域的 ABR 不会将区域间的路由信息和自治系统外部路由信息传递到本区

    域。为保证到本自治系统的其他区域和自治系统外的路由依旧可达,该区域的 ABR 将生成一条缺

    省路由 Type-3 LSA,发布给本区域中的其他非 ABR 路由器。

    4. NSSA区域和Totally NSSA区域

    NSSA(Not-So-Stubby Area)区域是 Stub 区域的变形,与 Stub 区域的区别在于 NSSA 区域允许

    引入自治系统外部路由,由 ASBR 发布 Type-7 LSA 通告给本区域。当 Type-7 LSA 到达 NSSA 的

    ABR 时,由 ABR 将 Type-7 LSA 转换成 Type-5 LSA,传播到其他区域。

    可以将区域配置为 Totally NSSA(完全 NSSA)区域,该区域的 ABR 不会将区域间的路由信息传

    递到本区域。为保证到本自治系统的其他区域的路由依旧可达,该区域的 ABR 将生成一条缺省路

    由 Type-3 LSA,发布给本区域中的其他非 ABR 路由器

    展开全文
  • OSPF区域划分与路由计算 ...2.2 OSPF的区域类型 骨干区域:Area0区域即为骨干区域,其他区域必须连接在骨干区域,同样其他区域也通过骨干区域交换信息 传输区域:需要将路由信息转发给其他区域 末端区...

    OSPF的区域划分与路由计算

    一、区域划分及LSA的种类

    1. 实验拓扑

    在这里插入图片描述

    2. 区域划分

    2.1 区域划分的目的

    • 防止环路,OSPF的同区域没有环路,多区域通过spf算法防止环路
    • 隔离LSA的泛洪
    • 增加网络的稳定性

    2.2 OSPF的区域类型

    • 骨干区域:Area0区域即为骨干区域,其他区域必须连接在骨干区域,同样其他区域也通过骨干区域交换信息
    • 传输区域:需要将路由信息转发给其他区域
    • 末端区域:没有再连其他区域

    2.3 区域的应用

    • 将末端区域配置成Stub区域:Stub中的路由表里会有一条缺省的3类LSA
      在这里插入图片描述在这里插入图片描述
    • 配置Totally Stub区域:路由表中只有一类和二类LSA:在配置Stub的基础上加上no-summary
    • NSSA区域:可以保证Stub区域引入外部网络(传输7类LSA),Stub区域的边界路由器会将7类LSA转为5类LSA
    • Totally NSSA区域:减少了三类LSA
      在这里插入图片描述
      在这里插入图片描述

    3. LSA的种类

    LSA类型 通告路由器 LSA内容 传播范围
    Router LSA(1类) OSPF Router 拓扑信息+路由信息 本地区域
    Network LSA(2类) DR 拓扑信息+路由信息 本地区域
    Network-summary-LSA(3类) ABR 域间路由信息 非Stub区域
    ASBR-summary-LSA(4类) ABR ASBR的Router ID 非Stub区域
    AS-external-LSA(5类) ASBR 路由进程域外路由 OSPF进程域
    NSSA LSA(7类) ASBR NSSA域外部路由信息 NSSA区域
    • Router LSA:查看AR4的ospf数据库
      在这里插入图片描述
    • Network-LSA:只有DR进行通告,并且只有MA网络才会产生。查看AR4的ospf数据库
      在这里插入图片描述
    • Network-summary-LSA:区域隔离了一类和二类LSA,三类LSA用于区域之间进行路由信息的交换,在AR3上查看去往192.168.1.0网段的三类LSA信息:使用命令 dis ospf lsdb summary 192.168.1.0
      在这里插入图片描述
    • ASBR-summary-LSA:配合五类LSA使用,在AR2上查看四类LSA信息
    • AS-external-LSA:在ASBR中配置到达外部网络的静态路由,并通过import-route static命令进行配置,在AR3上查看5类LSA信息:dis ospf lsdb ase self-originate
      在这里插入图片描述
      在AR3的GE0/0/1端口捕获数据包:
      在这里插入图片描述

    二、OSPF协议路由的计算

    当LSDB构建完成之后,需要调用SPF算法,对LSDB中的LSA进行处理,从而计算出所有路径

    1. 选定根节点
    2. 遍历该节点的所有直连节点,若根与某节点为:
      • 新分支,则添加到分支列表,并记录权重和下一跳
      • 已经存在于分支列表,则与其权重进行比较,将优先级最优的更新到分支列表
      • 已经存在于分支列表则可忽略
    3. 取出分支列表中的最优分支加入到权重列表,并选点该节点
    4. 分支列表非空,则继续步骤三,否则结束。

    三、参考资料

    展开全文
  • JVM内存区域的划分

    2017-02-18 23:19:12
    方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时...


    JVM 内存模型图


        方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息

    
    



    可以参考如下的链接:
    http://blog.csdn.net/ns_code/article/details/17565503

    展开全文
  •  内存中对数据的存储不是杂乱无章的,而是有相应的划分,根据数据类型分门别类安放到相应的位置。  存储的区域由最高存储地址到最低存储地址依次为: 命令行参数区:命令行参数和环境变量;栈区(stack):...
  • 这里引用了简书两张图片:...值类型:只需要一段单独内存,用于存储实际数据,(单独定义时候放在栈中) 引用类型:需要两段内存 第一段存储实际数据,它总是位于堆中 第二段是一个引用,指向数据在堆中...
  • OSPF协议将其管理的网络划分为不同类型的若干区域(Area),其中标准区域特点是(64);存根区域(stub)的特点是(65)。 (64)A.不接受本地AS之外的路由信息,也不接受其他区域的路由汇总信息 B.不接受本地AS...
  • 1. java中内存区域的划分 上节谈了Java中的垃圾回收机制,今天我们聊聊Java中内存区域的划分。 总得来说Java中内存分为四块:栈、堆、数据域、代码域 1. 栈 栈中主要存放基本类型的数据和对象的引用也就是存放变量...
  • Java中内存区域划分

    2019-12-11 20:25:37
    在函数中定义的基本类型的变量数据和对象的引用变量都在栈中, 栈内存是Java程序的运行区,是在线程创建时创建的,他的生命周期跟随线程的生命周期,线程结束后,占内存也就释放了。栈中的数据是以栈帧的格式存在的...
  • 在研究袁店一矿地质条件基础上,利用几何分形理论绘制岩体结构分布图,进而对103采区底板岩体结构的类型进行划分。结果表明:采区内大部分岩体结构都是破碎或是块裂,只有部分区域岩体结构属于松散或是完整
  • JVM中内存主要划分为5个区域,即方法区,堆内存,程序计数器,虚拟机栈以及本地方法栈。 方法区:方法区是一个线程之间共享的区域。常量,静态变量以及JIT编译后代码都在方法区。主要用于存储已被虚拟机加载类...
  • OSPF:开放式最短路径优先协议 V1/V2/V3 v3为IPV6使用 无类别链路状态路由协议... 区域划分 2.地址规划 触发更新、30min周期更新;组播更新地址224.0.0.5 224.0.0.6 一、ospf数据包类型 Hello 发现、建立、...
  • OSPF区域类型

    2010-08-05 21:50:34
    OSPF区域类型划分如下: 1.骨干区域(即传输区域) :area 0 2.非骨干区域(即常规区域):除area 0之外其他所有许可范围内区域 非骨干区域又可划分如下: 1.标准区域:即正常传输数据区域 ...
  • 三角形划分区域

    2017-05-31 15:29:20
    用N个三角形最多可以把平面分成几个区域? 输入描述 输入数据第一行是一个正整数T(1 输出描述 对于每组测试数据,请输出题目中要求结果。 输入样例 2 1 2 输出样例 2 8 提示 来源or类型 入门题-...
  • 栈:对象引用 基本数据类型,栈帧,局部变量表,操作数栈,方法出口\color{red}{基本数据类型,栈帧,局部变量表,操作数栈,方法出口}基本数据类型,栈帧,局部变量表,操作数栈,方法出口 本地方法:n
  • java内存区域划分

    2019-07-17 17:00:45
    ...2. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中 3. 堆:存放用new产生的数据 4. 静态域:存放在对象中用static定义的静态成员 5. 常量池:存放常量 6...
  • JVM内存区域划分

    2013-06-11 13:37:51
    JAVA 运行时内存区域划分为5个区域:程序计数器,java虚拟机栈,本地方法栈,堆和方法区。 1,程序计数器:线程私有,下一个要执行字节码地址; 2,java虚拟机栈:线程私有;每个JVM线程在创建时候,...
  • OSPF 有五种类型的协议报文:1、Hello 报文:周期性发送,用来发现和维持 OSPF 邻居关系,以及进行 DR(DesignatedRouter,指定路由器)/BDR(Backup Designated Router,备份指定路由器)的选举。2、DD(Database ...
  • Java内存区域划分

    2012-06-26 21:03:07
    Java内存区域划分:1、程序计数器:每个线程私有,记录当前线程执行字节码行号信息。工作时通过改变其值来选取下一条字节码指令(单线程执行、或多线程CPU时间片切换时)。2、虚拟机栈:每个线程私有,生命周期...
  • java 内存区域划分

    2013-10-30 11:39:32
    堆(Heap)和非堆(Non-heap)内存按照官方的说法:“Java 虚拟机具有一个堆,...可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,...
  • OSPF划分区域详解.

    2011-11-09 10:30:54
    如果一台ABR路由器经过骨干区域从其他ABR路由器收到多条网络汇总LSA,那么这台始发ABR路由器将会选择这些LSA通告中代价最低LSA,并且将这个LSA最低代价通告给与它相连非骨干区域。 4、ASBR汇总LSA ...
  • 通过分析煤炭基地规划矿区煤层与含水层发育特征、煤层与含水层在垂向与平面上叠置关系,在考虑成煤时代、充水水源类型、充水方式及充水途径等因素,对我国煤炭基地规划矿区进行水文地质类型划分,为煤炭资源科学开采...
  • JVM内存区域划分简述

    2019-12-01 04:03:54
    引用类型的地址都在这里存放,如数组, 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部 变量. 在有些版本的 JVM 实现中(例如HotSpot),...
  • 程序由代码和数据组成,其中代码存储在代码区中,数据根据类型的不同存储在不同的区域中。本文分别介绍了C和C++中内存区域的划分。 C++作为一款C语言的升级版本,具有非常强大的功能。它不但能够支持各种程序设计...
  • 脱离对象–和对象解绑—和这个类对象无关了 class A { 修饰符 static 数据类型 变量名 =初始化(可写可不写) } 静态属性不保存在对象中 而是保存在类中(由于类在程序运行中只有一份 所以静态属性只有一份) 静态...
  • 摘要:图像分割大部分时候会涉及到直方图统计信息计算,对直方图区域进行划分后,可以得到相应直方图统计信息(目标/背景先验概率、均值等),由此可以进一步求解不同阈值选取准则函数。此处仅给出最常用一...
  • 【Java】内存区域划分

    2016-05-16 09:37:32
    java中内存区域分为4类 栈内存空间,存放引用堆内存空间地址 堆内存空间,存放new出来对象 全局数据区,保存static类型属性和全局变量 全局代码区,保存所有方法定义
  • 1.8版本 最清晰理解Java内存区域划分

    千次阅读 2019-03-27 16:31:48
    从Java的各种数据类型的存储看Java内存区域划分 一、各种基本数据类型的存储 我们先来看一段小代码: public class{ int a = 20; public static void main(String[] args){ int b = 10; String str1 = ”abc...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,082
精华内容 432
关键字:

区域类型的划分