JVM原理和深度调优

OutOfMemoryError:如果本地方法栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的本地方法栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

Jave堆----线程公用

=============

平时所说的java调优就是它

在JVM中,堆(heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数据对象分配内存的区域。

Java堆载虚拟机启动的时候就被创建,堆中储存了各种对象,这些对象被自动管理内存系统(Automatic Storage Management System,也即是常说的“Garbage Collector(垃圾回收器)”)所管理。这些对象无需、也无法显示地被销毁。

Java堆的容量可以是固定大小,也可以随着需求动态扩展,并在不需要过多空间时自动收缩。

Java堆所使用的内存不需要保证是物理连续的,只要逻辑上是连续的即可。

JVM实现应当提供给程序员调节Java 堆初始容量的手段,对于可动态扩展和收缩的堆来说,则应当提供调节其最大和最小容量的手段。

Java 堆异常:

OutOfMemoryError:如果实际所需的堆超过了自动内存管理系统能提供的最大容量时抛出。

方法区----线程公用

===========

方法区是可供各条线程共享的运行时内存区域。存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法

方法区在虚拟机启动的时候创建。

方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。

方法区在实际内存空间中可以是不连续的。

Java虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段,对于可以动态扩展和收缩方法区来说,则应当提供调节其最大、最小容量的手段。

Java 方法区异常:

OutOfMemoryError: 如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。

JVM内存分配

=======

内存分配其实真正来讲是有三种的、但对于JVM来说只有两种

  • 栈内存分配:

大家在调优的过程中会发现有个参数是-Xss 默认是1m,这个内存是栈内存分配, 在工作中会发现栈OutOfMemory Error内存溢出、就是因为它的内存空间不够了 一般情况下没有那么大的栈、除非你的一个方法里边有几十万行代码、一直往那压、不出,所以导致栈的溢出、栈的内存分配直接决定了你的线程数 、比如说你默认情况下是1m 、系统一共给你512m、那最高可以分配512个线程,再多系统分配不了啦、因为没有那么多的内存 、像tomcat、resin、jboss等、有个最大线程数、要根据这个来调、调个100万没有意义、分配不了那么大、调太少整个性能发挥不出来 ,调这个 、跟你的cpu有关系、需要找一个折中位置 、根据应用 、是IO密集型的还是CPU密集型的来调-Xss的值、它这里边主要保存了一些参数 、还有局部变量 、就比如说写代码、有开始有结束、这里边肯定定义了很多变量、比如:int x=1 y=0 只要在这方法内的都属于局部变量 、因为你要做运算、要把这东西存住、只有等程序结束的时候才能销毁,对于这种参数是不会产生线程安全问题、因为线程是私有的

  • 堆内存分配:

Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢

JVM原理与深度调优

jvm堆结构

======

JVM原理与深度调优

(图一)

1.Young(年轻代)

年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Old。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor区过来的对象。而且,Survivor区总有一个是空的。

2.Old(年老代)

年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。

3.Permanent:(持久代)

也叫方法区、用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是值指MethodArea,不属于Heap。

java堆结构和垃圾回收

============

JVM原理与深度调优

图(二)

Direct Momery 严格意义来说也算堆,它是一块物理内存、可以分为操作系统内存、是比较快的、不会走JVM 在java里边实现了内存映射、这样速度更快

CodeCache 放一些字节码、类的信息会放在里边

Permanent Generation space 方法区、严格意义来说也属于堆

Eden Space 区

Survivor Space区

Tenured Generation Old区(年老代)

JVM GC 管理

调优大部分调优的是怎么回收,Minor GC 回收Eden Space和 Survivor Space , Full GC回收所有区域

不管什么GC,回收过程中会出现暂停、回收过程中用户线程是不会工作的、这样就造成程序卡了 这是无法改变不了的事实、避免不了、不过可以优化暂停时间的长短

原则上不能出现Full GC 、所有区域都要跑一遍 、出现Full GC 应用就不可用

Jvm 堆配置参数

=========

1、-Xms初始堆大小

默认物理内存的64/1(<1GB),建议小于1G、可根据应用业务调节

2、-Xmx最大堆大小

默认物理内存的4/1(<1GB)、建议小于1G、实际中建议不大于4GB(否则会出现很多问题)

3、一般建议设置 -Xms= -Xmx

好处是避免每次在gc后、调整堆的大小、减少系统内存分配开销

4、整个堆大小=年轻代大小+年老代大小+持久代大小(Permanent Generation space区、也会被Full GC回收)

jvm新生代(young generation

========================

JVM原理与深度调优

图(三)

1、新生代=1个eden区和2个Survivor区

2、-Xmn 年轻代大小

设置年轻代大小、比如-Xmn=100m那么新生代就是100m,然后共享

3、-XX:NewRatio

年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。

4、-XX:SurvivorRatio

Eden区与Survivor区的大小比值,设置为8(默认是8) ,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10

比如新生代=100m,设置-XX:SurvivorRatio为8,那E =80m S0 =10m S1=10m(1/10)

5、用来存放JVM刚分配的Java对象

java老年代(tenured generation)

============================

JVM原理与深度调优

图(四)

1、老年代=整个堆-年轻代大小-持久代大小

年轻代就是上面讲的-xmn配置的参数、持久代参数默认是0

2、年轻代中经过垃圾回收没有回收掉的对象被复制到年老代。

就是这个对象收集完一次、发现被引用了、某个地方使用了、回收不掉才放进去,一般是多次回收、从E区回收过程中、先进S0或者S1、S0或者S1再回收一次、回收不掉再放到年老区

3、老年代存储对象比年轻代年龄大的多,而且不乏大对象。

对互联网企业来说、最常用的是"缓存"的对象比较多、缓存一般会用弱引用、但弱引用也不会轻易被回收的、除非是在整个堆的内存不够的情况下、防止你的内存宕机、强引用是和垃圾回收机制相关的。一般的,如果一个对象可以通过一系列的强引用引用到,那么就 说明它是不会被垃圾回收机制(Garbage Collection)回收的,

刚才说了缓存对象一般是弱引用、有些数据丢了是没关系的、只是提高你的系统性能才放到缓存里边去、但是如果有一天内存不够了 、缓存占了很大一部分对象、你不回收的话、你整个系统都不可用了、整个服务都不能用了、如果回收掉、我可以从数据库去取、可 能速 度慢点、但是我的服务可用性不会降低

比如说刚开始分配的对象 、这个对象暂定是OLD区、刚开始一部分内存区域被缓存占据了、一般情况下对于一个缓存的设计都有初始值、对于java来说、比较通用的缓存是可以自动伸缩的、

如图(四)整个OLD区50M有45M是被缓存占据了、不会被回收掉、那整个OLD区只有5M可以用了 、假如E区有40M 、S0 分配10M 、S1分配也是10M 、理想情况下、经过E区到S0、S1到老年代的大小不到1M、 那5M就够了、不会出现FULL GC 、也不会出现 内存溢出、一旦你的对象大于5M、比如10M的数据、 放不进去了、就会出现FULL gc 、FULL gc会把整个缓存全都收掉、瞬间缓存数据就没了、然后把10M的数据放进去、这就是弱引用、可以理解为这是一种服务降级、如果是强引用那就直接挂了

4、新建的对象也有可能直接进入老年代

4.1、大对象,可通过启动参数设置

-XX:PretenureSizeThreshold=1024(单位为字节,默认为0、也就是说所有的默认都在新生代)来代表超过多大时就不再新生代分配,而是直接在老年代分配

4.2、大的数组对象,切数组中无引用外部对象。

5、老年代大小无配置参数

java持久代(perm generation)

========================

1、持久代=整个堆-年轻代大小-老年代大小

2、-XX:PermSize 最小 -XX:MaxPermSize 最大

设置持久代的大小,一般情况推荐把-XX:PermSize设置成 -XX:MaxPermSize的值为相同的值,因为永久代大小的调整也会导致堆内存需要触发fgc。

3、存放Class、Method元信息,其大小与项目的规模、类、方法的数量有关。一般设置为128M就足够,设置原则是预留30%的空间

刚开始设置了128M、随着程序的运行、java有一个叫lib的地方放了很多类库、这个类库并不是所有的都加载的、只有在用的时候或者系统初始化的时候会加载一部分、比如已经占了100M了、但是随着业务的运行会动态去类

库里加、把一些Class文件通过反射的方式装进去、这样你的内存不断增大、达到128M以后就挂了、就会报方法区溢出、怎么做?调大到256M、然后监控、超过阈值再调大、简单方式是调大、另外JDK里边有一个GC可以回收

如果能接受停机、就调大,简单、快速、已解决问题为主

4、永久代的回收方式

4.1、常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收

比如一个常量=5 它的意义就是个值、如果回收、发现它没被引用就被回收了

4.2、对于无用的类进行回收,必须保证3点:

类跟常量不一样、一个类里边可能有好多东西、比如这个类引用那个类、

  • 类的所有实例都已经被回收

  • 加载类的ClassLoader已经被回收

  • 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

################################################################

jvm垃圾收集算法

=========

1、引用计数算法

每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。还有一个问题是如何解决精准计数。

这种方法现在已经不用了

2、根搜索算法

从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。

在java语言中,GC Roots包括:

虚拟机栈中引用的对象。

方法区中类静态属性实体引用的对象。

方法区中常量引用的对象。

本地方法栈中JNI引用的对象。

jvm垃圾回收算法

=========

1、复制算法(Copying)

JVM原理与深度调优

  • 复制算法采用从根集合扫描,并将存活对象复制到一块新的,没有使用过的空间中,这种算法当控件存活的对象比较少时,极为高效,但是带来的成本是需要一块内存交换空间用于进行对象的移动。

  • 此算法用于新生代内存回收,从E区回收到S0或者S1

从根集合扫描、就是刚才说的GC-Roots 收集算法、从它开始查你的引用、如果没有被引用、开始执行算法、并将存活对象复制到一块新的、(S0或者S1)

2、标记清除算法

JVM原理与深度调优

  • 标记-清除算法采用从根集合进行扫描,对存活的对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如图所示。

  • 标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片!

适合老生代去回收

JVM原理与深度调优

标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。

标记-整理算法是在标记清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

名词解释

=====

1、串行回收

gc单线程内存回收、会暂停使有用户线程

2、并行回收

收集是指多个GC线程并行工作,但此时用户线程是暂停的;所以,Seral是串行的,Paralle收集器是并行的,而CMS收集器是并发的。

3、并发回收

是指用户线程与GC线程同时执行(不一定是并行,可能交替,但总体上是在同时执行的),不需要停顿用户线程(其实在CMS中用户线程还是需要停顿的,只是非常短,GC线程在另一个CPU上执行)

串行回收要区分好并行回收和并发回收的区别,这地方非常关键、在选择GC的过程中根据应用场景来选择

JVM常见垃圾回收器

==========

JVM原理与深度调优

上图是HotSpot里的收集器,中间的横线表示分代,有连线表示可以组合使用。

年轻代区域有

Serial 串行

ParNew 并发

Parallel Scavenge 并行

年老代区域有

CMS

Serial Old

Parallel Old

G1目前还不成熟 、适合年轻代和年老代

Serial 回收器(串行回收器)

==================

JVM原理与深度调优

是一个单线程的收集器,只能使用一个CPU或一条线程区完成垃圾收集;在进行垃圾收集时,必须暂停所有其它工作线程,直到收集完成。

缺点:Stop-The-World

优势:简单。对于单CPU的情况,由于没有多线程交互开销,反而可以更高效。是Client模式下默认的新生代收集器。

新生代Serial回收器

1、通过-XX:+UseSerialGC来开启

Serial New+Serial Old的收集器组合进行内存回收

2、使用复制算法。

3、独占式的垃圾回收。

一个线程进行GC,串行。其它工作线程暂停。

老年代Serial回收器

1、-XX:UseSerialGC来开启

Serial New+Serial Old的收集器组合进行内存回收

2、使用标记-压缩算法

3、串行的、独占式的垃圾回收器。

因为内存比较大的原因,回收比新生代慢

ParNew回收器(并行回收器)

================

JVM原理与深度调优

并行回收器也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的CPU上,它产生的停顿时间要短

于串行回收器,而在单CPU或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。

新生代ParNew回收器

============

1、-XX:+UseParNewGC开启

新生代使用并行回收收集器,老年代使用串行收集器

2、-XX:ParallelGCThreads 指定线程数

默认最好与CPU数理相当,避免过多的线程数影响垃圾收集性能

3、使用复制算法。

4、并行的、独占式的垃圾回收器。

新生代Parallel Scavenge回收器

=======================

1、吞吐量优先回收器

关注CPU吞吐量,即运行用户代码的时间/总时间。比如:JVM运行100分钟,其中运行用户代码99分钟,垃圾回收1分钟。则吞吐量是99%,这种收集器能最高效率的利用CPU,适合运行后台运算

2、-XX:+UseParallelGC开启

使用Parallel Scavenge+Serial Old收集器组合回收垃圾,这也是Server模式下的默认值

3、-XX:GCTimeRation

来设置用户执行时间占总时间的比例,默认99,即1%的时间用来进行垃圾回收

4、-XX:MaxGCPauseMillis

设置GC的最大停顿时间

5、使用复制算法

老生代Parallel Old回收器

==================

1、-XX:+UseParallelOldGC开启

使用Parallel Scavenge +Parallel Old组合收集器进行收集

2、使用标记整理算法。

3、并行的、独占式的垃圾回收器。

CMS(并发标记清除)回收器

==============

JVM原理与深度调优

运作过程分为4个阶段:

初始标记(CMS inital mark):值标记GC Roots能直接关联到的对象。

并发标记(CMS concurrent mark):进行GC RootsTracing的过程。

重新标记(CMS remark):修正并发标记期间用户程序继续运行而导致标记发生改变的那一部分对象的标记.

并发清除(CMS concurrent sweep):

其中标记和重新标记两个阶段仍然需要Stop-The-World,整个过程中耗时最长的并发标记和并发清除过程中收集器都可以和用户线程一起工作

CMS(并发标记清除)回收器

===============

1、标记-清除算法

同时它又是一个使用多线程并发回收的垃圾收集器

2、-XX:ParalleCMSThreads

手工设定CMS的线程数量,CMS默认启动的线程数是(ParallelGCTherads+3)+3/4)

这是它的公式,一般情况下、对于IO密集型的 cpu的核数乘以2+1 ,CPU密集型的一般CPU的核数+1

3、-XX+UseConcMarkSweepGC开启

使用ParNew+CMS+Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure” 失败后的后备收集器使用.

失败以后就会触发Full GC 、位了避免这种情况发生、就要对它进行配置、触发Full GC有两种情况、promotion failed和concurrent mode failure

对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能

会触发Full GC。

promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在

执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。

对应措施为:增大survivor space、老年代空间或调低触发并发GC的比率。

4、

如何快速更新自己的技术积累?

  • 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
  • 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
  • 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
  • 学习以后不知道有没有学成,则可以通过面试去检验。

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目

八年CRUD,疫情备战三个月,三面头条、四面阿里拿offer面经分享

八年CRUD,疫情备战三个月,三面头条、四面阿里拿offer面经分享

ull GC。

promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在

执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。

对应措施为:增大survivor space、老年代空间或调低触发并发GC的比率。

4、

如何快速更新自己的技术积累?

  • 在现有的项目里,深挖技术,比如用到netty可以把相关底层代码和要点都看起来。
  • 如果不知道目前的努力方向,就看自己的领导或公司里技术强的人在学什么。
  • 知道努力方向后不知道该怎么学,就到处去找相关资料然后练习。
  • 学习以后不知道有没有学成,则可以通过面试去检验。

我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!

以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目

[外链图片转存中…(img-NbABU6RG-1723296057888)]

[外链图片转存中…(img-IvzAYjL4-1723296057889)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 15
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值