Java面试知识点(全)

编程入门 行业动态 更新时间:2024-10-05 11:19:10

Java面试<a href=https://www.elefans.com/category/jswz/34/1770093.html style=知识点(全)"/>

Java面试知识点(全)

Java面试知识点(全)
注:随时更新

文章目录

  • JVM内存结构
    • 内存结构
    • 程序计数器
    • 虚拟机栈
    • 本地方法栈
  • JAVA堆
  • JVM GC过程
  • GC执行机制(回收器)
  • JVM判断对象是否可以被回收算法
  • 根搜索算法是现在GC使用的搜索算法。
  • 回收算法
    • 标记-清除算法(Mark-Sweep)
    • 复制算法(Copying)
    • 标记-整理算法(Mark-Compact)
  • 分代回收算法(Generational Collecting)
    • 1、简要说明
    • 2、分代回收的GC类型
    • 3、触发时机
    • 4、年轻代GC过程
    • 5、年老代GC
  • JVM问题&优化
    • 容易导致Full GC的情况
    • JVM堆内存分配问题讨论
    • 调优策略
  • JVM常见配置选项
  • JVM 发生OOM情况
  • JVM优化
    • 堆大小设置
    • 回收器选择
  • 辅助信息
  • 常见配置汇总
  • 调优总结
  • 外传

JVM内存结构

内存结构

Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有的用途以及创建销毁的时机,其中蓝色部分代表的是所有线程共享的数据区域,而绿色部分代表的是每个线程的私有数据区域。

  • 方法区(Method Area):
    方法区属于线程共享的内存区域,又称Non-Heap(非堆),主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool)的区域,它主要用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后存放到运行时常量池中,以便后续使用。

  • JVM堆(Java Heap):
    Java 堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,注意Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做GC 堆,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

  • 程序计数器(Program Counter Register):
    属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

  • 虚拟机栈(Java Virtual Machine Stacks):
    属于线程私有的数据区域,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。每个方法从调用直结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程,如下(图有误,应该为栈桢):
    解GC机制之前,需要首先搞清楚JAVA程序在执行的时候,内存究竟是如何划分的。
    私有内存区的区域名称和相应的特性如下表所示:

区域名称 特性

程序计数器

指示当前程序执行到了哪一行,执行JAVA方法时纪录正在执行的虚拟机字节码指令地址;执行本地方法时,计数器值为undefined

虚拟机栈

用于执行JAVA方法。栈帧存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。程序执行时栈帧入栈;执行完成后栈帧出栈

本地方法栈

用于执行本地方法,其它和虚拟机栈类似
着重说一下虚拟机栈中的局部变量表,里面存放了三个信息:
各种基本数据类型(boolean、byte、char、short、int、float、long、double)
对象引用(reference)
returnAddress地址
这个returnAddress和程序计数器有什么区别?前者是指示JVM的指令执行到哪一行,后者则是你的代码执行到哪一行。
私有内存区伴随着线程的产生而产生,一旦线程中止,私有内存区也会自动消除,因此我们在本文中讨论的内存回收主要是针对共享内存区。下面介绍一下共享内存区。

JAVA堆

JAVA虚拟机管理的内存中最大的一块,所有线程共享,几乎所有的对象实例和数组都在这类分配内存。GC主要就是在JAVA堆中进行的。

方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。但是已经被最新的 JVM 取消了。现在,被加载的类作为元数据加载到底层操作系统的本地内存区。

Java堆
既然GC主要发生在堆内存中,这部分我们会对堆内存进行比较详细的描述。
堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。堆是应用程序在运行期请求操作系统分配给自己的向高地址扩展的数据结构,是不连续的内存区域。用一句话总结堆的作用:程序运行时动态申请某个大小的内存空间。

新生代:
刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。
至于为什么新生代要分出两个survivor区,在我的另一篇博客中有详细介绍为什么新生代内存需要有两个Survivor区

老年代:如果某个对象经历了几次垃圾回收之后还存活,就会被存放到老年代中。老年代的空间一般比新生代大。

GC名称 介绍
Minor GC 发生在新生代,频率高,速度快(大部分对象活不过一次Minor GC)
Major GC 发生在老年代,速度慢
Full GC 清理整个堆空间
不过实际运行中,Major GC会伴随至少一次 Minor GC,因此也不必过多纠结于到底是哪种GC(在有些资料中看到把full GC和Minor GC等价的说法)。
那么,当我们创建一个对象后,它会被放在堆内存的哪个部分呢?

如果Major GC之后还是老年代不足,那苍天也救不了了。。。。JVM会抛出内存不足的异常

JVM GC过程

  • 什么时候?
    eden满了minor gc;
    升到老年代的对象大于老年代剩余空间full gc;
    系统在不可预测的时间调用System.gc()函数gc;
    gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制和新生代老年代比例,通过 MaxTenuringThreshold控制进入老年前生存次数
  • 对什么东西?
    超出了作用域或引用计数为空的对象;
    从gc root开始搜索找不到的对象,而且经过一次标记、清理,仍然没有复活的对象
  • 做什么?
    删除不使用的对象,回收内存空间;
    运行默认的finalize释放资源,当然程序员想立刻调用就用dipose调用以释放资源如文件句柄,JVM用from survivor、to survivor对它进行标记清理,对象序列化后也可以使它复。
    包含GC执行机制、CMS收集器、分代收集、回收算法和优劣势

GC执行机制(回收器)

1、串行GC
在搜索扫描和复制过程都是采用单线程实现,适用于单CPU、新生代空间较小或者要求GC暂停时间要求不高的地方。是client级别的默认方式。
2、并行GC
在搜索扫描和复制过程都是采用多线程实现,适用于多CPU、或者要求GC暂停时间要求高的地方。是server级别的默认方式。
3、同步GC
同时允许多个GC任务,减少GC暂停时间。主要应用在实时性要求重于总体吞吐量要求的中大型应用,即使如此,降低中断时间的技术还是会导致应用程序性能的降低。

回收器概述年轻代年老代
串行回收器(serial collector)客户端模式的默认回收器,所谓串行,指的是单线程回收,回收时将会暂停所有的应用线程的执行适用Serial old回收器标记-清除-合并,标记所有存活对象,从头遍历堆,清除所有死亡对象,最后把存活对象移动到堆的前端,堆的后端就空了
并行回收器服务器模式的默认回收器,利用过多个线程进行垃圾回收,充分利用CPU,回收期间暂停所有应用线程Parallel Scavenge回收器,关注可控制的吞吐量,吞吐量越大,垃圾回收时间越短Parrellel old回收器,多线程,同样采用“标记-清除-合并”,特点是吞吐量优先
CMS回收器停顿时间最短,步骤:1初始标记;2并发标记;3重新标记;4并发清除。优点是停顿时间段,并发回收,缺点是无法处理浮动垃圾,而且会导致空间碎片不适用适用
G1回收器将堆内存划分为多个等大的区域,按照每个区域进行回收。步骤: 1初始标记;2并发标记;3最终标记;4筛选回收。特点是并行并发,分代收集,不会导致空间碎片,也可以由编程者自主确定停顿时间上限适用适用

JVM判断对象是否可以被回收算法

  • 1.1 引用计数法
    每个对象创建的时候,会分配一个引用计数器,当这个对象被引用的时候计数器就加1,当不被引用或者引用失效的时候计数器就会减1。任何时候,对象的引用计数器值为0就说明这个对象不被使用了,就认为是“垃圾”,可以被GC处理掉。
    评价:
    【优点】算法实现简单。
    【缺点】不能解决对象之间循环引用的问题。有垃圾对象不能被正确识别,这对垃圾回收来说是很致命的,所以GC并没有使用这种搜索算法。
  • 1.2 根搜索算法
    以一些特定的对象作为基础原始对象,或者称作“根”,不断往下搜索,到达某一个对象的路径称为引用链。
    如果一个对象和根对象之间有引用链,即根对象到这个对象是可到达的,则这个对象是活着的,不是垃圾,还不能回收。例如,假设有根对象O,O引用了A对象,同时A对象引用了B对象,B对象又引用了C对象,那么对象C和根对象O之间的路径的可达的,C对象就不能当做垃圾对象。引用链为O->A->B->C。
    反之,如果一个对象和根对象之间没有引用链,根对象到这个对象的路径是不可达的,那么这个对象就是可回收的垃圾对象。
    评价:
    【优点】可找到所以得垃圾对象,并且完美解决对象之间循环引用的问题。
    【缺点】不可避免地要遍历全局所有对象,导致搜索效率不高。

根搜索算法是现在GC使用的搜索算法。

可以当做GC roots的对象有以下几种:
虚拟机栈中的引用的对象。(java栈的栈帧本地变量表)
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。(声明为final的常量对象)
本地方法栈中JNI的引用的对象。(本地方法栈的栈帧本地变量表)
下面是从网上找来的图,将就看看:GC ROOTS就是跟对象节点,蓝色的是可达的引用链,引用链上的对象是活着的,不能被当做垃圾对象回收。相反暗灰色的路径表示不可达的路径,这些对象将会被回收。每个圈圈里面的数字,表示其被引用的次数,没错,就是上面说到的引用计数法的计数值。

回收算法

这里讨论的是oracle的Hotspot VM常见的垃圾回收算法。使用的搜索算法都是基于根搜索算法实现的。

标记-清除算法(Mark-Sweep)

该算法分两步执行:
1) 标记Mark:从GC ROOTS开始,遍历堆内存区域的所有根对象,对在引用链上的对象都进行标记。这样下来,如果是存活的对象就会被做了标记,反之如果是垃圾对象,则没做有标记。GC很容易根据有没有被做标记就完成了垃圾对象回收。
2) 清除Sweep:遍历堆中的所有的对象(标记阶段遍历的是所有根节点),找到未被标记的对象,直接回收所占的内存,释放空间。
评价:
【优点】没有产生额外的内存空间消耗,内存利用率高。
【缺点】效率低,清除阶段要遍历所有的对象;回收的垃圾对象是在各个角落的,直接回收垃圾对象,导致存在不连续的内存空间,产生内存碎片。
标记-清除算法操作的对象是【垃圾对象】,对于活着的对象(被标记的对象),它则直接不理睬。

复制算法(Copying)

复制算法把内存区间一分为二,有对象存在的一半区间称为“活动区间”,没有对象存在处于空闲状态的空间则为“空闲区间”。
当内存空间不足时触发GC,先采用根搜索算法标记对象,然后把活着的对象全部复制到另一半空闲区间上,复制算法的“复制”就来自这一操作。复制到另一半区间的时候,严格按照内存地址依次排列要存放的对象,然后一次性回收垃圾对象。
这样原来的空闲区间在GC后就变成活动区间,而且内存顺序齐整美观。原来的活动区间在GC后就变成了完全空的空闲区间,等待下一次GC把活的对象被copy进来。
评价:
【优点】GC后的内存齐整,不产生内存碎片。
【缺点】GC要使用两倍的内存,或者说导致堆只能使用被分配到的内存的一半,这个算法对空间要求太高!如果存活的对象较多,则意味着要复制很多对象并且要维护大量对象的内存地址,所以存活的对象数量不能太多,否则效率也会很低。
复制算法复制移动的对象是【活着的对象】,对于垃圾对象(不被标记的对象)则直接回收。

标记-整理算法(Mark-Compact)

这个算法则是对上面两个算法的综合结果。也分为两个阶段:
1)标记:这个阶段和标记-清除Mark-Sweep算法一样,遍历GC ROOTS并标记存活的对象。
2)整理:移动所有活着的对象到内存区域的一侧(具体在哪一侧则由GC实现),严格按照内存地址次序依次排列活着的对象,然后将最后一个活着的对象地址以后的空间全部回收。
评价:
【优点】内存空间利用率高,消除了复制算法内存减半的情况;GC后不会产生内存碎片。
【缺点】需要遍历标记活着的对象,效率较低;复制移动对象后,还要维护这些活着对象的引用地址列表。

分代回收算法(Generational Collecting)

分代回收算法就是现在JVM使用的GC回收算法。

1、简要说明

  • 1)先来看看简单化后的堆的内存结构:
    Java堆 = 年老代 + 年轻代
    (空间大小比例一般是3:1)
    年轻代 = Eden区 + From Space区 + To Space区
    (空间大小比例一般是8:1:1)
  • 2)按照对象存活时间长短,我们可以把对象简单分为三类:
    短命对象:存活时间较短的对象,如中间变量对象、临时对象、循环体创建的对象等。这也是产生最多数量的对象,GC回收的关注重点。
    长命对象:存活时间较长的对象,如单例模式产生的单例对象、数据库连接对象、缓存对象等。
    长生对象:一旦创建则一直存活,几乎不死的对象。
  • 3)对象分配区域
    短命对象存在于年轻代,长命对象存在于年老代,而长生对象则存在于方法区中。
    由于GC的主要内存区域是堆,所以GC的对象主要就是短命对象和长命对象这类寿命“有限”的对象。

2、分代回收的GC类型

针对HotSpot VM的的GC其实准确分类只有两大种:
1)Partial GC:部分回收模式
Young GC:只收集young gen的GC。和Minor GC一样。
Old GC:只收集old gen的GC。只有CMS的concurrent - collection是这个模式
Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式
2)Full GC:收集整个堆,包括young gen、old gen,还有永久代perm gen(如果存在的话)等所有部分的模式。同Major GC。

3、触发时机

HotSpot VM的串行GC的触发条件是:
young GC:当young gen中的eden区分配满的时候触发。
Full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC;或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。
并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。

4、年轻代GC过程

当需要在堆中创建一个新的对象,而年轻代内存不足时触发一次GC,在年轻代触发的GC称为普通GC,Minor GC。注意到年轻代中的对象都是存活时间较短的对象,所以适合使用复制算法。这里肯定不会使用两倍的内存来实现复制算法了,牛人们是这样解决的,把年轻代内存组成是80%的Eden、10%的From Space和10%的To Space,然后在这些内存区域直接进行复制。

刚开始创建的对象是在Eden中,此时Eden中有对象,而两个survivor区没有对象,都是空闲区间。第一次Minor GC后,存活的对象被放到其中一个survivor,Eden中的内存空间直接被回收。在下一次GC到来时,Eden和一个survivor中又创建满了对象,这个时候GC清除的就是Eden和这个放满对象的survivor组成的大区域(占90%),Minor GC使用复制算法把活的对象复制到另一个空闲的survivor区间,然后直接回收之前90%的内存。周而复始。始终会有一个10%空闲的survivor区间,作为下一次Minor GC存放对象的准备空间。
要完成上面的算法,每次Minor GC过程都要满足:
存活的对象大小都不能超过survivor那10%的内存空间,不然就没有空间复制剩下的对象了。但是,万一超过了呢?前面我们提到过年老代,对,就是把这些大对象放到年老代。

5、年老代GC

什么样的对象可以进入年老代呢?如下:
在年轻代中,如果一个对象的年龄(GC一次后还存活的对象年岁加1)达到一个阈值(可以配置),就会被移动到年老代。
Survivor中相同年龄的对象大小总和超过survivor空间的一半,则不小于这个年龄的对象都会直接进入年老代。
创建的对象的大小超过设定阈值,这个对象会被直接存进年老代。
年轻代中大于survivor空间的对象,Minor GC时会被移进年老代。
年老代中的对象特点就是存活时间较长,而且没有备用的空闲空间,所以显然不适合使用复制算法了,这个时候使用标记-清除算法或者标记-整理算法来实现GC。负责年老代中GC操作的是全局GC,Major GC,Full GC。
什么时候触发Major GC(主要/重要的)呢?
在Minor GC时,先检测JVM的统计数据,查看历史上进入老年代的对象平均大小是否大于目前年老代中的剩余空间,如果大于则触发Full GC。
老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
MajorGC采用标记—清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。
当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

JVM问题&优化

内存调优
JVM内存调优,主要是减少GC的频率和减少Full GC的次数,Full GC的时候会极大地影响系统的性能。所以在此基础上,更加要关注会导致Full GC的情况。

容易导致Full GC的情况

年老代空间不足
1)分配足够大空间给old gen。
2)避免直接创建过大对象或者数组,否则会绕过年轻代直接进入年老代。
3)应该使对象尽量在年轻代就被回收,或待得时间尽量久,避免过早的把对象移进年老代。

方法区的永久代空间不足
1)分配足够大空间给。
2)避免创建过多的静态对象。
被显示调用System.gc()
通常情况下不要显示地触发GC,让JVM根据自己的机制实现。

JVM堆内存分配问题讨论

  • 年轻代过小(年老代过大)
    导致频繁发生GC,增大系统消耗
    容易让普通大文件直接进入年老代,从而更容易诱发Full GC。
  • 年轻代过大(年老大过小)
    导致年老代过小,从而更容易诱发Full GC。
    GC耗时增加,降低GC的效率。
  • Eden过大(survivor过小)
    Minor GC时容易让普通大文件直接绕过survivor进入年老代,从而更容易诱发Full GC。
  • Eden过小(survivor过大)
    导致GC频率升高,影响系统性能。

调优策略

保证系统吞吐量优先
减少GC暂停时间优先

JVM常见配置选项

总结一下常见配置。
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

JVM 发生OOM情况

  • 1、Java堆溢出:heap
    Java堆内存主要用来存放运行过程中所以的对象,该区域OOM异常一般会有如下错误信息;
    java.lang.OutofMemoryError:Javaheap space
    此类错误一般通过Eclipse Memory Analyzer分析OOM时dump的内存快照就能分析出来,到底是由于程序原因导致的内存泄露,还是由于没有估计好JVM内存的大小而导致的内存溢出。
    另外,Java堆常用的JVM参数:
    -Xms:初始堆大小,默认值为物理内存的1/64(<1GB),默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
    -Xmx:最大堆大小,默认值为物理内存的1/4(<1GB),默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
    -Xmn:年轻代大小(1.4or lator),此处的大小是(eden + 2 survivor space),与jmap -heap中显示的New gen是不同的。

  • 2、栈溢出:stack
    栈用来存储线程的局部变量表、操作数栈、动态链接、方法出口等信息。如果请求栈的深度不足时抛出的错误会包含类似下面的信息:
    java.lang.StackOverflowError
    另外,由于每个线程占的内存大概为1M,因此线程的创建也需要内存空间。操作系统可用内存-Xmx-MaxPermSize即是栈可用的内存,如果申请创建的线程比较多超过剩余内存的时候,也会抛出如下类似错误:
    java.lang.OutofMemoryError: unable to create new native thread
    相关的JVM参数有:
    -Xss: 每个线程的堆栈大小,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.
    在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

  • 3、运行时常量溢出 constant
    运行时常量保存在方法区,存放的主要是编译器生成的各种字面量和符号引用,但是运行期间也可能将新的常量放入池中,比如String类的intern方法。
    如果该区域OOM,错误结果会包含类似下面的信息:
    java.lang.OutofMemoryError: PermGen space
    相关的JVM参数有:
    -XX:PermSize:设置持久代(perm gen)初始值,默认值为物理内存的1/64
    -XX:MaxPermSize:设置持久代最大值,默认为物理内存的1/4

  • 4、方法区溢出 directMemory
    方法区主要存储被虚拟机加载的类信息,如类名、访问修饰符、常量池、字段描述、方法描述等。理论上在JVM启动后该区域大小应该比较稳定,但是目前很多框架,比如Spring和Hibernate等在运行过程中都会动态生成类,因此也存在OOM的风险。
    如果该区域OOM,错误结果会包含类似下面的信息:
    java.lang.OutofMemoryError: PermGen space
    相关的JVM参数可以参考运行时常量。
    另外,在定位JVM内存问题的时候可以借助于一些辅助信息:

1、日志相关
-XX:+PrintGC:输出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails:输出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps:打印GC停顿耗时
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间.
-XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
-Xloggc:filename:把相关日志信息记录到文件以便分析.
2、错误调试相关:
-XX:ErrorFile=./hs_err_pid.log:如果JVM crashed,将错误日志输出到指定文件路径。
-XX:HeapDumpPath=./java_pid.hprof:堆内存快照的存储文件路径。
-XX:-HeapDumpOnOutOfMemoryError:在OOM时,输出一个dump.core文件,记录当时的堆内存快照
3、类装载相关
-XX:-TraceClassLoading:打印class装载信息到stdout。记Loaded状态。
-XX:-TraceClassUnloading:打印class的卸载信息到stdout。记Unloaded状态。

JVM优化

堆大小设置

JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
典型设置:
1、java-Xmx3550m -Xms3550m -Xmn2g-Xss128k
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
2、java -Xmx3550m -Xms3550m -Xss128k-XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

回收器选择

JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

  • 吞吐量优先的并行收集器
    如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
    典型配置:
    1、java -Xmx3800m -Xms3800m -Xmn2g -Xss128k-XX:+UseParallelGC -XX:ParallelGCThreads=20
    -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
    -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
    2、java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20-XX:+UseParallelOldGC
    -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
    3、java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC-XX:MaxGCPauseMillis=100
    -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
    4、java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100-XX:+UseAdaptiveSizePolicy
    -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

– 响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置:
1、java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
2、java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC-XX:CMSFullGCsBeforeCompaction=5-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

辅助信息

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
-XX:+PrintGC
输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails
输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps-XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
输出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息

输出形式:

34.702: [GC {Heap before gc invocations=7:
def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 99% used[0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K, 55% used[0x221d0000, 0x22527e10, 0x227d0000)to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 3% used[0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 0% used[0x1ebd0000, 0x1ebd0000, 0x21bd0000)from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 4% used[0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]

-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。

常见配置汇总

1、堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
2、收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
3、垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
4、并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
5、并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

调优总结

1、年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
2、年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
3、较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压

外传

😜 原创不易,如若本文能够帮助到您的同学
🎉 支持我:关注我+点赞👍+收藏⭐️
📝 留言:探讨问题,看到立马回复
💬 格言:己所不欲勿施于人 扬帆起航、游历人生、永不言弃!🔥

更多推荐

Java面试知识点(全)

本文发布于:2024-02-07 05:31:19,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1753550.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:知识点   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!