什么是JVM
java虚拟机。JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。所以,JAVA虚拟机JVM是属于JRE的,而现在我们安装JDK时也附带安装了JRE(当然也可以单独安装JRE)。
GC
垃圾回收机制思想
@H_502_13@1、Java编写程序不需要考虑内存管理; 2、垃圾回收器作为守护线程在后台运行。在不可预知的情况下(在内存紧张时自动跳出来),对内存堆中死亡的对象或者长时间不运行的对象进行清除和回收,程序员不可以实时调用垃圾回收器进行垃圾回收。 3、可以手动使用System.gc()通知GC运行,但是Java的语言规范并不能保证GC一定会运行。
判断对象是否是垃圾的方法?/对象是否已经死去的方法?
@H_502_13@引用计数法:为创建的对象分配一个引用计数器,用来存储该对象引用的次数。当次数为0,可以认为该对象已经死亡。但该方法无法检测“循环引用”:当2个对象互相引用时,即使它俩都不被外界引用,它俩的计数器都不为0,因此永远不会被回收。实际上,他们已经无用了。 可达性分析算法:把所有对象想象成一个树,从树的根结点GC Roots出发,持续遍历找出所有连接的树枝对象,这些对象称为“可达”对象。其余的则是“不可达”对象。 哪些对象可以作为GC Roots呢? 1.虚拟机栈中引用的对象; 2.本地方法栈中引用的对象; 3.方法区中静态变量、常量引用的对象;
垃圾回收算法有哪些?
@H_502_13@复制算法:此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中;此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现 “碎片” 问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间; 两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法; ##存活对象少,垃圾多。 标记-清除:此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除;此算法需要暂停整个应用,同时,会产生内存碎片; 一块区域,标记可达对象(可达性分析),然后回收不可达对象,会出现碎片,那么引出; ##简单方便,但内存碎片多。 标记-整理:此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。 此算法避免了 “标记-清除” 的碎片问题,同时也避免了 “复制” 算法的空间问题。多了碎片整理,整理出更大的内存放更大的对象; ##存活对象多,垃圾少。 分代收集: 综上:新生代基本采用复制算法,老年代采用标记-整理算法。cms采用标记-清除算法。
常见的GC收集器:
@H_502_13@Serial New收集器 ##针对新生代,采用复制算法。client模式下首选新生代收集器 Serial Old(串行)收集器 ##新生代采用复制算法,老年代采用标记-整理算法。client模式下首选老年代收集器 Parallel New(并行)收集器 ##新生代采用复制算法,老年代采用标记-整理算法。server模式下首选的新生代收集器 Parallel Scavenge(并行)收集器 ##针对新生代,采用复制算法。更加关注吞吐量 Parallel Old(并行)收集器 ##针对老年代,标记-整理算法。Parallel Scavenge(并行)收集器的老年代版本 CMS收集器 ##基于标记-清除算法。获取最短停顿时间为目标,老年代收集器 G1收集器 ##整体上是基于标记-整理算法 ,局部采用复制算法。更加关注停顿时间
如何GC?
@H_502_13@Minor GC:Minor GC是发生在新生代中的垃圾收集动作,采用的是复制算法。 在Eden区申请空间失败时就会MinorGC,对Eden区GC来清除非存活对象,同时把存活对象复制到存活区中的一个s0,对象经过MinorGC才能进入到存活区,MinorGC只会发生在新生代,会非常频繁地执行;又触发了一次Minor GC后,Eden和s0中存活的对象被复制到s1中,然后Eden和s0被清空。同一时刻,只有Eden区和一个存活区同时被操作。当每次从Eden复制到一个存活区或者从一个存活区复制到另一个存活区时,由于虚拟机给每个对象定义一个年龄计数器,计数器自动增加,默认情况下如果复制发生超过16次,jvm会停止复制并把们移动到老年代中。 Full GC:Full GC是发生在老年代的垃圾收集动作,采用的是标记-清除-压缩算法。如果年老代空间满了,就会触发Full GC。Full GC对整个块进行回收包括年轻代、年老代和永久代。Full GC是一个压缩处理过程,所以它比Minor GC要慢得多。发生 Full GC 一般都会有一次 Minor GC。 CMS收集器中,当老年代满时会触发 Major GC。 目前,只有CMS收集器会有单独收集老年代的行为。其他收集器均无此行为。 针对新生代(主要指Eden区)的Minor GC 比较常见,各个收集器均支持。 通常能单独发生收集行为的只是新生代的Minor GC,所以这里“反过来”的情况只是理论上允许,实际上除了CMS收集器,其他都不存在只针对老年代的收集。 Java的垃圾回收器GC主要针对堆区。方法调用时,会创建栈帧在栈中,调用完是程序自动出栈释放,而不是gc释放。java虚拟机,对于方法的调用采用的是栈帧(方法调用和方法执行),调用则入栈,完成之后则出栈。不就回收了内存资源。而针对于其他,GC回收的时间不定。 java的垃圾收集机制主要针对新生代和老年代的内存进行回收,不同的垃圾收集算法针对不同的区域。所以java的垃圾收集算法使用的是分代回收。 一般java的对象首先进入新生代的Eden区域,当进行GC的时候会回收新生代的区域,新生代一般采用复制收集算法,将活着的对象复制到survivor区域中,如果survivor区域装在不下,就查看老年代是否有足够的空间装下新生代中的对象,如果能装下就装下,否则老年代就执行FULL GC回收自己,老年代还是装不下,就会抛出OUtOfMemory的异常。
能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?
@H_502_13@在什么时候: 1.新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。 2.大对象以及长期存活的对象直接进入老年区。 3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。 对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。 做什么: 新生代:复制清理; 老年代:标记-清除和标记-压缩算法; 永久代:存放Java中的类和加载类的类加载器本身。
引用的分类?
@H_502_13@强引用:只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显式的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。 Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收 obj = null; //手动置null 软引用:用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。 弱引用:弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。 虚引用:最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。 在 JDK1.2 之后,用 Phantomreference类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个get()方法,而且它的get()方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。 引用队列(ReferenceQueue):引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。 与软引用、弱引用不同,虚引用必须和引用队列一起使用。
内存管理
JVM由哪些部分组成?
@H_502_13@类加载器,在 JVM 启动时或者类运行时将需要的 class 加载到 JVM 中; 执行引擎,执行引擎的任务是负责执行 class 文件中包含的字节码指令,相当于实际机器上的 cpu; 内存区,将内存划分成若干个区以模拟实际机器上的存储、记录和调度功能模块,如实际机器上的各种功能的寄存器或者 PC 指针的记录器等; 本地方法调用,调用 C 或 C++ 实现的本地方法的代码返回结果;
JVM内存模型:描述的是类被加载时,经过解析后,存储到特定的数据区。
@H_502_13@1.线程共享: 1.堆:内存中最大一块,存放对象实例以及数组。垃圾收集器管理的主战场,分新生代和老年代。内存空间逻辑上连续,物理上可不连续。 作用:jvm启动时创建,用来维护运行时数据。 2.方法区:又称永久代(PermGen),用来存储类的信息(方法,方法名,返回值)、常量、静态变量、即时编译器生成的代码。 JVM规范把方法区称为堆的一个逻辑部分,但方法区还有一个别名叫非堆,目的是将其与堆区分开来。 运行时常量池:方法区的一部分, 存放编译期生成的各种字面量和符号引用。 JDK1.8改进:方法区中永久代向元空间的转换。 >为什么要做这个转换? 1、字符串存在永久代中,容易出现性能问题和内存溢出。 2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。 3、永久代会为GC带来不必要的复杂度,并且回收效率偏低。 >解析: 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间较大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小: -XX:MetaspaceSize ##初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整: 如果释放了大量的空间,就适当降低该值; 如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。 -XX:MaxMetaspaceSize ##最大空间,默认是没有限制的。 除了上面两个指定大小的选项以外,还有两个与GC相关的属性: -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集; -XX:MaxMetaspaceFreeRatio,在GC之后,较大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集; 2.线程隔离: 1.程序计数器:当前线程执行字节码的行号指示器,每个线程都有自己的程序计数器;如果正在执行Java方法,则记录的是正在执行的字节码指令的地址;如果是native方法,则计数器为空。这是jvm中唯一没有规定任何outofMemoryError情况的区域。 2.虚拟机栈:生命周期同线程,描述的是Java方法执行的内存模型。方法从执行到结束对应着栈帧进栈出栈的过程,线程结束内存自动释放; 3.本地方法栈:描述的是虚拟机用到的native方法出栈和入栈的过程(通常我们不需要了解这块,它底层是C语言实现的,);
为什么分为线程共享和非线程共享呢
@H_502_13@首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢? 概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器), VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因。 非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说只发生在堆上)的原因。
堆的分区
@H_502_13@新生代:采用复制算法回收内存; HotSpot虚拟机默认Eden和Survivor的大小比例是8:1; Eden区:内存分配的地方,是一个连续的空闲内存区域;新对象总是在Eden区生成,只有经受住了Minor GC,才能顺利进入存活区。 存活区:2个(s0和s1),交替使用,在下一次垃圾回收时,之前被清除的存活区又用来放置存活下来的对象。 老年代:在新生代中经历了N次(默认16)回收后仍然没有被清除的对象,就会被复制到老年代中,都是生命周期较长的对象。如果对象不能在Eden区中创建,它也会直接在年老代中创建。 设置-XX:PretenureSizeThreshold:参数(通常3MB) ##当大于该值时,对象直接在老年代中创建 永久代:即方法区。存储的是常量,常量池,静态变量。 >JVM的永久代中会发生垃圾回收么? 参考答案 永久代会垃圾回收,但是没有自己的垃圾收集器,是和老年代一起回收的。 永久代也是可以回收的,条件是: 1.该类的实例都被回收; 2.加载该类的classLoader已经被回收; 3.该类不能通过反射访问到其方法而且该类的java.lang.class没有被引用; 当满足这3个条件时,是可以回收,但回不回收还得看jvm。 “相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的。”
Java内存模型
@H_502_13@1、所有变量存储在主内存上; 2、每条线程有自己的工作内存; 3、线程的工作内存保存了需要使用的变量拷贝,这是从主内存中拷贝而来的; 4、线程对变量的所有操作在工作内存中进行,不能直接操作主内存的变量; 5、不同线程不能直接访问对方工作内存的变量; 6、线程间变量的值传递需要经过主内存。 问题:2个线程如何通信? 当存在公共状态时:2个线程之间的通信通过共享对象来进行。 当不存在时:必须通过明确的发送信息来显式通信。 例如:使用wait()和notify()方法。
内存泄露:
@H_502_13@含义:程序申请内存空间后,无法释放内存空间;内存泄漏堆积起来最后会消耗完内存; 分类: 1.常发性:多次; 2.偶发性:特定情况下; 3.一次性:一次; 3.隐式:不停地分配; 原因: 1.静态集合类引起内存泄漏;像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。 2.当集合里面的对象属性被修改后,再调用remove()方法时不起作用。 3.各种连接;比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。 4.监听器。在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。 5.单例模式;如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露。 或者:长生命周期对象引用短生命周期对象;没有将无用对象设置为null。 解决方案: 1.修改JVM启动参数,直接增加内存。(-xms,-Xmx参数一定不要忘记加。) 2.检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。 3.对代码进行走查和分析,找出可能发生内存溢出的位置。
内存溢出
@H_502_13@含义:程序申请内存时,没有足够的空间供其使用,出现out of memory; 关于下面哪种情况会导致持久区jvm堆内存溢出?https://www.Nowcoder.com/test/question/done?tid=16720950&qid=14849#summary
JVM启动参数
@H_502_13@1.堆内存分配: -xms? ##设置堆的最小值,默认是物理内存的 1/64; -Xmx ? ##设置堆的最大值,默认是物理内存的 1/4; 默认空余堆内存小于 40% 时,JVM 就会增大堆直到-Xmx 的最大限制;空余堆内存大于 70% 时,JVM 会减少堆直到 -xms 的最小限制; 因此服务器一般设置-xms、-Xmx 相等以避免在每次 GC 后调整堆的大小。 2.非堆内存分配: -XX:PermSize=? ##设置永久代初始值,默认是物理内存的 1/64; -XX:MaxPermSize=? ##设置永久代最大值,默认是物理内存的 1/4; -Xmn2G ##设置新生代大小为 2G; -XX:SurvivorRatio=? ##设置年轻代中 Eden 区与 Survivor 区的比值。
内存分配规则
@H_502_13@1.对象优先分配在Eden区,如果没有足够空间,将执行一次Minor GC; 2.大对象直接进入年老区,目的是避免在Eden区和2个存活区之间发生大量的内存拷贝; 3.长期存活的对象进入年老代。JVM为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC,那么对象就会进入Survivor区。 之后每经过一次Minor GC,那么对象的年龄加1,直到达到阈值后对象进入老年区。 4.动态判断对象年龄;如果存活区中相同年龄的所有对象大小总和大于存活区空间的一半,那么年龄大于或者等于该年龄的对象可以直接进入年老区; 5.空间分配担保;每次进行Minor GC时,JVM会计算存活区移动到年老区对象的平均大小,如果这个值大于老年区的剩余空间大小则进行一次Full GC; 如果小于,则检查 HandlePromotionFailure 设置,如果为true,则只进行Monitor GC,如果是false,则进行Full GC。
在如下几种情况下,Java虚拟机将结束生命周期
@H_502_13@执行了 System.exit()方法; 程序正常执行结束; 程序在执行过程中遇到了异常或错误而异常终止; 由于操作系统出现错误而导致Java虚拟机进程终止;
类加载器
类加载器的概念?/底层原理的考察,其中涉及到类加载器的概念,功能以及一些底层的实现。
@H_502_13@类加载器用来加载Java字节码到Java虚拟机中。 Java虚拟机使用java类的方式如下:Java 源程序(.java 文件)在经过 Java编译器编译之后就被转换成 Java字节代码(.class文件)。 类加载器负责读取Java字节代码,并转换成 java.lang.class 类的一个实例。每个这样的实例用来表示一个java类。 通过此实例的newInstance()方法就可以创建出该类的一个对象。 实际的情况可能更加复杂,比如Java字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
如何判定两个 Java 类是相同的
@H_502_13@1、类的全名是否一致 2、加载此类的类加载器是否一致
类加载器加载 class 文件的过程?
@H_502_13@1、加载:把.class文件加载到内存; 2、连接:分3步。字节码验证,类数据结构分析及内存分配,符号表的链接; 3、初始化; 4、使用; 5、卸载;GC垃圾回收。
JDK中提供了3个类加载器,根据层级从高到低为
@H_502_13@Bootstrap ClassLoader,主要加载JVM自身工作需要的类。 ##加载系统类(即内置类如String)。 Extension ClassLoader,主要加载%JAVA_HOME%\lib\ext目录下的库类。 ##加载扩展类(即继承类和实现类)。 Application ClassLoader,主要加载Classpath指定的库类,一般情况下这是程序中的默认类加载器,也是ClassLoader.getSystemClassLoader()的返回值。 (这里的Classpath默认指的是环境变量中配置的Classpath,但是可以在执行Java命令的时候使用-cp 参数来修改当前程序使用的Classpath) ##加载应用类(程序员自定义的类)
JVM加载类的实现方式,我们称为 双亲委托模型
@H_502_13@双亲委派模型的工作过程? 如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层的类加载器都是如此, 因此所有的类加载请求最终都应该传送到顶层的Bootstrap ClassLoader中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。 为什么这样设计呢? 1、为了安全。避免自定义的类替换掉Java的核心类; eg:假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JV M会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它。 2、避免重复加载。区分不同类,不仅仅看类名,还要看类加载器。
类加载器基于3个机制
@H_502_13@1、委托性:双亲委派模型。 2、可见性:子类加载器可以看到所有父类加载器加载的类,但是反过来不能。 3、单一性:一次就加载一个类,确保类不会被重复加载。
内存屏障
内存屏障为何重要?
@H_502_13@ 对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序。也就是说,程序的读写操作不一定会按照它要求处理器的顺序执行。当数据是不可变的,同时/或者数据限制在线程范围内,这些优化是无害的。如果把 这些优化与对称多处理(symmetric multi-processing)和共享可变状态(shared mutable state)结合,那么就是一场噩梦。 当基于共享可变状态的内存操作被重新排序时,程序可能行为不定。一个线程写入的数据可能被其他线程可见,原因是数据写入的顺序不一致。适当的放置内存屏障通过强制处理器顺序执行待定的内存操作来避免这个问题。
Java创建一个对象的过程
@H_502_13@ 1.检测类是否被加载: 当虚拟机执行到new时,会先去常量池中查找这个类的符号引用。如果能找到符号引用,说明此类已经被加载到方法区(方法区存储虚拟机已经加载的类的信息),可以继续执行;如果找不到符号引用,就会使用类加载器执行类的加载过程,类加载完成后继续执行。 2.为对象分配内存: 类加载完成以后,虚拟机就开始为对象分配内存,此时所需内存的大小就已经确定了。只需要在堆上分配所需要的内存即可。 具体的分配内存有两种情况:第一种情况是内存空间绝对规整,第二种情况是内存空间是不连续的。 对于内存绝对规整的情况相对简单一些,虚拟机只需要在被占用的内存和可用空间之间移动指针即可,这种方式被称为指针碰撞。 对于内存不规整的情况稍微复杂一点,这时候虚拟机需要维护一个列表,来记录哪些内存是可用的。分配内存的时候需要找到一个可用的内存空间,然后在列表上记录下已被分配,这种方式成为空闲列表。 分配内存的时候也需要考虑线程安全问题,有两种解决方案: 第一种是采用同步的办法,使用CAS来保证操作的原子性。 另一种是每个线程分配内存都在自己的空间内进行,即是每个线程都在堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB),分配内存的时候再TLAB上分配,互不干扰。 3.为分配的内存空间初始化零值: 对象的内存分配完成后,还需要将对象的内存空间都初始化为零值,这样能保证对象即使没有赋初值,也可以直接使用 4.对对象进行其他设置: 分配完内存空间,初始化零值之后,虚拟机还需要对对象进行其他必要的设置,设置的地方都在对象头中,包括这个对象所属的类,类的元数据信息,对象的hashcode,GC分代年龄等信息。 5.执行 init 方法: 执行完上面的步骤之后,在虚拟机里这个对象就算创建成功了,但是对于Java程序来说还需要执行init方法才算真正的创建完成,因为这个时候对象只是被初始化零值了,还没有真正的去根据程序中的代码分配初始值,调用了init方法之后,这个对象才真正能使用。 到此为止一个对象就产生了,这就是new关键字创建对象的过程。
对象的内存布局是怎样的?3
@H_502_13@对象头:第一部分用于存储Mark Word,即对象自身的运行时数据。 另外一部分是类型指针,即对象指向它的类元数据的指针。 实例数据:是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。 对齐填充:HotSpot要求对象大小为8字节的整数倍,因此通过对齐填充来补全对齐。
对象是如何定位访问的?有两种:句柄定位和直接指针
@H_502_13@句柄定位:Java 堆会画出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; 直接指针访问:java堆对象的不居中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址; 比较:使用直接指针就是速度快,使用句柄reference指向稳定的句柄,对象被移动改变的也只是句柄中实例数据的指针,而reference本身并不需要修改。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。