通过Reentrantlock解释AQS源代码
AQ的核心是其共享的INT类型值称为状态。该状态使用什么?实际上,这主要取决于如何实施他的子类。例如,什么是重新进入?以这种状态记录该线程已重新输入多少次。例如,如果线程得出状态,状态的值已锁定,状态的价值已从0变为1.2,它将再次变为3,什么时候将发布?被发布。这是AQ的核心。一个数字。该数字代表了如何实现子类的方式。线程节点当然是节点是节点。每个节点都包含一个线程。我们称其为线程节点。如此多的线程节点可以为此状态而战。
首先中断lock()方法,然后运行程序,
在lock()方法中,我们可以读取它致电Sync.Acquire(1),并且
然后遵循获取(1),您可以看到我们在获取(1)中写的tryAcquire(arg)
跟进到TryAcquire(ARG)并称为Nonfairtrytacquire(获取)
nonfairtacquire(获取)阅读时,您会发现它在状态下被调用。在nonfairtrytacquire(获取)中就是这种情况。首先,获取当前线程,获取状态的值,然后判断状态的价值是否为0 it表示没有人锁定,没有一个锁定?此锁的操作用于CAS的操作(比较setstate)。在他的值为1之后,状态的值设置为1,设置当前线程,并设置当前线程。它是唯一的,唯一的锁定线程,否则如果当前线程已拥有此锁锁?在原始基础上添加1个非常简单,以便我们可以获取此锁,我们将重新进入后者。
我们跟进了TryAcquire(ARG)以在此锁后进行此操作。如果您无法获得它?如果无法获得它,则实际上称为cessiveical()方法,并且在consectical method.clusive.clcusive,arg)中调用addWaiter(node.exclusive)。收购方法的方法是猜测这在做什么。如果您得到此锁,请考虑以后不跑步而不运行。这是什么意思?排队时,排队时需要传递两个参数。第一个参数是特定方法的返回值。AddWaiter(Node.Exclusive)。查看此方法AddWaiter,服务员服务员的名称,AddWaiter添加了服务员。使用什么样的方式?node.clusive链接到他,这意味着线程被扔进队列中的队列。
让我们谈谈这个addWaiter()方法。此方法意味着添加服务员时的使用类型。如果该线程是节点。排定性,那是他的锁。在场,然后添加等待主义队列的线程的节点,然后添加一个死周期,这意味着如果我不这样做,我会惊叹不已做到。那怎么做?
如果您考虑一下,我们会考虑AQS数据结构图,也就是说,他具有称为状态的int类型,然后在状态下排列了一个队列。该队列是带有头和尾巴的两条链接列表。现在,您希望您想要一个节点到该队列,排队。如果我们考虑其他节点,应该添加到队列的末端?它是如何做的?首先将尾巴记录在Oldtail中,而Oldtail指向此尾巴。如果Oldtail并不意味着空,它将将我们的新节点的前节点设置为该队列的末端,然后再次使用CAS操作。节点设置为尾部。整个代码似乎很麻烦。实际上,这很简单。它要添加当前要添加到服务员队列的线程的节点。如果您理解这一点,您就会理解为什么AQ是高效率。然后,我们讨论源代码。这增加了线程节点操作。如果它不成功,它将继续尝试。尝试到线程队列结束之前,这意味着还将其他节点添加到线程队列的末尾。我只不过是等待您的其他线程添加到最后。我将最后一个添加到最后一个Oneno,无论如何,我必须添加到线程的末端。
在这里,我们可以总结源代码。AQS的核心(Abstractqueueedsynchronizer)是使用CAS(CompareAreAndset)操作头部和尾部,这意味着锁定双向列表的操作被CAS操作替换。
如何通过AQ设置链接列表尾巴以了解为什么AQ如此高
如果您想将尾巴添加到链接列表中,尤其是许多线程必须在链接列表中添加尾巴。让我们考虑如何做普通方法?第一个是肯定的,因为多线程,您需要确保线程的安全性。通常,我们将锁定整个链接列表(同步)。在我们的新线程到来之后,它将被添加到尾巴上,并将其添加到尾巴上。这是正常的,但是如果我们锁定整个链接的列表,则锁定太多了。现在,这不是锁定整个链接列表的方法,而是观察尾部节点的方法。怎么办?比较eaneandaettail(oldtail,node),oldtail是其预期价值。如果我们要将当前线程设置为整个链接列表的尾巴,则会出现另一个线程,它插入节点,然后考虑节点oldtail = lighttail;整个Oldtail等同于整个新尾巴?它不等,因此,由于它不相等,这意味着中线被其他线程中断。如果它仍然是原始的旧尾巴,则意味着目前没有中断的线CompareAndaettail(Oldtail,Node)方法被用作新的尾巴,因此CAS的使用不需要锁定原始的链接列表。这也是AQS相对较高的效率。
为什么是两个路链接列表?
实际上,当您必须添加线程节点时,您需要查看上一个节点的状态。如果先前的节点在持有线程的过程中,则此时必须等待。如果以前的节点已被取消,它已被取消。然后,您应该跨越该节点,并且不考虑其状态,因此您需要查看上一个节点的状态,必须是两个路。
接下来,让我们解释Acquirequeump()的方法。此方法意味着您尝试将锁定在队列中并在队列中排队以获取锁。那么它如何做?让我们先采用此方法,首先获取for循环中节点节点的前节点,然后判断如果前节点是头节点,然后调用TryAcquire(arg)方法获取此锁,获取标题为节点,您设置的节点是第二个节点。您的节点将使用前节点为此锁定。此时,前节点已释放。如果您设置的节点已获得此锁定,则将其设置为将其设置。节点是将当前节点设置为前节点。如果未获得此锁,则将阻止当前节点并等待。什么在等待?等待前节点唤醒您,所以出现后,如何竞争?如果您是最后一个节点,请不要说,您会诚实地等待。如果您已经在您面前的头节点,它表明了什么?它表明轮到我了,然后我将跑步,尝试看看我是否可以得到这个锁。也许前节点已经发布了此锁。在前节点发布此锁后,觉醒队列中的线程,我想非常清楚地执行该过程。例如,有一个人,他后面有几个人。目前,第一人称目前是第一个人。目前,第一个人是第一人称。这是第一个获得此锁定的人,它始终是第一个获得锁定的人。那么人们会落后什么?站在团队后面,然后他可能会看着他面前的人是否向前迈出了一步。如果他走了,他还迈出了一步。当后来的人排在球队的第二位置时,他发现在Frontit中发现了前线是第一个人。第一人离开时,轮到他了。他会看看第一人称是否结束。完成此事后, 他成为头节点。这就是它的意思。
在这一点上,有一些AQ的细节。我建议你读它。例如,AQ如何释放锁定以及如何在发布后通知后节点。这相对简单。本章不会一一重复它们。你自己。
varhandle
让我们谈谈另一个细节。让我们在此方法中查看AddWaiter()方法,有一个Node.setprevreled(OldTail)。此方法意味着将当前节点的前节点写为尾部。当您输入此方法时,您会看到prev.set(this thist,p),这是什么上述?当您真正读取此代码时,您会发现上一个被称为varhandle的东西。这是什么varhandle?只有在JDK1.9之后。让我们谈谈这个变体,var呼叫变量,称为句柄的句柄,例如,我们写了一个句子,称为object o = new object(),我们有newObject,在此记忆中有一个小引用“ o”时间,指向此内存中的大型内存是新内存的对象。那么,这是什么意思?指的是这种“引号”。让我们考虑一下。如果Varhandle代表“引用”,那么Varhandle表示的值是否为“引用”。当然,这次,我们将有一个问题。已经有一个“ O”指向此对象。我们为什么要使用另一个引用来指向此对象?为什么是这样?
让我们看一下一个小程序,使用这个小程序来了解此varhandle的含义。在此类中,我们定义一个int类型变量x,然后定义一个变量类型变量句柄,该变量变量句柄在静态代码块中设置。Handle指向T01_hellovarhandle类中X变量的引用。换句话说,此X也可以通过此手柄找到。这更准确。您可以通过此x,8中找到此x,然后可以通过句柄找到此x。这样,我们可以使用此句柄来操作此x的值。在我们的手表方法中,我们创建了T01_hellovarhandle对象T.此t对象中有一个X,其中有一个手柄。此句柄还指向此x。从x。当然可以(int)handle.get(t)获取此x 8?我还可以通过hander.set设置此t对象的x值的值。(T,9)。很容易理解读写操作,因为句柄指向此变量,但是最重要的是可以通过此句柄来做什么?IntersectionHandle.comParendlet(t,9,10),原子的修改值,我通过hander.comparendset(t,9,10)将9到10到10至100更改为10至100。这是一个原子操作。您使用x = you100,会是原子吗?当然,int类型是原子质,但是长期呢?也就是说,即使x = 100也不是原子质,因此您可以进行一些比较的操作(原子操作(原子操作)如果您需要安全,则需要锁定,但是如果您通过手柄,则不需要,因此这是Where the the varhandle。除了可以完成普通属性的原子操作外,Varhandle还可以完成原子线的安全操作。这也是varhandle的含义。
在JDK1.9之前,只能通过反射完成操作类中成员变量的属性。反射和使用之间的区别在于varhandle更加有效。可以将varhandle理解为对dual -in -in -in -in -law的直接操纵,因此varhandle具有很高的高反射
螺纹网
首先,让我们谈谈螺纹插座,线程,螺纹,本地本地的含义,线程到底是什么?让我们看以下applet。我们可以看到这个小程序定义了一类。这堂课叫人。该类定义字符串类型变量名称。名称的值是“ Zhangsan”。我们实例这个人类类,然后在主方法中创建两个线程。第一个线程打印出p.name,第二个线程已将p.name的值更改为“ lisi”。两个线程访问了同一对象
如果您考虑这个小程序,您还必须知道,最终结果肯定是打印的“ lisi”而不是“ Zhangsan”,因为尽管原始值是“ Zhangsan”,但在此之后仍有一个线程将其变成“ Lisi”一秒钟的1秒。它在两秒钟的线程后打印出来,然后必须是“ lisi”,所以这是正常的,但是有时我们想在这个对象中使我访问过这个对象中的每个线程,我必须关联当我不得不修改内容时,另一个线程。我该怎么做?让我们看一下这个小程序。在这个小程序中,我们使用ThreadLocal。我们查看主要方法中的第二个线程。在此线程结束1秒后,将一个人对象设置为TL对象。在此TL对象中,第一个线程返回以在两秒钟后将其在TL对象中获取值。1秒钟后,第二个线程设置为TL对象中的一个值。我去了一个线程中设置一个值,另一个线程应能够在获得时获得值,但不幸的是,请查看代码。当我们在1秒内结束时,设置一个值,在两秒钟内不可能在钟声中获得此值。这个小程序证明了这一点。为什么这是?原因是,如果我们使用threadLocal,则设置的值是线程的独特之处。唯一的线程意味着什么?也就是说,当此线程中使用此螺纹插座时,只有只能自身设置的人,以及当您将其设置在自己的线程中,当您要访问它时,设置也是只能由您自己的线程访问以访问的人。这是ThreadLocal的含义
一个人设置在TL对象中,但是设置设置后,为什么不能读取其他线程?如何读取此操作?要读取ThreadLocal的源代码,我们尝试读取ThreadLocal的源代码
ThreadLocal源代码
让我们首先看一下线程源源代码的设置方法。线程局部设置如何?首先获取当前线程,这是您会发现此设置方法中有一个容器threaclocalmap。该容器是地图,键/值对,然后将其读低。您会发现此值设置为设置为设置。在地图中,此地图是什么,键设置为此,值设置了我们想要的值,这是当前的对象线程,值是该值person class。只是在情况下设置它,如果它是空的?只需创建一个地图
让我们回头看这张地图,threadlocalmap映射= getMap(t),让我们看一下此地图所在的位置。我们单击getMap方法以查看其返回值为t.threadLocals
我们输入此t.ThreadLocals,您会发现什么是threadlocalmap?它实际上在线程类中,所以此映射在Thred类中
目前,我们应该了解地图的集合方法实际上是在当前线程中设置地图:
因此,目前您会发现设置了原始人类类,并且当前线程中的某个地图已消失。目前,我们能理解吗?Intercectionwe请注意“当前线程”段落,因此T1线程集具有一个人对自己的地图的对象。T2线程也通过T2螺纹的地图访问,因此不值得阅读,因此您使用它在使用螺纹插座时使用它,您将其完全隔离为Set and Get,这在我自己的线程中是独一无二的。其他线程不可用。过去,我们的理解是在地图中,但不在地图中,所以您,您必须读取源代码,您可以理解源代码
为什么要使用ThreadLocal?
我们根据Spirng的声明对其进行分析。我们为什么要使用ThreadLocal。一般而言,我们必须通过数据库。配置文件中的交易实际上管理一系列方法,方法1.方法2,方法3 ...,这些方法可以写在例如第一个方法中写在配置文件中数据库连接。第二和第三连接到数据库。一种方法的连接,它不是同一对象。您能想到这件事以形成完整的交易吗?连接将放在连接池中。如果第一个方法是第一个连接,则第二个是第二个连接,第三个是第三个连接,这是第三个连接,可以形成完整的交易,可以形成完整的交易。我从未听说过不同的连接可以形成完整的交易。因此,如何确保如此多的连接保证是相同的连接?将此连接放在该线程的本地对象中。稍后再说,我实际上是从threadlocal中获取的。当采用第一个方法时,将连接放置在螺纹插座中。在时间之内,直接从螺纹锁定位置,不要从线程池中取它。
Java的四个引号:强,虚弱
实际上,Java中有4个引用,可以将4种类型分为强,柔软,虚弱和虚拟
GC:Java的垃圾回收机制
首先了解什么是参考?
对象o = new Object()这是一个引用,一个变量指向新对象,称为引用。此东西在Java中分为4种类型。普通引用,例如object o = new object(),这称为强引号。强烈报价的特征是什么?让我们看下面的小程序。
强制引用
首先,我看到了一个名为M的课程。在此类中,我重写了一种名为finalize()的方法。我们可以看到这种方法是一种废弃的方法。我为什么要重写他?主要想解释在垃圾回收过程中,各种参考文献不同。当垃圾回收利用时,它将调用finalize()方法。这是什么意思?当我们有新的地方时,无需用Java语言手动回收。需要C ++。在这种情况下,Java的垃圾回收机制将自动帮助您回收此对象,但是它回收了对象的对象。在时间之内,它将调用finalize()方法。重写此方法后,我们可以观察到它。什么时候通过垃圾回收,何时被称为。在您需要在哪些情况下重写此方法的情况下,将不需要重写此方法,因此不应重写。
让我们解释一下正常参考的正常参与。普通引用是默认引用。默认引用是,只要有一个指向该对象的应用程序,就不会回收垃圾回收。Force,为什么不回收呢?因为有一个参考点,它将不会回收。只有没有提及方向的引用,它才会被回收?指向您创建的对象。
让我们看一下以下小程序。我新Mo出来了,然后称为System.gc()以显示垃圾回收,以让垃圾回收尝试一下。最终阻止当前线程,为什么?因为System.gc()在其他线程中运行。如果主线程直接退出,整个程序将退出,那么GC没有任何意义,因此您要阻止当前线程,这里拨打此处,请在此处致电,请在此处致电,请在此处致电,在此处致电,在此处致电,请致电这里,在这里打电话,在此处致电,在此处致电,在此处致电,在此处致电,在此处致电,在此处致电,在此处致电,在此处致电,在此处致电,在此处致电Mosystem.in.read()阻止方法,它没有意义,只是阻止当前线程。
阻止电流线程是保持当前程序停止。您会发现该程序将永远不会输出运行。为什么?让我们考虑一下,这个m有一个很小的参考文献指向它,它指向它。绝对不是垃圾。如果不是垃圾,它将不会被回收。
然后,您想让它显示回收,您要做什么?我们让M = null,m = nul意味着不会参考此M对象,也就是说,M和New M()之间的参考将被中断运行程序后,您会发现输出是:最终确定,它指示什么?它表明M对象已回收,总而言之,这是一个很强的引用
让我们看一下要声明软引用的软引用,以反映记忆中的软引用,如何做?柔软意味着软。
让我们分析softreference
软引用
让我们谈谈软参考的含义。当有一个对象(字节数组)时,当指向软引用时,只有在系统内存不够的时候才能恢复(字节数组)
让我们运行这个程序。当程序运行时,让我们将最大桩内存设置为20MB,这意味着我的堆内存直接分配了20MB。您需要设置一个堆内存。如果未设置它,它将永远不会回收它。这次,当我们运行程序时,您会发现当第三个呼叫m.get()输出时,输出值为null。让我们分析一下,目前,我们的堆内存只能以最大为20MB。10MB一次创建字节阵列时分配。目前,可以分配堆内存。目前,我致电GC进行回收利用,因为堆叠的内存就足够了。目前已分配了15MB。目前,记忆的内存是否足够15MB?它一定不够,如果不够,我该怎么办?清理,因为清理时的内存不够,它会杀死您的软参考,并且然后分配15MB内存,因此,此时您要获得第一个字节数组的第一个字节数组。软引用的含义,使用大腿思考此软参考场景:进行缓慢的 - 数字使用,此东西主要是用于缓慢的使用
例如,您从内存中读取了一张大图片,并出现了特殊的图片,但是在使用它之后,可以将其在内存中慢慢存在。当您使用时,直接从内存中拿走它,但是因为这大图占据了很多空间。如果不使用它,那么其他人则需要使用此空间,然后杀死它。目前,使用软参考。
让我们举一个例子。从数据库中读取许多数据。这些数据可能可以单击返回,我也可以访问这些数据。如果内存中有,我不需要从数据库中获取它。这次,我也可以使用软应用程序。您可以在新空间中杀死我。没问题,我下次去数据库,但是当新空间仍然足够时,我不需要从数据库中获取它。
参考弱
接下来,让我们谈谈弱参考。弱参考意味着,只要遇到GC,它将被回收。刚才我们说,软引用的概念是垃圾回收可能不会被回收。软引用的生存周期仍然相对较长。然后,我们说申请薄弱。弱参考是,只要垃圾回收看见,该参考是对点特别薄弱的参考,请直接杀死它
让我们看一下这个小程序,弱Reference M =新的弱Reference<>(new M()),这里我们有新的对象。这是第一点。该m针对弱参考。此弱参考文献中有一个参考。然后通过m.get()打印此M对象,然后调用GC要求垃圾回收。如果未回收,您可以接下来得到它,否则
运行程序后,我们看到第一个打印被打印出来,并且在第二次打印之前调用了GC,因此第二次打印了空值。对于引用的对象,此弱参考对象中有一个弱参考到另一个大的M对象,但是这个大的M对象垃圾回收并杀死它。那么它的用途是什么?这件事的作用是,如果有强有力的参考文献有强有力的参考,只要有强的参考文献消失,就应该回收此弱参考。必须回收。这东西在哪里使用?通常在容器中使用
让我谈谈ThreadLocal的最典型应用。让我们看以下代码。注意创建一个称为TL的对象。该TL对象的引用指向螺纹插座对象。这是我们最直观的想法
我们的想法是一个想法,但是它在其中执行了什么样的操作?让我们看看上面的图片。从左到右看它。首先,我们目前必须有一个线程。任何方法都必须在特定线程中运行。该线程是我的主线程。在此线程中,此线程在此线程中。有一个称为TL的线程的局部变量。TL具有线路对象。这是一个强烈的引用。然后,我将一个对象放在threadLocal中,但是您还记得吗,您还记得吗,将对象放在螺纹对象术语中,实际上它位于当前线程中放置的threadlocals变量中。此螺纹变量指向地图,也就是说,我们将此M对象放在此地图中。它的关键是我们的threadLocal对象,值是我们。对象m,让我们考虑一下。在ThreadLocal中设置时,请先获取当前线程,然后在当前线程中获取映射,然后使用此映射设置螺纹locacal对象。该方法在值中是谁?它是螺纹插座对象。集合进入时,将此东西放入所谓的条目中。该条目是什么?请注意代码,此条目是从弱参考弱Rereference继承的
现在意味着有一个条目。它的母班是一个弱者。此弱率中安装了什么?它是螺纹局部对象,这意味着此条目是一个键和值,并且此条目的类型是螺纹锁定。当然,此值是我们M或其他值的值。输入是从螺纹锁定继承的。当进入构造函数时,将调用超级(k)。此k指螺纹局部对象。我们认为弱报道等同于新的弱ReReference键?
让我们从左侧看下图。目前,我们应该理解TL是对此螺纹局部对象的有力引用,并且MAP中的键通过弱参考指向螺纹局部对象。我们假设这是一个强烈的强度,当TL指向此螺纹local焦点对象消失时,TL是局部变量,并且该方法已终止。当TL消失时,如果此threadlocal对象仍然由强引号键指导,则此螺纹插座对象可以回收吗?它不得不工作,并且因为该线程中存在许多线程,例如很长时间服务器线程,每年7*24小时,365天,并且不间断的操作。长期存在,此地图键也将长期存在。如果此键存在很长时间,则此螺纹局部对象将永远不会消失,因此这里会有内存泄漏,但是如果此键很弱,此问题仍然存在。将自动恢复
关于ThreadLocal还有另一个问题。当我们的TL的强参考消失时,Key的指向也被回收,但是不幸的是,此关键指向无效,但是此threadlocals映射将始终存在,这相当于说kekey/value是的,是的,您的键是null,您是一个值指向的值指向。,您的10MB字节码,您仍然可以访问它吗?无法访问它。如果地图越来越多地积累了越来越多的,它仍会在内存中泄漏。我该怎么办?因此,您必须记住这一点。在threadLocal中使用对象。
使用
为了使用它做一件事,它是管理外部内存。首先,该虚拟构造函数至少使用两个参数。第二个参数必须是队列。基本上是没有用的,也就是说,它不适用于您,因此它使用谁?它是由编写JVM(虚拟机)的人使用的
让我们看一下下面的小程序,创建一个在小程序中设置的列表以模拟内存溢出,并创建一个参考等级(参考队列)。此内存是什么样的?有一个幻影对象,指向新的phantomreference对象,可以从new中进行phantomreference对象访问此对象中的两个内容。第一个内容是它指出了我们新的对象,通过特殊参考,我们新的对象是新的。第二个内容是第二个内容。它关联一个队列(队列)。目前,它被回收一次。此引号将安装到此队列中,这意味着该队列在做什么?当回收垃圾时,一旦使用虚拟报价回收,它将安装在此队列中,并允许您接收通知。您什么时候检测到,如果该队列中有参考,它表明它表明什么表示?交叉口表明此虚拟引号已回收。该虚拟引号称为特殊引用,任何对象都指向对象。如果不提到垃圾回收,那么杀死这个M对象是肯定的。最重要的是,当M对象被杀死时,您会收到通知,您的通知方式是什么?是在此队列中放置一个值(队列)
那么我们的小程序是什么意思?在微型程序启动之前,设置堆内存的最大值,然后在第一个线程启动后,它将不断分配列表中的对象。A线程不断监视此中的更改队列。如果这意味着将这种虚拟用途放入,则意味着它是回收的
第一个线程启动后,我们将看到,无论我们如何获得幻影的值,IT输出之间的差异是空的。
那么,如果我无法在这里获得价值,该怎么办?我该使用什么?只是给您通知并在通知通知时将其放在队列中。仅使用JVM使用它,在使用JVM时如何使用它?当检测到队列时,他将处理相应的过程当队列回收时检测到队列。相应的治疗何时出现?
通常有这种情况。NIO中有一个新的新缓冲区,称为DirectByteBuffer(直接内存)。直接内存未直接由JVM(虚拟机)管理。谁管理?它是由操作系统管理的,也称为记忆堆。这种直接的buffer可以将其直接定向到堆的外部。然后让我们考虑一下。如果将此直接划分设置为null,那么垃圾回收器可以回收直接buffer吗?它指向不在堆中的内存。您如何回收它,因此无法回收。那么,编写虚拟机的人如何回收直接buffer?如果有一天您还使用了一堆外部内存,当将此直接buffer设置为null时,如何在堆外恢复内存?您可以使用虚拟报价。当我们通过垃圾回收器检测到这种虚拟恢复时,您将进行相应的处理以回收堆的清单
也许将来有一天,您写了一个荨麻,然后当你们所有人在网络中分配记忆时,请使用一堆内存,因此您想自动在堆中回收它。API人,让人们自己回收自身,对吗?那么,您此时如何自动回收?您可以在虚拟性中检测到队列,何时将检测到的DirectByteBuffer(直接内存)回收。目前,您要清理一堆内存。如果您是用C和C ++语言编写的虚拟机,当然,DEL和Free的两个功能也由C和C ++提供。现在提供了Java。此类可以使用Java的反射机制在JDK 1.8时使用它,但是在JDK1.9之后,将其添加到包中。普通人无法使用它,但是JUC的许多底层都使用此类使用此课程,这是此不安全类中的两种方法。分配系统方法直接分配内存,也就是说,分配了一堆外部内存。自由度方法回收内存也正在手动恢复内存。这与C/C ++相同。
参考:
https://blog.csdn.net/qq_39192827/article/details/85611873
https://www.mashibing.com/studycourseno=419§no=18184&calllll = %2fsubject%2fstudyline%2f1%3fcourseid%3d10434
https://blog.csdn.net/qq_35190492/article/details/116431270
https://blog.csdn.net/lzb348110175/article/details/103709548
原始:https://juejin.cn/post/7107066064978051108