C、C++等语言中,内存的分配和释放由程序代码来完成,容易出现由于程序员漏写内存释放代码引起的内存泄露,最终导致系统内存耗尽。
Java代码运行在JVM中,由JVM来管理 堆Heap 内存的分配和回收(Garbage Collection),把程序员从繁琐的内存管理工作中释放出来,更专注于业务开发。Java内存回收工作由标记(识别可回收对象)和回收(释放可回收对象)两个步骤组成。
和程序代码释放内存相比,内存自动管理会占用一部分CPU时间,Stop The World特点回暂停业务程序运行,非常影响执行效率。Java各版本中,一直致力于内存管理算法的优化,形成了一套针对各种内存分区(新生代、老年代)和运行场景(单核、多核、客户端、服务端)的特点而针对性设计的内存回收算法。
这里说的内存回收机制,主要是指针对堆Heap和元空间Metaspace内存的回收,线程相关内存(栈、本地栈、
程序计数器)内存随线程创建和回收,直接内存的释放由其在堆内存中引用释放时触发。
在内存被回收前,系统必须标记哪些内存已经没有人使用可以释放,这个工作就由内存标记算法的来完成,在Java各版本中,使用过如下几种标记算法。
这是早期的内存标记算法,每个堆中分配的对象都有一个引用计数器,计数一个对象被引用的次数。当对象创建并赋值给变量时,计数为1,当有其他变量引用该对象时,引用计数+1;但引用此对象的变量超出存活范围或释放对对象引用(包括变量引用了其他对象或变量被设置为null等),引用计数-1。对象的引用计数为0时,表示此对象可被垃圾收集器回收。
引用计数法的有点是简单,执行速度快,只要变量一遍对象检测引用计数是否为0即可判断是否可回收;确定是无法检测出循环引用而导致内存无法回收。
又称跟踪算法(Tracing),算法引入了图论,把所有对象间的关系看成一张图,内存标记从一组根节点(GC Root Set)开始,通过递归搜索,建立对象的引用关系图,当搜索完毕后,图外的对象就是可回收对象。这是目前Java中使用的内存标记算法。

可作为GC Root的对象包括:
采用跟踪算法标记内存对象后,再扫描堆内存中未被标记的对象,进行回收。此算法不移动对象,仅对不存活对象进行回收,在存活对象占比高的情况下处理效率高,但不移动对象会引起内存碎片。
此方法和标记-清理算法使用相同标记算法,但在对不存活对象回收时,会把存活对象向内存前部空闲区域移动,同时更新对象的指针。此方法在清理的基础上,会对对象进行移动,执行成本较高,但可解决内存碎片问题。基于此算法的内存回收实现,一般会增加句柄和句柄表。
该算法把内存分为空闲区和对象区,新建对象存储到对象区中。当对象区满时,先采用跟踪算法对对象进行标记,再把存活对象拷贝到空闲区,清空原对象区,空闲区和对象区互换角色。在拷贝过车中,程序需要暂停,此算法适用于存活对象叫少的情况,可以解决内存碎片问题。
JDK8中,堆中移除了永生代区域,堆内存主要由新生代和老年代两部分组成。其中新生代由一个伊甸园(Eden)和两个幸存者Survivor From和Survivor To 3部分组成,新创建对象首先保存在Eden中,当Eden中对象达到一定数量时,JVM触发Minor GC,GC时,先把Eden和From中的存活对象拷贝到Survivor To区,再清除Eden和From两个区域的数据,最后From和To互换身份,完成一次内存回收。新生代区域对象数量大,存活时间短,一般采用复制-清除算法,通过这种结构和回收方式来提高垃圾回收效率,减少内存碎片。
经过若干(默认15)次后还存活的对象,将进入老年代区,当老年代数据满时,会触发Major GC(又称Full GC),此时新生代、老年代、元区域、直接内存区域都会执行GC操作。
上述的内存标记算法、回收方式和分代策略是垃圾回收的方法,根据这些方法,针对不同的用户场景(Server、Client)和系统配置(单线程、多线程),JVM实现了适用于各场景的垃圾回收器。
年轻代收集器
老年代收集器
混合收集器
Serial是单线程收集器,Serial收集器只能使用单个线程进行收集工作,在收集的时候必须得停掉其它线程,等待收集工作完成其它线程才可以继续工作。
Serial收集器是JVM中最早的垃圾收集器,也是JDK1.3前的唯一收集器,不再适用于现代多核CPU和Server(服务端)场景,但是非常的适合单核CPU和Client场景。

ParNew是Serial的升级版,其工作的流程和Serial基本一致,主要的改进是支持多线程同时执行垃圾回收工作,即上图中的GC Thread支持多线程,可以充分利用多核CPU的性能。它是HotSpot上第一个真正意义实现并发的收集器。GC默认开启线程数等于CPU数量,可通过 -XX:ParallelGCThreads 来控制垃圾收集线程的数量。
Parallel Scavenge是吞吐量优先的收集器,其工作方式和ParNew基本一样,但是它以提高系统吞吐量(Throughput)为设计目标,吞吐量=业务运行时间/系统总运行(业务+GC)时间。
ParNew等收集器的关注点是尽量缩小垃圾回收的停顿时间,而缩短停顿时间必然需要提高垃圾回收的频率,导致业务线程和GC线程间频繁的切换,从而增加CPU在现场切换上的损耗。
而以吞吐量为设计目标的Parallel Scavenge收集器,可以通过扩大新生代内存容量,减少垃圾回收发生的次数,虽然提高了单次GC的时长,但减少了线程切换开销,从整体上可以提高系统的吞吐量。

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是:

单次GC时间参数并非设置的越小越好,而是一把双刃剑,如果减少单次GC时间,必然导致GC频率的上升;而设置的增大,则必然需要更大的内存来支撑。
由于Parallel Scavenge和其他收集器(Serial、ParNew、CMS等)使用了不用的设计框架,导致其无法和CMS协同工作。
工作模式基本和新生代的Serial一样为单线程,它采用标记-整理算法,这个模式主要是给Client模式下的JVM使用。如果是Server模式有两大用途:
Parallel Scavenge的老年版本,JDK6开始出现,采用标记-整理算法。Parallel Old的出现结合Parallel Scavenge,真正的形成“吞吐量优先”的收集器组合。JDK7和8中,作为老年代默认的收集器。
CMS收集器是以最短回收停顿时间为目标的收集器。重视响应,以带来好的用户体验,是并发低停顿收集器,通过-XX:+UseConcMarkSweepGC参数启用CMS收集器。
CMS采用支撑多线程并发的标记-清除算法,它的运作分为4个阶段:

CMS在初始标记和重新标记阶段需要暂停业务线程,在执行时间上,初始标记 < 重新标记 < 并发标记,所以时间最长的并发标记,业务线程和GC线程并发运行,所以用户感受上,GC暂停的时间很短。但其也存在几个缺点,具体如下:
G1收集器再JDK7中首次出现,JDK7/8中默认关闭,可通过 -XX:+UseG1GC 参数打开,是JDK9中默认收集器。G1收集器是当前最先进的收集器之一,设计目标也是降低延迟,是用于替代CMS的功能更为强大的新型收集器,它解决了CMS内存空间碎片等缺陷。
G1收集器弱化了内存分代的概念,而是把内存分割为若干个大小相同的区域(Region),以区域为单位,增量式的执行内存回收,可以同时作用于新生代和老年代。G1采用标记-清除-复制方式回收内存,一个完整周期包括:初始标记、并发标记、重新标记、清除、转移回收。对象标记后,通过并行方式把一组或多组区域中存活的对象以增量的方式复制到不同区域进行压缩,从而减少内存碎片。


作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.