精华内容
下载资源
问答
  • 思维导图 对象的创建 对象的内存布局 对象头( Header ) ...在 JVM-01自动内存管理机制之Java内存区域与内存溢出异常(上)中我们介绍了 运行时数据区域,这里我们来继续探讨下hotspot虚拟机对象 ...

    思维导图

    这里写图片描述

    在 JVM-01自动内存管理机制之Java内存区域与内存溢出异常(上)中我们介绍了 运行时数据区域,这里我们来继续探讨下hotspot虚拟机对象


    对象的创建

    这里写图片描述

    在语言层面上,创建对象(例如克隆、反序列化)通常仅仅是一个new 关键字而己,而在虚拟机中,对象(指普通Java 对象,非数组和Class 对象等) 的创建是一个非常复杂的过程。

    虚拟机遇到一条new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java 堆中划分出来。

    假设Java 堆中内存是绝对规整的,就仅仅是把指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为**“指针碰撞**”( Bump the Pointer)。

    如果Java 堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录哪些内存块是可用的, 在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录,这种分配方式称为“空闲列表”( Free List )。

    选择哪种分配方式由Java 堆是否规整决定,而Java 堆是否规整是由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew 等带Compact(紧凑)过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS 这种基于Mark-Sweep 算法的收集器时,通常采用空闲列表。

    还要考虑内存分配在多线程下同步问题。一种解决办法是对分配内存空间的动作进行同步处理。实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性:另一种是把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java 堆中预先分配一小块内存,称为本地线程分配缓冲( Thread Local Allocation Buffer)。哪个线程要分配内存,就在哪个线程的TLAB 上分配,只有TLAB 用完并分配新的于LAB 时,才需要同步锁定。

    内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),接下来,虚拟机要对对象进行必要的设置。例如这个对象是哪个类的实例、如何才能找到类的元数信息、对象的哈希码、对象的GC 分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁等。


    对象的内存布局

    在HotSpot虚拟机中,对象在内存中存储的布局可以分为3 块区域:对象头( Header )、实例数据(Instance Data)和对齐填充(Padding)。

    ##对象头( Header )
    Hot Spot 虚拟机的对象头包括两部分信息

    • 第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID 、偏向时间戳等
    • 另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

    实例数据(Instance Data)

    数据部分,是对象存储的真正有效信息,也是在程序代码中所定义的各种类型字段的内容。包括父类和接口继承下来的,也包括子类中定义的。这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源代码中定义顺序的影响。从分配策略中知道,相同宽度的字段总是被分配到一起。在这个前提下父类定义的变量会出现在子类之前。


    对齐填充(Padding)

    对齐填充,不是必须的,只起到地址对齐的作用。HotSpot自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是对象内存大小必须是8字节的整数倍。因此当对象实例数据没有对齐时,就需要通过Padding的方式来补全。


    对象的访问定位

    Java程序通过栈上的reference数据来操作堆上的具体对象,由于reference类型在Java虚拟机规范中只规定了一个指向对象引用。而没有规定这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,它取决于Java虚拟机实现。


    目前主要有如下两种实现方式

    使用句柄(类似间接指针)

    在Java堆中划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含对象实例数据与类型各自具体地址信息

    这里写图片描述


    直接指针访问 (HotSpot采用这种方式)

    Java堆中的对象布要考虑如何放置访问类型数据相关的信息,而reference中存储的直接就是对象地址

    这里写图片描述


    两种方式的比较

    句柄的好处reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。

    直接指针访问的最大好处就是速度更快,节省一次指针定位时间开销,因为对象访问在Java中非常频繁,这类开销积少成多也非常可观。


    展开全文
  •  不同的虚拟机实现中,对象的内存布局有差别,以最常用的HotSpot虚拟机为例,对象在内存中存储的布局分为3块区域: 对象头(Header) 、 实例数据(Instance Data) 、 对齐填充(Padding) 。   1)对象头:...
    本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。最近开始看这本书,打算再开一个相关系列,来总结一下这本书中的重要知识点。呃呃呃,说好的那个图片请求框架呢奋斗奋斗奋斗~  不要急哈,因为这个请求框架设计的内容还是比较广的,目前业余时间正在编写当中,弄好了之后就会放上来。在完成之前,咱还是先来学习一下其他知识。微笑微笑微笑

    1、内存模型

    java虚拟机在执行java程序的过程中会把它说管理的内存划分为若干个不同的数据区域,如下图所示:
    图片来源于网络
    (1)程序计数器(Program Counter Register)
        线程私有。程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复登记处功能都需要依赖这个计数器的值来完成。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。这类内存区域称为“线程私有”的内存
        程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域

    (2)Java虚拟机栈
        也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表操作数栈动态链接方法出口等信息。平常我们把java分为堆内存和栈内存,其中的“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中局部变量表部分。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小
        对于java虚拟机栈,有两种异常情况:
        1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
        2)如果虚拟机栈在动态扩展时,无法申请到足够的内存,就会抛出OutOfMemoryError
        
         Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。

    (3)本地方法栈(Native Method Stack)
        线程私有。本地方法栈和虚拟机栈所发挥的作用非常相似,它们之间的区别主要是,虚拟机栈是为虚拟机执行Java方法(也就是字节码)服务的,而本地方法栈则为虚拟机使用到的Native方法服务
        与虚拟机栈类似,本地方法栈也会抛出StackOverflowErrorOutOfMemoryError异常。

    (4)Java堆(Java Heap)
        所有线程共享。Java堆在虚拟机启动时创建,是Java虚拟机所管理的内存中最大的一块。Java堆的唯一目的就是存放对象实例数组
        Java堆是垃圾收集器管理的主要区域,因此也成为“GC堆”。从内存回收的角度来看,由于现在收集器大都采用分代收集算法,所以Java堆可以细分为:新生代老年代;再细分一点:Eden空间From Survivor空间 To Survivor空间等。从内存分配角度来看,线程共享的Java堆可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。但是不管怎么划分,哪个区域,存储的都是对象实例。
        Java堆物理上不需要连续的内存,只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且也无法再扩展时,将会抛出OutOfMemoryError异常。

    (5)方法区(Method Area)
        所有线程共享。用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。方法区也有一个别名叫做Non-Heap(非堆),用于与Java堆区分。对于HotSpot虚拟机来说,方法区又习惯称为“永久代”(Permancent Generation),但这只是对于HotSpot虚拟机来说的,其他虚拟机的实现上并没有这个概念。相对而言,垃圾收集行为在这个区域比较少出现,但也并非不会来收集,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载上
        
        运行时常量池:
        运行时常量池属于方法区。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。也就是说,这部分内容,在编译时只是放入到了常量池信息中,到了加载时,才会放到运行时常量池中去。运行时常量池县归于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区的运行时常量池运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。

        当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常,常量池属于方法区,同样可能抛出OutOfMemoryError异常。

    (6)直接内存(Direct Memory)
        直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁的使用,也可能会导致OutOfMemoryError异常。直接内存的分配不会受到Java堆大小的限制,但需要收到本机总内存的大小以及处理器寻址空间的限制。典型的一个使用直接内存的例子,就是JDK1.4中加入的NIO。关于NIO具体内容可看《Java笔试面试题整理第七波》中的第六部分。

    内存区域模型小结:
        (1)线程私有的区域:程序计数器、虚拟机栈、本地方法栈;
        (2)所有线程共享的区域:Java堆、方法区;(注:直接内存不属于虚拟机内存模型的部分)
        (3)没有异常的区域:程序计数器;
        (4)StackOverflowError异常:Java虚拟机栈、本地方法栈;
        (5)OutOfMemoryError异常:除程序计数器外的其他四个区域,Java虚拟机栈、本地方法栈、Java堆、方法区;直接内存也会出现OutOfMemoryError。

    2、对象的创建、对象内存布局、对象的访问定位

    2.1 对象的创建过程    

    Java在语言层面,通过一个关键字new来创建对象。在虚拟机中,当遇到一条new指令后,将开始如下创建过程:
        (1)判断类是否加载、解析、初始化
        虚拟机遇到一条new指令时,先去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那先执行相应的类加载过程。
        (2)为新对象分配内存
        前面说到,对象的内存分配是在Java堆中的。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,此时Java堆中的情况有两种可能,一种是Java堆中内存是绝对规整的,一种是Java堆中的内存并不是规整的。因此有两种分配方式:
        1)Java堆内存是规整的,即所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,此时,分配内存仅需要把这个指针向空闲空间那边挪动一段与对象大小相等的距离,这种方式也称为“指针碰撞”(Bump the Pointer);
        2)Java堆内存不是规整的,即已使用的内存和空闲的内存相互交错,就没有办法简单地进行指针的移动,此时的分配方案是,虚拟机必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的控件划分给对象实例,并更新列表上的记录,这种方式也称为“空闲列表”(Free List);

        选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,对于Serial、ParNew等带Compact过程的垃圾收集器,系统采用的是指针碰撞算法;对于CMS这种基于Mark-Sweep算法的收集器,通常采用空闲列表算法。

        (3)解决并发安全问题
        确定了如何划分内存空间之后,还有一个问题就是,对象的创建在虚拟机中是非常频繁的行为,比如,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况,解决这种并发问题,一般有两种方案:
        1)对分配内存空间的动作进行同步处理,比如,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
        2)另一种方式是,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时,才需要同步锁定,虚拟机是否使用TLAB,可以通过-XX:+/-UserTLAB参数来设定。

        (4)初始化分配到的内存空间
        内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作也可以提前至TLAB分配时进行。也正是这一步操作,才保证了我们对象的实例字段在Java代码中可以不赋初值就直接使用。注意,此时对象的实例字段全部为零值,并没有按照程序中的初值进行初始化

        (5)设置对象实例的对象头
        上面工作完成后,虚拟机对对象进行必要的设置,主要是设置对象的对象头信息,比如,这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等...
        
        (6)初始化对象<init>方法
        其实,上面工作完成后,从虚拟机角度来看,一个新的对象已经产生了,但从Java程序的角度来看,对象创建才刚刚开始,对象实例中的字段仅仅都为零值,还需要通过<init>方法进行初始化,把对象按照程序员的意愿进行初始化。此时,一个真正可用的对象才算完全产生出来。

    2.2 对象的内存布局  

    经过前面的创建工作,一个对象已经成功产生,也已经在Java堆中分配好了内存。那这个对象在Java堆内存中到底是什么形态呢?又包括哪些部分呢?这就涉及到了对象的内存布局了。
        不同的虚拟机实现中,对象的内存布局有差别,以最常用的HotSpot虚拟机为例,对象在内存中存储的布局分为3块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding)
        1)对象头:包含两部分信息,一部分是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个Java数组,对象头中还有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组大小。
        2)实例数据:真正存储对象有效信息的部分。也就是在程序中定义的各种类型的字段内容,包括从父类继承下来的,以及子类中定义的,都会在实例数据中记录。
        3)对齐填充:不是必然存在的,仅起着占位符的作用,对于HotSpot来说,虚拟机的自动内存管理系统要求对象其实地址必须是8字节的整数倍,因此,如果对象实例数据部分没有对齐时,就需要通过对齐填充的方式来补全。

    2.3 对象的访问定位  

    建立了对象是为了使用对象,我们对数据的使用是通过栈上的reference数据来操作堆上的具体对象,对于不同的虚拟机实现,reference数据类型有不同的定义,主要是如下两种访问方式:
        1)使用句柄访问。此时,Java堆中将会划出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据类型数据各自的具体地址信息,如下图:    

        2)使用直接指针访问。此时reference中存储的就是对象的地址。如下图:


    上面所说的,所谓对象类型,其实就是指,对象所属的哪个类。
        
    上面两种对象访问方式各有优势,使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时,只会改变句柄中的实例数据指针,而reference本身不需要修改;使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销(根据上图,节省的是对象实例数据的指针定位),由于对象的访问在Java中非常频繁,因此,这类开销积少成多后也是一项非常可观的执行成本。对于HotSpot而言,选择的是第二种方式。

    3、常见的OOM和SOF

    OOM分为两种情况:内存溢出(Memory Overflow)内存泄漏(Memory Leak)
    OOMOutOfMemoryError异常
        即内存溢出,是指程序在申请内存时,没有足够的空间供其使用,出现了Out Of Memory,也就是要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
        内存溢出分为上溢下溢比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。
        
        有时候内存泄露会导致内存溢出,所谓内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,举个例子,就是说系统的篮子(内存)是有限的,而你申请了一个篮子,拿到之后没有归还(忘记还了或是丢了),于是造成一次内存泄漏。在你需要用篮子的时候,又去申请,如此反复,最终系统的篮子无法满足你的需求,最终会由内存泄漏造成内存溢出

        遇到的OOM:
        (1)Java Heap 溢出
        Java堆用于存储对象实例,我们只要不断的创建对象,而又没有及时回收这些对象(即内存泄漏),就会在对象数量达到最大堆容量限制后产生内存溢出异常。
        (2)方法区溢出
       方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

    异常信息:java.lang.OutOfMemoryError:PermGen space

    方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。


    SOF:StackOverflow(堆栈溢出
        当应用程序递归太深而发生堆栈溢出时,抛出该错误。因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
        栈溢出的原因:
        (1)递归调用
        (2)大量循环或死循环
        (3)全局变量是否过多
        (4)数组、List、Map数据过大

    (注:文章中图片来源于网络)
    展开全文
  •  在光栅显示平面上,多边形是封闭的,它是用某一边界色围成的一个闭合区域,填充是逐行进行的,即用扫描线逐行对多边形求交,在交点对之间填充。边界标志填充算法就是在逐行处理时,利用边界或边界颜色作为标志来...

    http://blog.csdn.net/orbit/article/details/7467543

    四、边界标志填充算法

            在光栅显示平面上,多边形是封闭的,它是用某一边界色围成的一个闭合区域,填充是逐行进行的,即用扫描线逐行对多边形求交,在交点对之间填充。边界标志填充算法就是在逐行处理时,利用边界或边界颜色作为标志来进行填充的。准确地说,边界标志填充算法不是指某种具体的填充算法,而是一类利用扫描线连贯性思想的填充算法的总称。这类算法有很多种,本篇就介绍几种。

            首先介绍一种以边为中心的边缘填充算法,这种边界标志算法的基本思想是:对于每一条扫描线和每一条多边形边的交点(xiyi),将该扫描线上交点右方的所有象素取补,依次对多边形的每条边作此处理,直到最终完成填充。这里要介绍一下取补的定义,假设某点的颜色是M,则对该点的颜色取补得到M’ = A – MA是一个很大的数字,至少要比所有合法的颜色值大。根据取补的定义,如果对光栅位图某区域已经标记为M的颜色值做偶数次取补运算,该区域颜色不变;而做奇数次取补运算,则该区域颜色变为值为M’的颜色。算法可以简单描述为两个步骤:

     

    1、将绘图窗口的背景色置为M’颜色;

    2、对多边形的每一条非水平边,从该边上的每个象素开始向右求余;

     

    算法的处理过程如图(12)所示,左边是多边形的形状,右边分别是对每条边处理完成后填充区域的颜色情况,初始背景颜色是M’,经过处理后,需要填充的区域是奇数次取补,最终的颜色是要填充的正确值M,非填充区域经过偶数次取补,仍然是背景色M’

    图(12)边缘填充算法的处理过程

     

            算法的实现非常简单,对于光栅位图的展示,我们仍然采用前文所用的方法,用数字矩阵表示一块光栅位图区域,矩阵的每个位置表示一个像素点,用09表示颜色值。本算法示例用9表示最大值A0表示无效的区域,合法的颜色值就是18

    87 void EdgeCenterMarkFill(const Polygon& py, int color)

    88 {

    89     std::vector<EDGE3> et;

    90 

    91     InitScanLineEdgesTable(et, py);//初始化边表

    92 

    93     FillBackground(- color); //对整个填充区域背景颜色取补

    94     for_each(et.begin(), et.end(), EdgeScanMarkColor);//依次处理每一条边

    95 }

    76 void EdgeScanMarkColor(EDGE3& e)

    77 {

    78     for(int y = e.ymax; y >= e.ymin; y--)

    79     {

    80         int x = ROUND_INT(e.xi);

    81         ComplementScanLineColor(x, MAX_X_CORD, y);

    82 

    83         e.xi -= e.dx;

    84     }

    85 }

    这个算法非常简单,所有的函数实现也很简单,InitScanLineEdgesTable()函数前面已经介绍过,FillBackground()函数将填充背景初始化为要填充颜色的取补颜色,EdgeScanMarkColor()函数负责对每条非水平边进行处理,逐条扫描线进行颜色取补,ComplementScanLineColor()函数负责对y扫描线上[x1, x2]区间的点的颜色值取补。

            以上以边为中心的填充算法的优点是简单,缺点是对于复杂多边形,每一象素可能被访问多次(多次取补),效率不高。考虑对此算法改进,人们提出了栅栏填充算法。栅栏填充算法的基本思想是:经过多边形的某个顶点,在多边形内部建立一个与扫描线垂直的“栅栏”,当扫描线与多边形边有交点,就将交点与栅栏之间的象素取补。若交点位于栅栏左边,则将交点之右,栅栏之左的所有象素取补;若交点位于栅栏右边,则将栅栏之右,交点之左的象素取补。

            仍以图(12)所示的多边形为例,假设经过P4点建立一条栅栏,则改进的栅栏填充算法处理过程就如图(13)所示:

    图(13)栅栏填充算法的处理过程

     

    栅栏填充算法的实现和以边为中心的边缘填充算法类似,只是对每条边的扫描线取补处理的范围控制有区别,算法需要指定一个“栅栏”:

    115 void EdgeFenceMarkFill(const Polygon& py, int fence, int color)

    116 {

    117     std::vector<EDGE3> et;

    118 

    119     InitScanLineEdgesTable(et, py);//初始化边表

    120 

    121     FillBackground(- color); //对整个填充区域背景颜色取补

    122     for_each(et.begin(), et.end(),

    123         std::bind2nd(std::ptr_fun(FenceScanMarkColor), fence));//依次处理每一条边

    124 }

     97 void FenceScanMarkColor(EDGE3 e, int fence)

     98 {

     99     for(int y = e.ymax; y >= e.ymin; y--)

    100     {

    101         int x = ROUND_INT(e.xi);

    102         if(> fence)

    103         {

    104             ComplementScanLineColor(fence, x, y);

    105         }

    106         else

    107         {

    108             ComplementScanLineColor(x, fence - 1, y);

    109         }

    110 

    111         e.xi -= e.dx;

    112     }

    113 }

     

    注意FenceScanMarkColor()函数和EdgeScanMarkColor()函数的区别,就是这点区别使得栅栏填充算法主动减少了很多像素被访问的次数,而多边形之外的像素也不会被多余处理,效率提高了不少。

            栅栏填充算法只是减少了被重复访问的象素的数目,但仍有一些象素会被重复访问。从图(13)中很容易看出这一点。下面介绍的边标志算法利用扫描线的连贯性,对每个像素只访问一次即可完成多边形区域的填充。边标志算法的思想是设置一个标志,沿着扫描线从左到右访问多边形所在区域的像素的时候,用这个标志标识是否扫描到了多边形的边界。如果碰到边界(边界用特殊的颜色标识),则改变标识状态,接下来根据标识状态决定扫描到的像素点是否要填充成指定颜色。

            在实施边标志填充算法通常先求出多边形所在图像区域的最小包围盒,以便缩小扫描范围,加快填充速度。完整的填充算法如下:

     5 void EdgeMarkFill(int xmin, int xmax, int ymin, int ymax,

     6                   int boundarycolor, int color)

     7 {

     8     int flag = -1;

     9     for(int y = ymin; y <= ymax; y++)

    10     {

    11         flag = -1;

    12         for(int x = xmin; x <= xmax; x++)

    13         {

    14             if(GetPixelColor(x, y) == boundarycolor)

    15             {

    16                 flag = -flag;

    17             }

    18             if(flag == 1)

    19             {

    20                 SetPixelColor(x, y, color);

    21             }

    22         }

    23     }

    24 }

    可以看到该算法思想简单,实现容易。算法既不需要初始化边表、求交点、交点排序等额外操作,也不需要使用链表、堆栈等数据结构。不过,边标志算法虽然简单,但是使用时对边界的处理要格外小心,否则很容易出错。比如上下顶点之类的局部极值点,会造成标志的奇偶计数不匹配,填充时会出现“抽丝”现象,也就是扫描线上不该填充的区段被填上色,而应该填充的区段却没有填上色。再比如边界像素点重复的问题,多边形的边界是利用直线的生成算法依次画出的,当扫描线遇到斜率小于1的边时,容易产生重复的边界点,如图(14)所示,引起标志的无序改变,从而造成填充错误。

     

    图(14)斜率小于1的边的光栅扫描转换问题

             至此,本文完整介绍了8中常见的多边形区域填充算法,拖拖沓沓写了一个多月,总算完成了,居然有将近30页,总计13000多字(24000多可见字符),毕业以后就没写过这么长的文档了,感慨一下。本文给出的示例代码都是为了演示,基于可读性而设计的,大家在实际的图形处理软件中看到的算法实现可能和本文的示例算法“大相径庭”,但是基本原理都是一样的。

     

     

     

     

    参考资料:

     

    【1】计算几何:算法设计与分析 周培德  清华大学出版社 2005年

    【2】计算几何:算法与应用 德贝尔赫(邓俊辉译)  清华大学出版社 2005年

    【3】计算机图形学 孙家广、杨常贵 清华大学出版社 1995年


    展开全文
  •  不同的虚拟机实现中,对象的内存布局有差别,以最常用的HotSpot虚拟机为例,对象在内存中存储的布局分为3块区域: 对象头(Header) 、 实例数据(Instance Data) 、 对齐填充(Padding) 。   1)对象头: 包含...
    本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。最近开始看这本书,打算再开一个相关系列,来总结一下这本书中的重要知识点。呃呃呃,说好的那个图片请求框架呢奋斗奋斗奋斗~  不要急哈,因为这个请求框架设计的内容还是比较广的,目前业余时间正在编写当中,弄好了之后就会放上来。在完成之前,咱还是先来学习一下其他知识。微笑微笑微笑

    1、内存模型

    java虚拟机在执行java程序的过程中会把它说管理的内存划分为若干个不同的数据区域,如下图所示:
    图片来源于网络
    (1)程序计数器(Program Counter Register)
        线程私有。程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复登记处功能都需要依赖这个计数器的值来完成。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。这类内存区域称为“线程私有”的内存
        程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域

    (2)Java虚拟机栈
        也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表操作数栈动态链接方法出口等信息。平常我们把java分为堆内存和栈内存,其中的“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中局部变量表部分。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小
        对于java虚拟机栈,有两种异常情况:
        1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
        2)如果虚拟机栈在动态扩展时,无法申请到足够的内存,就会抛出OutOfMemoryError
        
         Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。

    (3)本地方法栈(Native Method Stack)
        线程私有。本地方法栈和虚拟机栈所发挥的作用非常相似,它们之间的区别主要是,虚拟机栈是为虚拟机执行Java方法(也就是字节码)服务的,而本地方法栈则为虚拟机使用到的Native方法服务
        与虚拟机栈类似,本地方法栈也会抛出StackOverflowErrorOutOfMemoryError异常。

    (4)Java堆(Java Heap)
        所有线程共享。Java堆在虚拟机启动时创建,是Java虚拟机所管理的内存中最大的一块。Java堆的唯一目的就是存放对象实例数组
        Java堆是垃圾收集器管理的主要区域,因此也成为“GC堆”。从内存回收的角度来看,由于现在收集器大都采用分代收集算法,所以Java堆可以细分为:新生代老年代;再细分一点:Eden空间From Survivor空间 To Survivor空间等。从内存分配角度来看,线程共享的Java堆可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。但是不管怎么划分,哪个区域,存储的都是对象实例。
        Java堆物理上不需要连续的内存,只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且也无法再扩展时,将会抛出OutOfMemoryError异常。

    (5)方法区(Method Area)
        所有线程共享。用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。方法区也有一个别名叫做Non-Heap(非堆),用于与Java堆区分。对于HotSpot虚拟机来说,方法区又习惯称为“永久代”(Permancent Generation),但这只是对于HotSpot虚拟机来说的,其他虚拟机的实现上并没有这个概念。相对而言,垃圾收集行为在这个区域比较少出现,但也并非不会来收集,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载上
        
        运行时常量池:
        运行时常量池属于方法区。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。也就是说,这部分内容,在编译时只是放入到了常量池信息中,到了加载时,才会放到运行时常量池中去。运行时常量池县归于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区的运行时常量池运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。

        当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常,常量池属于方法区,同样可能抛出OutOfMemoryError异常。

    (6)直接内存(Direct Memory)
        直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁的使用,也可能会导致OutOfMemoryError异常。直接内存的分配不会受到Java堆大小的限制,但需要收到本机总内存的大小以及处理器寻址空间的限制。典型的一个使用直接内存的例子,就是JDK1.4中加入的NIO。关于NIO具体内容可看《Java笔试面试题整理第七波》中的第六部分。

    内存区域模型小结:
        (1)线程私有的区域:程序计数器、虚拟机栈、本地方法栈;
        (2)所有线程共享的区域:Java堆、方法区;(注:直接内存不属于虚拟机内存模型的部分)
        (3)没有异常的区域:程序计数器;
        (4)StackOverflowError异常:Java虚拟机栈、本地方法栈;
        (5)OutOfMemoryError异常:除程序计数器外的其他四个区域,Java虚拟机栈、本地方法栈、Java堆、方法区;直接内存也会出现OutOfMemoryError。

    2、对象的创建、对象内存布局、对象的访问定位

    2.1 对象的创建过程    

    Java在语言层面,通过一个关键字new来创建对象。在虚拟机中,当遇到一条new指令后,将开始如下创建过程:
        (1)判断类是否加载、解析、初始化
        虚拟机遇到一条new指令时,先去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那先执行相应的类加载过程。
        (2)为新对象分配内存
        前面说到,对象的内存分配是在Java堆中的。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,此时Java堆中的情况有两种可能,一种是Java堆中内存是绝对规整的,一种是Java堆中的内存并不是规整的。因此有两种分配方式:
        1)Java堆内存是规整的,即所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,此时,分配内存仅需要把这个指针向空闲空间那边挪动一段与对象大小相等的距离,这种方式也称为“指针碰撞”(Bump the Pointer);
        2)Java堆内存不是规整的,即已使用的内存和空闲的内存相互交错,就没有办法简单地进行指针的移动,此时的分配方案是,虚拟机必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的控件划分给对象实例,并更新列表上的记录,这种方式也称为“空闲列表”(Free List);

        选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,对于Serial、ParNew等带Compact过程的垃圾收集器,系统采用的是指针碰撞算法;对于CMS这种基于Mark-Sweep算法的收集器,通常采用空闲列表算法。

        (3)解决并发安全问题
        确定了如何划分内存空间之后,还有一个问题就是,对象的创建在虚拟机中是非常频繁的行为,比如,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况,解决这种并发问题,一般有两种方案:
        1)对分配内存空间的动作进行同步处理,比如,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
        2)另一种方式是,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时,才需要同步锁定,虚拟机是否使用TLAB,可以通过-XX:+/-UserTLAB参数来设定。

        (4)初始化分配到的内存空间
        内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作也可以提前至TLAB分配时进行。也正是这一步操作,才保证了我们对象的实例字段在Java代码中可以不赋初值就直接使用。注意,此时对象的实例字段全部为零值,并没有按照程序中的初值进行初始化

        (5)设置对象实例的对象头
        上面工作完成后,虚拟机对对象进行必要的设置,主要是设置对象的对象头信息,比如,这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等...
        
        (6)初始化对象<init>方法
        其实,上面工作完成后,从虚拟机角度来看,一个新的对象已经产生了,但从Java程序的角度来看,对象创建才刚刚开始,对象实例中的字段仅仅都为零值,还需要通过<init>方法进行初始化,把对象按照程序员的意愿进行初始化。此时,一个真正可用的对象才算完全产生出来。

    2.2 对象的内存布局  

    经过前面的创建工作,一个对象已经成功产生,也已经在Java堆中分配好了内存。那这个对象在Java堆内存中到底是什么形态呢?又包括哪些部分呢?这就涉及到了对象的内存布局了。
        不同的虚拟机实现中,对象的内存布局有差别,以最常用的HotSpot虚拟机为例,对象在内存中存储的布局分为3块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding)
        1)对象头:包含两部分信息,一部分是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个Java数组,对象头中还有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组大小。
        2)实例数据:真正存储对象有效信息的部分。也就是在程序中定义的各种类型的字段内容,包括从父类继承下来的,以及子类中定义的,都会在实例数据中记录。
        3)对齐填充:不是必然存在的,仅起着占位符的作用,对于HotSpot来说,虚拟机的自动内存管理系统要求对象其实地址必须是8字节的整数倍,因此,如果对象实例数据部分没有对齐时,就需要通过对齐填充的方式来补全。

    2.3 对象的访问定位  

    建立了对象是为了使用对象,我们对数据的使用是通过栈上的reference数据来操作堆上的具体对象,对于不同的虚拟机实现,reference数据类型有不同的定义,主要是如下两种访问方式:
        1)使用句柄访问。此时,Java堆中将会划出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据类型数据各自的具体地址信息,如下图:    

        2)使用直接指针访问。此时reference中存储的就是对象的地址。如下图:


    上面所说的,所谓对象类型,其实就是指,对象所属的哪个类。
        
    上面两种对象访问方式各有优势,使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时,只会改变句柄中的实例数据指针,而reference本身不需要修改;使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销(根据上图,节省的是对象实例数据的指针定位),由于对象的访问在Java中非常频繁,因此,这类开销积少成多后也是一项非常可观的执行成本。对于HotSpot而言,选择的是第二种方式。

    3、常见的OOM和SOF

    OOM分为两种情况:内存溢出(Memory Overflow)内存泄漏(Memory Leak)
    OOMOutOfMemoryError异常
        即内存溢出,是指程序在申请内存时,没有足够的空间供其使用,出现了Out Of Memory,也就是要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
        内存溢出分为上溢下溢比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。
        
        有时候内存泄露会导致内存溢出,所谓内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,举个例子,就是说系统的篮子(内存)是有限的,而你申请了一个篮子,拿到之后没有归还(忘记还了或是丢了),于是造成一次内存泄漏。在你需要用篮子的时候,又去申请,如此反复,最终系统的篮子无法满足你的需求,最终会由内存泄漏造成内存溢出

        遇到的OOM:
        (1)Java Heap 溢出
        Java堆用于存储对象实例,我们只要不断的创建对象,而又没有及时回收这些对象(即内存泄漏),就会在对象数量达到最大堆容量限制后产生内存溢出异常。
        (2)方法区溢出
       方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

    异常信息:java.lang.OutOfMemoryError:PermGen space

    方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。


    SOF:StackOverflow(堆栈溢出
        当应用程序递归太深而发生堆栈溢出时,抛出该错误。因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
        栈溢出的原因:
        (1)递归调用
        (2)大量循环或死循环
        (3)全局变量是否过多
        (4)数组、List、Map数据过大

    (注:文章中图片来源于网络)


    展开全文
  • JAVA虚拟机(JVM)划重点 第二章 Java内存区域与内存溢出异常 之 虚拟机对象Java对象的创建 Java对象的创建 1、类加载过程 虚拟机遇到一个new指令时,首先检查这个指令的参数能否在常量池中定位到一个类的符号...
  • 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。 具体如下图 java 普通对象结构 java 数组对象结构 对象结构组成 对象头 ...
  • 【Java对象解析】不得不了解的对象

    万次阅读 多人点赞 2017-01-18 15:02:41
    HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。  HotSpot虚拟机的对象头(Object Header)包括两部分信息,第一部分用于存储对象...
  • Java虚拟机中对象内存的分配情况

    千次阅读 2019-02-20 17:50:16
      在前面的文章介绍了对象在虚拟机中的创建过程。本文主要是介绍下对象在虚拟机中的内存布局分配情况...  在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头,实例数据和对齐填充。 对象头 ...
  • 深入JVM内存区域

    千次阅读 2015-11-12 12:22:56
    JVM内存区域的划分和C/C++开发不同,在从事JAVA的开发过程中,我们对内存区域的关注相对较轻,但是了解和掌握JAVA的内存结构会帮助我们做出合理的优化决策。
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

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

    万次阅读 多人点赞 2017-04-19 22:31:57
    在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。下图是普通对象实例与数组对象实例的数据结构: 对象头 HotSpot虚拟机的...
  • JVM成神之路-Java对象模型

    千次阅读 2018-07-23 15:01:17
    首先我们要知道: 在jvm的内存结构中,对象保存在堆中,而我们在对对象进行操作时,其实操作的是对象的引用。...对象头(包含锁状态标志,线程持有的锁等标志) 实例数据 对齐填充 oop-klass model(...
  • HotSpot 虚拟机对象探秘

    万次阅读 2020-04-13 21:08:23
    在 HotSpot 虚拟机中,对象的内存布局分为以下 3 块区域: 对象头(Header) 实例数据(Instance Data) 对齐填充(Padding) 对象对象头记录了对象在运行过程中所需要使用的一些数据: 哈希码 GC 分代年龄 ...
  • 一个对象在内存中究竟是怎样进行布局...当该对象是一个数组时,对象头还会增加一块区域,用来保存数组的长度。以64位系统为例,对象头存储内容如下图所示: |-----------------------------------------------------
  • 目录 对象内存结构 没有继承的对象属性排布 有继承的对象属性排布 如何计算对象大小 创建一个含有premain()方法的Java 类。...将创建好的Java类打成一个jar包 ...当使用Class文件新建对象时,对象实例的...
  • 前一篇文章我们介绍了Java虚拟机的运行时数据区域之后...我们这个所说的是Sun的HotSpot虚拟机的Java堆内存区域,深入探讨HotSpot虚拟机在Java堆中对象的分配、布局和访问全过程。本文大纲: 一、 对象的创建 二、 对象
  • Java 对象占用内存大小

    千次阅读 2018-01-22 13:35:25
    HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域对象头(Header)、实例数据(Instance Data)和对齐填充(Padding) java 对象头 Mark Word HotSpot虚拟机的对象头(Object Header)包括两部分信息...
  • 深入理解对象池技术

    千次阅读 2017-05-17 16:07:28
    笔者介绍:姜雪伟,IT公司技术合伙人,IT...CSDN视频网址:http://edu.csdn.net/lecturer/144 对象池在游戏开发中使用的非常广泛,尤其对于内存的管理方面,很多人知道对象池但是不理解,本篇博客使用通俗的语言给读
  • JVM学习之对象内存布局,对象

    千次阅读 2018-10-08 15:54:29
    创建对象之后需要使用对象,java中除了对对象属性方法的调用以外还有加锁实现同步等其他操作,这里的锁加在了哪里,如何记录锁,如何对锁进行分类(有对象锁,class锁),垃圾回收机制中有关于GC的标记,知道当前...
  • HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。 对象头(Object Header)  JVM的对象头包括二/三部分信息:1、Mark ...
  • JVM系列之:对象的锁状态和同步

    万次阅读 2020-07-24 09:14:07
    当第二个线程进入同一个区域的时候,必须等待第一个线程解锁该对象。 JVM是怎么做到的呢?为了实现这个功能,java对象又需要具备什么样的结构呢?快来一起看看吧。 java对象头 Java的锁状态其实可以分
  • 初始化默认值以后,JVM要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象对象头Object Header之中。 这部分数据的...
  • java对象解析

    千次阅读 2021-04-21 21:40:19
    对象创建的过程 ...我们创建对象则去找列表中对应的空闲区域去创建我们的对象。 堆是否规整有我们垃圾回收器来决定的 ,如果垃圾回收带有我们的 压缩算法,那么他会规整的分配我们的对象 (2)规整分配线程.
  • 在前面文章了解到Java对象实例是如何在Java堆中创建的,下面我们详细了解Java普通对象创建后,在HotSpot虚拟机Java堆中的内存布局是怎样的,可以分为3个区域对象头(Header)、实例数据(Instance)和对齐填充...
  • Hotspot虚拟机的对象

    千次阅读 2019-04-16 19:04:14
    创建 Step1:类加载检查 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,...对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大...
  • Java内存区域

    千次阅读 多人点赞 2019-10-30 20:42:05
    概述 对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。...运行时数据区域 Jav...
  • 在本文将 ,我对 Java 对象创建、对象内存布局、对象访问定位的三个过程 进行了详细介绍,希望你们会喜欢
  • 认识JVM--第二篇-java对象内存模型

    千次阅读 2011-07-03 23:57:14
    前一段写了一篇《认识JVM》,不过在一些方面可以继续阐述的,在这里继续探讨一下,本文重点在于在heap区域内部对象之间的组织关系,以及各种粒度之间的关系,以及JVM常见优化方法,文章目录如下所示: 1、回顾--...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 86,844
精华内容 34,737
关键字:

区域标志的对象