﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-欢迎您来到芯片的主页！-随笔分类-Java</title><link>http://www.cppblog.com/Chipset/category/9270.html</link><description>Welcome to Chipset's homepage!
</description><language>zh-cn</language><lastBuildDate>Thu, 23 Apr 2015 12:05:50 GMT</lastBuildDate><pubDate>Thu, 23 Apr 2015 12:05:50 GMT</pubDate><ttl>60</ttl><item><title>垃圾收集--从算法角度去看</title><link>http://www.cppblog.com/Chipset/archive/2015/04/20/210402.html</link><dc:creator>Chipset</dc:creator><author>Chipset</author><pubDate>Mon, 20 Apr 2015 09:21:00 GMT</pubDate><guid>http://www.cppblog.com/Chipset/archive/2015/04/20/210402.html</guid><wfw:comment>http://www.cppblog.com/Chipset/comments/210402.html</wfw:comment><comments>http://www.cppblog.com/Chipset/archive/2015/04/20/210402.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Chipset/comments/commentRss/210402.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Chipset/services/trackbacks/210402.html</trackback:ping><description><![CDATA[<div>其实垃圾收集就是对于无用的(也称作死亡的)对象释放掉他们占用的资源。无论Java还是C#还是脚本语言，还是其他，垃圾收集无非就三四个算法，下面分别介绍。<p style="background-color:transparent;"><strong>1. 引用计数</strong><br style="background-color:transparent;" /></p><p style="background-color:transparent;">一 个对象(本文里对象都是广义的对象，不是专指用class类型创建的变量，下同)刚创建时，计数器(用一个整数变量)为1，引用一次计数器加1，过期减 1，右值引用[请参考C++11标准里的右值引用概念，如果你不明白的话]一次减1，当计数器降到0时该对象过期，回收掉他占用的资源。</p><p style="background-color:transparent;">原理十分简单，实现起来也容易，尤其对于大对象，简直是绝配，计数器是共享变量，通常用一个整型变量表示，它的加减用原子操作就能搞定，速度很快。再者就是实时性很好，引用就加1，不引用就减1，过期就消失。</p><p style="background-color:transparent;">对 于小对象来说就不爽了，比如一个字符，占用的内存比计数器还小，对于对象都从堆上创建的语言，例如Java，不太合适。另外一个缺点是循环引用比较麻烦， 此时计数器永远不会降到0，此时会导致内存泄漏。早期的COM漏内存就是因为这个原因，Perl里至今对象都不能循环引用，否则漏内存。解决方案也有，请 参考C++11里的weak_ptr和boost里的weak_ptr实现。</p><p style="background-color:transparent;"><strong>2. 标记清理</strong></p><p style="background-color:transparent;">看名字就能明白，就像打标签。定期的扫描堆上的对象，把一个对象分成三个阶段(从数学角度来看是三个集合) 黑-灰-白处理，黑表示占用状态，白表示无用状态，灰位于二者之间，白表示可以回收了。实际实现中可能并不严格按照理论上这样区分哦。</p><p style="background-color:transparent;">不停的扫描会耗费太多的资源，长时间不扫描则可能堆内存耗尽，这个扫描时机是个问题。扫描实际有的设计成任务触发，有的设计成时间触发。不论设计成哪种都不能保证很完美，因为收集器也是应用程序(从操作系统内核的角度去看)，也接受调度，由于收集的是一个对象的集合(通常是一次收集多个对象占用的资源)，因此实时性不太好保证，尤其在单核心的年代和计算能力不太强的嵌入式领域尤为突出。</p><p style="background-color:transparent;">什么事情有缺点就有优点，多核心的年代就好多了，尤其当CPU占用率不高且内存比较大时，这标记清理的做法可能还是比较不错的。</p><p style="background-color:transparent;"><strong>3. 拷贝</strong></p><p style="background-color:transparent;">最好理解的就是半堆拷贝，内存一分为二，一边是有用对象一边是无用对象。从堆空间(请网上搜索进程的线性地址空间，我这里就是那个意思)的一端开始，不停 的把活着的对象复制到另外一端，等这边都剩死的对象时，把这段空间(一半堆内存)标记为空闲。考虑内存利用率和处理速度原因，实际实现中很多时候可不一定 采用半堆的做法(可能采用1/N堆，N&gt;2)，这里是为了便于理解才用半堆做例子。</p><p style="background-color:transparent;">复制小对象很好，但是复制大对象会比较慢。复制貌似最粗劣最不好的垃圾收集算法，其实不然，想想引用计数和标记清理都不搬运对象，因此长时间运行(想想服务器能动不动就关机吗？)会产生很多内存碎片，而复制的做法则相当于压缩内存，降低内存碎片，提升内存利用率。想想极端情况下吧，本来有接近1GB内存空 闲(假设有个2字节的对象刚好位于中间)，此时申请512MB以上的堆内存会注定失败，因为没有连续的512MB内存可用。上述其他算法都解决不了这种问题， 复制收集的做法却能很好的解决这种问题。</p><p style="background-color:transparent;">对于有指针的C和C++，复制的收集算法没法用，因为这会导致指针悬挂和野指针，对于Java等，这种收集算法也许很不错。跟标记清理类似，由于一次收集多个对象占用的资源，实时性也是个问题。</p><p style="background-color:transparent;"><strong>4. 分代收集</strong></p><p style="background-color:transparent;">严格的说分代收集不应该算算法，应该算策略。<br style="background-color:transparent;" /></p><p style="background-color:transparent;">请考虑人的家谱就能容易理解多了，或者考虑数据结构中的树层次结构，道理类似。对象也可以分代，显然越年轻的对象越容易死(请考虑作用域的概念，或者想想C++，Java等程序里大括弧里和嵌套的变量也行)。分代收集是后来才引入的，Java和C#里都有采纳。实际效果表明，分代收集比上面那三种中的任意一种收集算法都好用，尤其复杂对象比较多时更是如此，抛开实时性不谈，至少平均速度比较快。</p><p style="background-color:transparent;">现实中，商用领域没有一种垃圾收集算法特别适合所有场合，多数垃圾收集器都采用多种算法组合来设计。多数垃圾收集器都是并发执行的，有的设计则是一个线程始终在收集垃圾内存，尤其在多核的年代更是如此。</p><p style="background-color:transparent;"><br /></p><p style="background-color:transparent;"><strong>垃圾收集优缺点</strong><br /></p><p style="background-color:transparent;">垃 圾收集带来了数不尽的好处，尤其从软件工程的角度去考虑时，好处太多了，这就不多罗嗦了，但是也会降低程序执行速度，尤其在实时性要求高的场合，真就没少见用垃圾收集器的，或者这样说，在高实时性的应用里，用C和C++和汇编，在一般场合下用Java,C#甚至脚本都行。</p>垃圾收集会带来额外的内存开销，我觉得还是数据化比较容易理解，理论上同样算法下，有3倍以上的内存时，Java代码可以达到C++代码的80%以上速度，如果想达到同样的速度，Java代码需要消耗7倍于C++代码耗费的内存。注意是理论上，实际中可能还不止这个数。</div><img src ="http://www.cppblog.com/Chipset/aggbug/210402.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Chipset/" target="_blank">Chipset</a> 2015-04-20 17:21 <a href="http://www.cppblog.com/Chipset/archive/2015/04/20/210402.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Why Java Will Always Be Slower Than C++?</title><link>http://www.cppblog.com/Chipset/archive/2008/12/18/69739.html</link><dc:creator>Chipset</dc:creator><author>Chipset</author><pubDate>Thu, 18 Dec 2008 09:20:00 GMT</pubDate><guid>http://www.cppblog.com/Chipset/archive/2008/12/18/69739.html</guid><wfw:comment>http://www.cppblog.com/Chipset/comments/69739.html</wfw:comment><comments>http://www.cppblog.com/Chipset/archive/2008/12/18/69739.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Chipset/comments/commentRss/69739.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Chipset/services/trackbacks/69739.html</trackback:ping><description><![CDATA[<h1 style="font-weight: normal;"></h1>
<span style="font-size: 10pt;">
<h1 style="font-weight: normal;">Why Java Will Always Be Slower than C++</h1>
<h1 style="font-weight: normal;"><em>by Dejan Jelovic</em></h1>
<h1 style="font-weight: normal;">&nbsp;<em><font size="4">"Java is high performance. By high performance we mean
adequate. By adequate we mean slow." </font></em><font size="4">- </font> <a href="http://www.mrbunny.com/">
<font size="4">Mr. Bunny</font></a></h1>
</span>
<h1 style="font-weight: normal;"><a href="http://www.mrbunny.com/"><font size="4"></font></a></h1>
<p class="separatefromnext"><em></em></p>
<span style="font-size: 10pt;">
<p class="separatefromnext"><em></em></p>
<span style="font-size: 12pt;">
</span>
<p><a href="http://www.mrbunny.com/"><font size="4"></font></a></p>
</span>
<p><a href="http://www.mrbunny.com/"><font size="4"></font></a></p>
&nbsp;&nbsp;&nbsp; 耍过Java程序，或者用Java码过程序的人都晓得，Java要比用C++写成的原生程序要慢。这是咱用Java时已经承认的事实。<br>　　不过，很多人想要说服我们说这只不过是暂时的，他们说Java从设计上来讲并不慢，相反，只是现在的JIT实现相对比较嫩，有很多能优化的地方JIT并没有优化到，拖了后腿。其实不然，不管JIT们多牛，Java永远要比C++慢。<br>　　<br>　　我想说...<br>　　宣扬Java不慢于C++的人往往是觉得，（语法）严格的语言，可以让编译有更大的优化空间。因此，除非你想做人肉编译器优化整个程序，否则通常都是编译器做得更好。<br>　　这是真的。在数值计算领域，Fortran仍然胜于C++，的确因为它更严格。不用担心指针瞎搅和，编译器可以更安心地优化。C++想打败Fortran的唯一办法，就是好好设计一个像Blitz++那样的库。<br><br>　　测试...<br>
Java可以跟得上C++的地方，就是基准测试。计算起第N个斐波纳契数，或者运行起Linpack，Java没理由不跟C++跑得一样快。当所有的计
算都放在一个类里，并且只使用基本的数据类型，比如说int或者double时，Java编译器的确能跟得上C++的脚步。<br><br>　　事实...<br>　　当开始在程序中使用对象的时候，Java就放松了潜在的优化。这一节会告诉你为什么。<br><br>　　1.&nbsp;所有的对象都是从堆里分配的。<br>　　Java从栈里分配的，就只有基本数据类型，如int，或者double，还有对象的引用。所有的对象都是从堆里分配的。<br>　　当有大量语义上是一回事的对象时，这不成问题。C++同样也是从堆上分配这些对象。但是，当有值语义不同的小对象时，这就是一个主要的性能杀手。<br>
什么是小对象？对我来说，就是迭代器们。在设计中，我用了很多迭代器。别人可能会用复数。3D程序员可能会矢量或者点类。处理时间序列的人可能会有时间
类。使用这些类的人，无一例外地讨厌把不费时间的栈上分配换成花费固定时间的堆上分配。假如把它放在一个循环里，就变成了O(n)对0了。如果再加一层循
环，没错，又变成O(n^2)对0了。<br><br>　　2.&nbsp;大量的转换。<br>　　得益于模板，好的C++程序员甚至可以写于完全没有转换的牛程序。不幸，Java没有模板，所以Java代码总是充满了转换。<br>　　对于性能，它们意味着什么？呃，在Java里所有的转换都是很费时的动态转换。多费时？想想你可能会怎么样实现转换的：<br>　　最快的方法就是，给每一个类赋值一个序号，然后用一个矩阵来描述任意两个类是否相关的。如果是的话，需要给指针加上多少的位移才能进行转换。这种方法的伪码看起来应该是这样的：<br>DestinationClass&nbsp;makeCast&nbsp;(Object&nbsp;o,&nbsp;Class&nbsp;destinationClass)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;Class&nbsp;sourceClass&nbsp;=&nbsp;o.getClass&nbsp;();&nbsp;//&nbsp;JIT&nbsp;compile-time<br>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;sourceClassId&nbsp;=&nbsp;sourceClass.getId&nbsp;();&nbsp;//&nbsp;JIT&nbsp;compile-time<br><br>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;destinationId&nbsp;=&nbsp;destinationClass.getId&nbsp;();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;offset&nbsp;=&nbsp;ourTable&nbsp;[sourceClassId][destinationClassId];<br><br>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(offset&nbsp;!=&nbsp;ILLEGAL_OFFSET_VALUE)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;&lt;object&nbsp;o&nbsp;adjusted&nbsp;for&nbsp;offset&gt;;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;IllegalCastException&nbsp;();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br>好一堆代码。这只是一个简单的情景——用矩阵来表示类的关系浪费了一部分内存，没有哪个成熟的编译器会这样子做。他们会使用map或者遍历继承树，这样会变得更慢。<br><br>　　3.&nbsp;攀升的内存占用。<br>　　Java程序储存数据占用的内存大概是相当的C++程序的两倍。原因如下：<br>　　1.&nbsp;启用了垃圾收集的程序一般都比不使用垃圾收集的程序多花50%的内存。<br>　　2.&nbsp;本来C++里在栈上分配的对象，到了Java就在堆上分配了。<br>　　3.&nbsp;Java对象比较大，因为所有的对象都有一个虚表，还要加上对（线程）同步的原生支持。<br>大的内存映像让程序更大概率被放到磁盘的交换区去。没有什么比交换文件更慢的了。<br>　　4.&nbsp;缺少更细致的控制。<br>Java原来就是作为一种简单的语言来设计的。很多在C++里让程序员控制细节的特性在Java里都被一脚踢开了。<br>　　比如说，在C++里可以改进引用的位置(?)。或者一次申请和释放很多个对象。或者用指针耍一些小技巧，更快地访问成员。<br>　　5.&nbsp;没有高层次的优化。<br>　　程序员处理高层次的概念。而编译器处理剩下的低层次概念。对于程序员来说，一个叫Matrix的类就代表了比一个叫Vector的类更高层次的概念。而对于编译器来说，这些名字都是符号表的一个入口。他们只关心类里面有哪些函数，函数里面有哪些语句。<br>　　这样想一下，比如说要实现一个exp(double&nbsp;x,&nbsp;double&nbsp;y)函数，计算出x的y次幂。对于一个编译器，它能只看一下这个函数，然后指出，exp(exp(x,&nbsp;2),&nbsp;0.5)可以优化成x自己吗？当然不行。<br>　　编译器能做的优化只是语句层面的，而y是在编译器里面的。即使程序员知道两个函数是对称的，可以把它们都消去，或者函数的调用顺序只是相反的，除非编译器能只瞄一下语句，然后指出来，不然优化是不可能完成的。<br>　　所以，如果想要完成一个高水平的优化，必须存在某种方法，可以让程序员来告诉编译器优化的规则。<br><br>
没有哪个流行的程序语言/系统可以做到这点，至少已知的方法，比如微软承诺的智能语言，都不能。即便如此，在C++里可以用模板元编程来实现对高层次对
象的优化。临时消除，部分求值，对称函数调用的消去，和其它可以用模板实现的优化。当然，不是所有的高层次优化都可以这样做。并且实现这些东西相当麻烦。
但是大多数都可以完成，有人已经用这些技术实现了好些时髦的库。<br><br>　　不幸的是，Java没有任何元编程的特质，因此在Java中不会有这种高层次的优化。<br>　　所以...<br><br>　　由于存在这种语言特性，Java不可能达到C++这种速度。这相当程序上暗示了，对于要求高性能的软件和竞争激烈的COTS舞台上，使用Java不是一种明智的选择。但是因为它和缓的学习曲线，它的容错，和它庞大的标准库，所以适合开发中小型自用和定制软件。<br><br>　　附记...<br>　　1.&nbsp;有人向James&nbsp;Gosling（谁？google之...）提交了很多可以改进Java性能的语言特性。文本在这里。不幸的是，Java语言已经有四年没有改动过了，所以看起来这些提议似乎不会在一夜之间被实现。<br><br>　　2.&nbsp;最有可能往Java里加入泛型的是Generic&nbsp;Java。又很不幸的是，GJ只是通过在编译时把所有类型信息去掉来支持泛型。所以最后面执行环境看到的，仍然是缓慢的转换。<br><br>　　3.&nbsp;垃圾收集的FAQ包含了关于垃圾收集慢于定制分配器的信息（上面第四点）。<br><br>
4.&nbsp;这里是一篇宣称垃圾收集比栈分配的快的文章。但是它的要求是物理内存必须是程序实际需要的内存的七倍之多。还有，它描述的是一种stop-
and-copy（是不是那种执行到一半，然后停下来，把内存拷到另外一块内存，同时清除垃圾的那种方法？），而且还不是并发的。<br><br>　　反馈...<br>　　我收到很多关于这篇文章的反馈。附上一些典型的评论，还有我的回答：<br><br>　　&#8220;你还忘记了指出在Java里所有的方法都是虚方法，因为没有人会加上final关键字。&#8221;<br>　　事实上，不使用final关键字不是问题的关键所在，使用者才是。同时，虚函数也没有问题，但是却失去了优化机会。自从JIT们知道怎么样内联虚函数，这就变得不那么显著了。<br><br>　　JIT可以内联虚函数，所以Java可以比C++更快。<br>　　C++也可以使用JIT编译。不信的可以看看.NET的C++编译器。<br><br>　　到最后的时候，速度并不重要。电脑浪费了大部份时间在等待用户输入。<br>　　速度仍然很重要。我仍然在等我的笔记本启动起来，我在等我的编译器停下来，我还要等Word打开一个超长的文档。<br>　　我在一个金融公司工作。有时候我必须对一个很大的数据集进行模拟。速度在这种情况下都很重要。<br><br>　　有些JIT可以在栈上分配一些对象。<br>　　当然，一些。<br><br>　　你的转换代码看起来很丑。可以在类的继承层次上检查类。<br>　　首先，这样只比矩阵查找快一点点而已。<br>　　第二，这样只能查找类，类只占多少？低层次的细节往往是通过接口来实现的。<br><br>　　哈，那么我们都应该使用汇编？<br>　　不是的，我们都要使用对业务有用的语言。Java提供了庞大的标准库，让很多任务变得容易，因此Java是伟大的。它比其它所有的语言更容易移植（但并非100%可移植——不同的平台有不同问题）。它具有垃圾收集机制，简化了内存管理，同时也让某些构造如闭包可实现。<br>　　但是，同时，Java和所有的语言一样，也有瑕疵。在值语义的类型上缺少支持。它的同步并不是很有效率。它的标准库建立在异常检查之上，把实现拖进了接口。它的性能可以更好。它的数学库有些恼人的问题。诸如此类。<br><br>　　这些缺憾都是大问题吗？看你用它做什么。因此，在几种语言里，连同它的编译器以及可以选择的类库里选择对你的工程有利的一种。<br><br>原文见：http://www.jelovic.com/articles/why_java_is_slow.htm<br>[林杰杰翻译，不是我(Chipset)译，我仅仅是拷贝林杰杰的！]<br><br>  <img src ="http://www.cppblog.com/Chipset/aggbug/69739.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Chipset/" target="_blank">Chipset</a> 2008-12-18 17:20 <a href="http://www.cppblog.com/Chipset/archive/2008/12/18/69739.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>