区块链技术博客
www.b2bchain.cn

05-对象的创建过程与内存分配求职学习资料

本文介绍了05-对象的创建过程与内存分配求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

  • 一. 内存分配方式
    • 1. 指针碰撞
    • 2. 空闲列表
  • 二. 并发安全问题
    • 1. CAS
    • 2. TLAB
  • 三. 堆是分配对象存储的唯一选择吗?
    • 1. 逃逸分析

   
    我们都知道Java是一门面向对象的编程语言,当我们的类被加载到内存中后,程序执行到我们的 new 关键字的时候,就会去创建一个对象,而在虚拟机中,对象的创建过程又是怎么样的呢?当虚拟机遇到一条字节码new指令时,便开始执行创建的对象的流程:

05-对象的创建过程与内存分配

1)类加载检查:检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且该符号引用代表的类必须已被加载解析和初始化过,否则就得执行类加载过程。
2)分配内存:虚拟机将为新生对象分配内存,在堆中开辟出一块内存空间。
3)初始化:虚拟机将分配好的内存空间都初始化为零值(不包括对象头)。
4)设置对象头:如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,将这些信息存放在对象的对象头中。
5)执行init方法:执行方法,即对象按照程序员的意愿进行初始化,为零值的字段设置好编码时的值。

    当执行完init方法后,才算是一个真正可用的对象完全被构造出来。现在我们大致了解了整个创建对象的过程,在类加载检查通过后,则由虚拟机为新生对象分配内存,对象所需内存的大小在类加载完成之后便可完全确定。分配内存的过程实际上就是将Java堆中划分出一块固定大小区域的内存。在这个过程中,虚拟机它是如何进行内存的划分的呢?

一. 内存分配方式

    在hotspot虚拟机中,分配内存的方式有两种:1.指针碰撞;2.空闲列表。

1. 指针碰撞

    指针碰撞: 如果Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那分配内存就只需要把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump The Pointer)。

05-对象的创建过程与内存分配

2. 空闲列表

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

    选择使用哪种方式进行内存分配则由Java堆是否规整决定,而堆是否规整则又由所采用的的垃圾收集器的能力决定(是否会进行空间压缩),因此,当使用Serial、PparNew等带压缩整理过程的收集器时,采用指针碰撞,分配简单且高效;而当使用CMS这种基于清除算法的收集器时,则需要采用更为复杂的空闲列表来分配内存。
    当选择了内存分配方式后,似乎能够正常为对象分配内存了,如果这个时候,两个线程都在进行对象的创建,那么就会出现对同一个指针右移的情况或者对同一块内存区域进行分配的情况,如指针碰撞,A线程给a对象分配内存,指针还没来得及修改,此时B线程也给b对象分配内存,那么b对象就使用了a对象分配的内存空间,这样就出现了线程安全问题。

二. 并发安全问题

    为了解决在多线程情况下出现的内存分配并发安全问题,jvm提供了两种可选方案:1.CAS;2.TLAB

1. CAS

    CAS(compare and swap):对分配内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证内存分配操作的原子性。即,当某个线程进行内存分配时,会先确定这块内存是否已经有其它线程在进行分配,如果有,则不断进行尝试,直到确认某块内存没有线程进行分配。

2. TLAB

    TLAB(Thread Local Allocation Buffer):本地线程分配缓冲,每个线程会在虚拟机堆中预先分配一小块内存,当哪个线程需要创建对象时,就在哪个线程的本地缓冲区中进行分配,只有当这块内存区域用完后,分配新的缓存区时才需要进行同步锁定。这样就避免多个线程操作同一地址时,需要使用加锁等机制,从而影响分配速度

   虚拟机1.8默认使用的是 TLAB 方式来进行内存分配的,如果想要使用CAS方式,可以通过设置 -XX:-UseTLAB 参数来关闭TLAB功能即可。默认情况下,TLAB 空间的内存非常小,仅占有整个 Eden 空间的 1%,我们可以通过 -XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。如果通过TLAB分配失败的时候,则会回到Eden区通过 CAS 方式进行分配。

三. 堆是分配对象存储的唯一选择吗?

    我们在使用对象的时候,很多对象都只是在一个方法中进行使用,比如StringBuffer,而当这个方法调用完成后,这些对象就成为了垃圾而留存在内存中,只能等待垃圾回收的时候再清理这部分内存,这些对象的使用就加快了垃圾回收的频次,如果大量的使用对系统的性能是非常有影响的。
    而随着 JIT 编译期的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换等技术给jvm内存分配方式带来了极大的优化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
    堆已经不是分配对象存储的唯一选择,在java中,通过逃逸分析,编译器能够分析出一个新的对象的引用的使用范围来使对象通过标量替换的形式在栈上进行存储。

1. 逃逸分析

    逃逸分析(Escape Analysis)是目前 Java 虚拟机中比较前沿的优化技术。这是一种可以有效减少 Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。

逃逸分析的基本行为就是分析对象动态作用域:
   当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
   当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中,称为方法逃逸。

    看下面的代码,strb 是方法 concatString的内部变量,这个方法将strb返回出去,strb可能在其它的方法中发生调用和更改,因此strb的作用域就不只是在当前方法,即它逃逸到了方法外部(方法逃逸),甚至被其它线程进行调用(线程逃逸)。

public class EscapeAnalysisDemo {      public StringBuffer concatString(String str1, String str2) {         StringBuffer strb = new StringBuffer();         strb.append(str1);         strb.append(str2);         return strb;     } }

    如果不想 strb 逃逸出方法,则可以进行如下修改,不返回 StringBuffer,而是返回 String
“`java

  • 一. 内存分配方式
    • 1. 指针碰撞
    • 2. 空闲列表
  • 二. 并发安全问题
    • 1. CAS
    • 2. TLAB
  • 三. 堆是分配对象存储的唯一选择吗?
    • 1. 逃逸分析

   
    我们都知道Java是一门面向对象的编程语言,当我们的类被加载到内存中后,程序执行到我们的 new 关键字的时候,就会去创建一个对象,而在虚拟机中,对象的创建过程又是怎么样的呢?当虚拟机遇到一条字节码new指令时,便开始执行创建的对象的流程:

05-对象的创建过程与内存分配

1)类加载检查:检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且该符号引用代表的类必须已被加载解析和初始化过,否则就得执行类加载过程。
2)分配内存:虚拟机将为新生对象分配内存,在堆中开辟出一块内存空间。
3)初始化:虚拟机将分配好的内存空间都初始化为零值(不包括对象头)。
4)设置对象头:如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,将这些信息存放在对象的对象头中。
5)执行init方法:执行方法,即对象按照程序员的意愿进行初始化,为零值的字段设置好编码时的值。

    当执行完init方法后,才算是一个真正可用的对象完全被构造出来。现在我们大致了解了整个创建对象的过程,在类加载检查通过后,则由虚拟机为新生对象分配内存,对象所需内存的大小在类加载完成之后便可完全确定。分配内存的过程实际上就是将Java堆中划分出一块固定大小区域的内存。在这个过程中,虚拟机它是如何进行内存的划分的呢?

一. 内存分配方式

    在hotspot虚拟机中,分配内存的方式有两种:1.指针碰撞;2.空闲列表。

1. 指针碰撞

    指针碰撞: 如果Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那分配内存就只需要把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump The Pointer)。

05-对象的创建过程与内存分配

2. 空闲列表

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

    选择使用哪种方式进行内存分配则由Java堆是否规整决定,而堆是否规整则又由所采用的的垃圾收集器的能力决定(是否会进行空间压缩),因此,当使用Serial、PparNew等带压缩整理过程的收集器时,采用指针碰撞,分配简单且高效;而当使用CMS这种基于清除算法的收集器时,则需要采用更为复杂的空闲列表来分配内存。
    当选择了内存分配方式后,似乎能够正常为对象分配内存了,如果这个时候,两个线程都在进行对象的创建,那么就会出现对同一个指针右移的情况或者对同一块内存区域进行分配的情况,如指针碰撞,A线程给a对象分配内存,指针还没来得及修改,此时B线程也给b对象分配内存,那么b对象就使用了a对象分配的内存空间,这样就出现了线程安全问题。

二. 并发安全问题

    为了解决在多线程情况下出现的内存分配并发安全问题,jvm提供了两种可选方案:1.CAS;2.TLAB

1. CAS

    CAS(compare and swap):对分配内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证内存分配操作的原子性。即,当某个线程进行内存分配时,会先确定这块内存是否已经有其它线程在进行分配,如果有,则不断进行尝试,直到确认某块内存没有线程进行分配。

2. TLAB

    TLAB(Thread Local Allocation Buffer):本地线程分配缓冲,每个线程会在虚拟机堆中预先分配一小块内存,当哪个线程需要创建对象时,就在哪个线程的本地缓冲区中进行分配,只有当这块内存区域用完后,分配新的缓存区时才需要进行同步锁定。这样就避免多个线程操作同一地址时,需要使用加锁等机制,从而影响分配速度

   虚拟机1.8默认使用的是 TLAB 方式来进行内存分配的,如果想要使用CAS方式,可以通过设置 -XX:-UseTLAB 参数来关闭TLAB功能即可。默认情况下,TLAB 空间的内存非常小,仅占有整个 Eden 空间的 1%,我们可以通过 -XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。如果通过TLAB分配失败的时候,则会回到Eden区通过 CAS 方式进行分配。

三. 堆是分配对象存储的唯一选择吗?

    我们在使用对象的时候,很多对象都只是在一个方法中进行使用,比如StringBuffer,而当这个方法调用完成后,这些对象就成为了垃圾而留存在内存中,只能等待垃圾回收的时候再清理这部分内存,这些对象的使用就加快了垃圾回收的频次,如果大量的使用对系统的性能是非常有影响的。
    而随着 JIT 编译期的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换等技术给jvm内存分配方式带来了极大的优化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
    堆已经不是分配对象存储的唯一选择,在java中,通过逃逸分析,编译器能够分析出一个新的对象的引用的使用范围来使对象通过标量替换的形式在栈上进行存储。

1. 逃逸分析

    逃逸分析(Escape Analysis)是目前 Java 虚拟机中比较前沿的优化技术。这是一种可以有效减少 Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。

逃逸分析的基本行为就是分析对象动态作用域:
   当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
   当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中,称为方法逃逸。

    看下面的代码,strb 是方法 concatString的内部变量,这个方法将strb返回出去,strb可能在其它的方法中发生调用和更改,因此strb的作用域就不只是在当前方法,即它逃逸到了方法外部(方法逃逸),甚至被其它线程进行调用(线程逃逸)。

public class EscapeAnalysisDemo {      public StringBuffer concatString(String str1, String str2) {         StringBuffer strb = new StringBuffer();         strb.append(str1);         strb.append(str2);         return strb;     } }

    如果不想 strb 逃逸出方法,则可以进行如下修改,不返回 StringBuffer,而是返回 String
“`java

  • 一. 内存分配方式
    • 1. 指针碰撞
    • 2. 空闲列表
  • 二. 并发安全问题
    • 1. CAS
    • 2. TLAB
  • 三. 堆是分配对象存储的唯一选择吗?
    • 1. 逃逸分析

   
    我们都知道Java是一门面向对象的编程语言,当我们的类被加载到内存中后,程序执行到我们的 new 关键字的时候,就会去创建一个对象,而在虚拟机中,对象的创建过程又是怎么样的呢?当虚拟机遇到一条字节码new指令时,便开始执行创建的对象的流程:

05-对象的创建过程与内存分配

1)类加载检查:检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且该符号引用代表的类必须已被加载解析和初始化过,否则就得执行类加载过程。
2)分配内存:虚拟机将为新生对象分配内存,在堆中开辟出一块内存空间。
3)初始化:虚拟机将分配好的内存空间都初始化为零值(不包括对象头)。
4)设置对象头:如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,将这些信息存放在对象的对象头中。
5)执行init方法:执行方法,即对象按照程序员的意愿进行初始化,为零值的字段设置好编码时的值。

    当执行完init方法后,才算是一个真正可用的对象完全被构造出来。现在我们大致了解了整个创建对象的过程,在类加载检查通过后,则由虚拟机为新生对象分配内存,对象所需内存的大小在类加载完成之后便可完全确定。分配内存的过程实际上就是将Java堆中划分出一块固定大小区域的内存。在这个过程中,虚拟机它是如何进行内存的划分的呢?

一. 内存分配方式

    在hotspot虚拟机中,分配内存的方式有两种:1.指针碰撞;2.空闲列表。

1. 指针碰撞

    指针碰撞: 如果Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那分配内存就只需要把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump The Pointer)。

05-对象的创建过程与内存分配

2. 空闲列表

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

    选择使用哪种方式进行内存分配则由Java堆是否规整决定,而堆是否规整则又由所采用的的垃圾收集器的能力决定(是否会进行空间压缩),因此,当使用Serial、PparNew等带压缩整理过程的收集器时,采用指针碰撞,分配简单且高效;而当使用CMS这种基于清除算法的收集器时,则需要采用更为复杂的空闲列表来分配内存。
    当选择了内存分配方式后,似乎能够正常为对象分配内存了,如果这个时候,两个线程都在进行对象的创建,那么就会出现对同一个指针右移的情况或者对同一块内存区域进行分配的情况,如指针碰撞,A线程给a对象分配内存,指针还没来得及修改,此时B线程也给b对象分配内存,那么b对象就使用了a对象分配的内存空间,这样就出现了线程安全问题。

二. 并发安全问题

    为了解决在多线程情况下出现的内存分配并发安全问题,jvm提供了两种可选方案:1.CAS;2.TLAB

1. CAS

    CAS(compare and swap):对分配内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证内存分配操作的原子性。即,当某个线程进行内存分配时,会先确定这块内存是否已经有其它线程在进行分配,如果有,则不断进行尝试,直到确认某块内存没有线程进行分配。

2. TLAB

    TLAB(Thread Local Allocation Buffer):本地线程分配缓冲,每个线程会在虚拟机堆中预先分配一小块内存,当哪个线程需要创建对象时,就在哪个线程的本地缓冲区中进行分配,只有当这块内存区域用完后,分配新的缓存区时才需要进行同步锁定。这样就避免多个线程操作同一地址时,需要使用加锁等机制,从而影响分配速度

   虚拟机1.8默认使用的是 TLAB 方式来进行内存分配的,如果想要使用CAS方式,可以通过设置 -XX:-UseTLAB 参数来关闭TLAB功能即可。默认情况下,TLAB 空间的内存非常小,仅占有整个 Eden 空间的 1%,我们可以通过 -XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。如果通过TLAB分配失败的时候,则会回到Eden区通过 CAS 方式进行分配。

三. 堆是分配对象存储的唯一选择吗?

    我们在使用对象的时候,很多对象都只是在一个方法中进行使用,比如StringBuffer,而当这个方法调用完成后,这些对象就成为了垃圾而留存在内存中,只能等待垃圾回收的时候再清理这部分内存,这些对象的使用就加快了垃圾回收的频次,如果大量的使用对系统的性能是非常有影响的。
    而随着 JIT 编译期的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换等技术给jvm内存分配方式带来了极大的优化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
    堆已经不是分配对象存储的唯一选择,在java中,通过逃逸分析,编译器能够分析出一个新的对象的引用的使用范围来使对象通过标量替换的形式在栈上进行存储。

1. 逃逸分析

    逃逸分析(Escape Analysis)是目前 Java 虚拟机中比较前沿的优化技术。这是一种可以有效减少 Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。

逃逸分析的基本行为就是分析对象动态作用域:
   当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
   当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中,称为方法逃逸。

    看下面的代码,strb 是方法 concatString的内部变量,这个方法将strb返回出去,strb可能在其它的方法中发生调用和更改,因此strb的作用域就不只是在当前方法,即它逃逸到了方法外部(方法逃逸),甚至被其它线程进行调用(线程逃逸)。

public class EscapeAnalysisDemo {      public StringBuffer concatString(String str1, String str2) {         StringBuffer strb = new StringBuffer();         strb.append(str1);         strb.append(str2);         return strb;     } }

    如果不想 strb 逃逸出方法,则可以进行如下修改,不返回 StringBuffer,而是返回 String
“`java

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 05-对象的创建过程与内存分配求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们