java虚拟机JVM总结

内存模型:程序计数器、虚拟机栈、本地方法栈、堆、方法区(永久代)
  1. 程序计数器(counter)
    • 线程私有。
    • 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    • 唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
  2. 虚拟机栈(VM stack)
    • 线程私有,使用连续的内存空间。
    • 描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。
    • 两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。
  3. 本地方法栈(native)
    • 与虚拟机栈相似。
    • 线程私有。
    • 描述的是本地方法内存模型。
  4. 堆(heap)
    • 所有线程共享。
    • 唯一目的就是存放对象实例。
    • 垃圾收集器管理的主要区域,也称GC堆。
    • 通过-Xms(最小值)和-Xmx(最大值)参数设置大小。
    • 分为新生代和老年代。
    • 新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及SurvivorSpace的大小。
    • 老年代用于存放经过多次新生代GC仍然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:1、大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。2、大的数组对象,且数组中无引用外部对象。
  5. 方法区(method area)
    • 所有线程共享。
    • 是线程安全的。
    • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • 可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集。
    • 通过-XX:PermSize 和 -XX:MaxPermSize 参数限制大小。
  6. 堆和栈的对比
    • 栈:主要存放引用和基本数据类型。
    • 堆:用来存放new出来的对象实例。
  7. 新建对象的内存分配
    • JVM 会试图为该对象在Eden Space中初始化一块内存区域。
    • 当Eden空间足够时,内存申请结束;否则到下一步。
    • JVM 试图释放在Eden中所有不活跃的对象。释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区。
    • Survivor区被用来作为Eden及Old的中间交换区域,当Old区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区。
    • 当Old区空间不够时,JVM 会在Old区进行完全的垃圾收集。
    • 完全垃圾收集后,若Survivor及Old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现“outofmemory”错误。
  8. 对象的访问
    • 涉及栈、堆、方法区这三个最重要内存区域之间的关联关系。
    • 如这句代码:Object obj = newObject()。
    • “Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现。
    • “new Object()”这部分的语义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(对象中各个实例字段的数据)的结构化内存。另外,堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息。
    • 对象类型数据(如对象类型、父类、实现的接口、方法等)是存储在方法区中。
    • 主流的访问方式有两种:使用句柄和直接指针。
    • 使用句柄:堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。优点是稳定。
    • 直接指针:有点是速度快,hotspot用的就是直接指针。
  9. 判断对象是否可以回收的算法
    • 引用技术算法:引用就+1,没引用就-1,0则回收。缺点是无法解决对象之间的循环引用。
    • 可达性算法(目前使用的算法):
      • 用GC Root判断对象是否与之有关系链,有则可达,没有则不可达,不可达的对象可以回收。
      • 可以作为GC Root的对象:虚拟机栈中可用的对象;方法区静态变量;方法区常量;本地方法栈中可用的对象。
垃圾收集器
  1. serial 收集器(Client VM首选)
    • 单线程、stop the world、复制算法、主用于新生代。
  2. ParNew(Server VM首选)
    • 多线程、stop the world、新生代。
    • -XX:ParallelGCThreads参数控制垃圾收集的线程数量。
  3. Parallel Scavenge
    • 拥有ParNew的特点,专注于吞吐量。
    • 控制最大垃圾收集停顿时间:-XX:MaxGcPauseMillis(缩短将以牺牲吞吐量和新生代空间换取)。
    • 吞吐量大小:-XX:GCTimeRatio。
    • GC自适应调节策略:-XX:UseAdaptiveSizePolicy(与ParNew主要区别)。
  4. Serial Old(Client VM)
    • Serial的老年代版本。
  5. Parallel Old
    • Parallel Scavenge的老年代版本。
    • 注重吞吐量和CPU资源敏感的考虑:Parallel Scavenge + Parallel Old。
  6. CMS(Concurrent Mark Sweep)
    • 标记 - 清除,已获取最短GC时间为目标,使用注重响应快的服务。
    • 标记分为4步:
      • 初始标记:只标记GC Root直接关联的节点,stop the world,速度快,单线程。
      • 并发标记:根据步骤一中的节点继续追踪标记有关联的对象,并发运行,时间长,占用CPU资源。
      • 重新标记:修正步骤二期间对象引用有变动的部分,stop the world,多线程标记。
      • 并发清楚:跟用户代码并发运行。
    • 缺点:会产生垃圾碎片,并且不能等到老年代100%才进行回收,得预留给并发回收的时候同时新分配的内存。
    • jdk1.6后,CMS启动阈值为92%,可以通过参数:-XX:CMSInitiatingOccupancyFraction 设置阈值。
    • 如果发生Concurrent Mode Failure,则会启动后备收集器Serial Old,这时GC时间会很长。
    • -XX:CMSFullGcsBeforeCompaction:设置执行多少次不压缩的fullGc后,跟着来一次带压缩的整理。
  7. G1收集器(暂不整理)
jdk命令行工具
  1. jps:显示指定系统内所有的HotSpot虚拟机进程。
  2. jstat:用于收集HotSpot虚拟机各方面的运行数据。
    • 例:jstat -gcutil 3700 500 10 指收集进程号为3700的虚拟机的信息,每个500毫秒收集一次,总共收集10次。显示的参数值表示已使用的空间占比。
  3. jstack:用于生成虚拟机当前的线程快照信息,包含每一条线程的堆栈信息。该命令通常用于定位线程停顿原因,当出现线程停顿时,可通过stack查看每个线程的堆栈信息,进而分析停顿原因。
    • 例:jstack 3700
  4. jinfo:用于查看和修改虚拟机的各项参数信息。
    • 例:jinfo 3700
  5. jmap:可以产生堆dump文件,查询堆和持久代的详细信息等。导出的文件再用mat工具进行解析。
    • 例:$ jmap -dump:format=b,file=dump.tmp 3700
JVM常见问题分析
  1. 年老代堆空间被占满:
    • 异常: java.lang.OutOfMemoryError: Java heap space。
    • 说明:这是最典型的内存泄漏方式,简单说就是所有堆空间都被无法回收的垃圾对象占满,虚拟机无法再在分配新空间。
    • 解决:这种方式解决起来也比较容易,一般就是根据垃圾回收前后情况对比,同时根据对象引用情况(常见的集合对象引用)分析,基本都可以找到泄漏点。
  2. 持久代被占满:
    • 异常:java.lang.OutOfMemoryError: PermGen space。
    • 说明:Perm空间被占满。无法为新的class分配存储空间而引发的异常。这个异常以前是没有的,但是在Java反射大量使用的今天这个异常比较常见了。主要原因就是大量动态反射生成的类不断被加载,最终导致Perm区被占满。更可怕的是,不同的classLoader即便使用了相同的类,但是都会对其进行加载,相当于同一个东西,如果有N个classLoader那么他将会被加载N次。因此,某些情况下,这个问题基本视为无解。当然,存在大量classLoader和大量反射类的情况其实也不多。
    • 解决:-XX:MaxPermSize=16m。
  3. 堆栈溢出
    • 异常:java.lang.StackOverflowError。
    • 说明:一般就是递归没返回,或者循环调用造成。
  4. 线程堆栈满
    • 异常:Fatal: Stack size too small。
    • 说明:java中一个线程的空间大小是有限制的。JDK5.0以后这个值是1M。与这个线程相关的数据将会保存在其中。但是当线程空间满了以后,将会出现上面异常。
    • 解决:增加线程栈大小。-Xss2m。但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部分。
  5. 系统内存被占满
    • 异常:java.lang.OutOfMemoryError: unable to create new native thread。
    • 说明:这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时,除了要在Java堆中分配内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。分配给Java虚拟机的内存愈多,系统剩余的资源就越少,因此,当系统内存固定时,分配给Java虚拟机的内存越多,那么,系统总共能够产生的线程也就越少,两者成反比的关系。同时,可以通过修改-Xss来减少分配给单个线程的空间,也可以增加系统总共内生产的线程数。
    • 解决:1、重新设计系统减少线程数量。2、线程数量不能减少的情况下,通过-Xss减小单个线程大小。以便能生产更多的线程。
  6. 内存溢出和内存泄漏
    • 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
    • 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
    • OutOfMemoryError异常定位的步骤:
      • 使用jps和jmap导出堆存储快照,通过内存映像分析工具对dump 出来的堆转储快照进行分析。
      • 确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
      • 若内存中的对象必须活着,即不存在内存泄漏,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
      • 若内存中的对象不必要活着,即存在内存泄漏。可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置。
JVM性能调优的矛盾:吞吐量和低延迟
  1. 吞吐量优先:那么GC就必然会比较少进行垃圾回收,会达到一定程度才进行垃圾回收,相对的就需要花费更长的暂停时间来执行内存回收。可以使用Parallel Scavenge + Parallel Old垃圾收集器。
  2. 低延迟优先:会频繁地执行垃圾回收,又会导致程序吞吐量的下降。可以使用CMS垃圾收集器。
java堆外内存
  1. java本地存储对象的几种方式:堆内存、堆外内存和磁盘。堆外内存可以通过-XX:MaxDirectMemorySize设置,不设置的话默认跟堆内存一样。来自于java.nio。
  2. 相对于堆内存的优点:
    • 避免了垃圾回收GC的工作。
    • 减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。
  3. 创建(以Netty为例):Netty使用的堆外内存是Java NIO的DirectByteBuffer类。首先向Bits类申请额度,根据一个全局变量totalCapacity(记录堆外内存的总大小),判断是否有足够限额分配,批准的话调用sun.misc.Unsafe分配内存,返回内存基地址。额度不够则先进行GC后再分配,如果垃圾回收了100ms内存还不够,则抛OOM异常。
  4. 回收:只有在进行full GC的时候才会回收对外内存,可以主动调用System.gc来触发,但有不确定性。所以一般主动从DirectByteBuffer中取出sun.misc.Cleaner,然后调用其clean()方法即可。
  5. 适用场景:不适合存储很复杂的对象,一般简单的对象或者扁平化的结构比较适合。
类加载器

类加载机制(类加载过程和类加载器)

定位哪个类对资源占用最大

JVM调优之jstack找出最耗cpu的线程并定位代码

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值