JVM基础

==== =JVM= ====

1、JVM 运行时数据区
 1)程序计数器(Program Counter Register):当前线程所执行的字节码的行号 指示器,
       字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的 字节码指令,
       分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个 计数器来完成;
 2)Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作 数栈、动态链
       接、方法出口等信息;
 3)本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚 拟机栈是服务
      Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
 4)Java 堆(Java Heap):Java 虚拟机中内存大的一块,是被所有线程共享 的,几乎所有的对
       象实例都在这里分配内存;
 5)方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变 量、即时编译后
      的代码等数据。
2、什么是垃圾:没有任何引用指向的一个对象或者多个对象(循环引用)
3、如何定位垃圾
 引用计数(ReferenceCount)
 一个对象有几个引用就计数几,当去掉一个引用,计数器就减少1,当计数器为0,则被回收
 注:此算法对循环引用无效,A引用B B引用C C引用A,3个对象相互引用但没有一个引用指向3个  
        对象的整体
 根可达算法(RootSearching)
 线程栈变量、静态变量、常量池、JNI指针
4、常见的垃圾回收算法
 1)标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
 2)拷贝算法 (copying) - 没有碎片,浪费空间
 3)标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
5、 堆内存划分的空间

在这里插入图片描述

6、常见的垃圾回收器

①、新生代收集器:
1)Serial:单线程收集器采用复制算法,用 -XX:+UserSerialGC 来选择 Serial 作为新生代收集
器。 只用一条线程执行垃圾收集工作,所有的用户线程必须暂停
2)ParNew:Serial 的多线程版本,ParNew 在单核 CPU 环境并不会比 Serial 收集器达到更好的
效果,默认开启的收集线程数和 CPU 数量一致,通过 -XX:ParallelGCThreads 来设置垃圾收
集的线程数。当用户线程都执行到安全点时,所有线程暂停执行,ParNew 收集器以多线程,
采用复制算法进行垃圾回收,回收完用户线程继续开始执行。
适用场景:多核服务器;与 CMS 收集器搭配使用。当使用 -XX:+UserConcMarkSweepGC 来
选择 CMS 作为老年代收集器时,新生代收集器默认就是 ParNew,也可以用
-XX:+UseParNewGC 来指定使用 ParNew 作为新生代收集器。
3)Parallel Scavenge:多线程收集器。与 ParNew 的不同之处是ParNew 的目标是尽可能缩短
垃圾收集时用户线程的停顿时间,Parallel Scavenge 的目标是达到一个可控制的吞吐量。
适用场景:注重吞吐量,高效利用 CPU,需要高效运算且不需要太多交互。
可以使用 -XX:+UseParallelGC 来选择 Parallel Scavenge 作为新生代收集器,jdk7、jdk8 默认
使用 Parallel Scavenge 作为新生代收集器。
②、老年代收集器:
1)Serial Old:Serial Old 收集器是 Serial 的老年代版本,单线程收集器,采用标记-整理算法。
适用场景:Client 模式(桌面应用);单核服务器;与 Parallel Scavenge 收集器搭配;
作为 CMS 收集器的后备预案。
2)CMS:
① 初始标记:标记一下 GC Roots 能直接关联到的对象,速度较快。
② 并发标记:进行 GC Roots Tracing,标记出全部的垃圾对象,耗时较长。
③ 重新标记:修噶并发阶段程序继续运行而导致变化的对象的标记记录,耗时较短。
④ 并发清除:用标记-清除算法清除垃圾对象,耗时较长。
CMS 是基于标记-清除算法,所以垃圾回收后会产生空间碎片,可以通过
-XX:UserCMSCompactAtFullCollection 开启碎片整理(默认开启),在 CMS 进行 Full GC 之
前,会进行内存碎片的整理。还可以用 -XX:CMSFullGCsBeforeCompaction 设置执行多少次
不压缩(不进行碎片整理)的 Full GC 之后,跟着来一次带压缩(碎片整理)的 Full GC。
适用场景:重视服务器响应速度,要求系统停顿时间最短。可以使用
- XX:+UserConMarkSweepGC 来选择 CMS 作为老年代收集器。
3)Parallel Old:
Parallel Scavenge 的老年代版本,是一个多线程收集器,采用标记-整理算法。
适用场景:与Parallel Scavenge 收集器搭配使用;注重吞吐量。jdk7、jdk8 默认使用该收集器
作为老年代收集器,使用 -XX:+UseParallelOldGC 来指定使用 Paralle Old 收集器。
③、堆内存垃圾收集器:G1
G1 收集器是 jdk1.7 收集器,现在已经成为 jdk9 默认的收集器。G1 进行垃圾收集的范围是
整个堆内存,把整个堆内存划分为多个大小相等的独立区域(Region),在 G1 收集器中还保
留着新生代和老年代的概念,它们分别都是一部分 Region
① 初始标记:标记出 GC Roots 关联的对象,速度较快,需要停止用户线程,单线程执行。
② 并发标记:从 GC Root 开始对堆中的对象进行可达分析,找出存活对象,耗时较长,
可以和用户线程并发执行。
③ 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。
④ 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的
GC 停顿时间来指定回收计划,为了提高回收效率,采用停顿用户线程。
适用场景:要求尽可能可控 GC 停顿时间;内存占用较大的应用。
可以用 -XX:+UseG1GC 使用 G1 收集器,jdk9 默认使用 G1 收集器。
垃圾回收器的搭配:Serial + CMS、Serial + Serial Old、CMS + Serial Old、CMS + ParNew
ParNew + Serial Old、Parallel Scavenge + Serial Old
Parallel Scavenge + Parallel Old

7、堆和栈之间的区别:
 1)物理地址角度
      堆的物理地址分配对对象是不连续的性能慢。在GC的时候也要考虑到不连续的分配,
      所以有各种算法。比如,标记-消除,复制,标记-压缩,分代;
      栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的性能快。
 2)内存分别 
     堆是不连续的,所以分配的内存是在运行期确认的,大小不固定。
     栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
     一般堆大小远远大于栈。
 3)存放的内容 
     堆存放的是对象的实例和数组。关注的是数据的存储
     栈存放:局部变量,操作数栈,返回结果。关注的是程序方法的执行。
     ①、静态变量放在方法区
     ②、静态的对象还是放在堆。
 4)程序的可见度
      堆对于整个应用程序都是共享、可见的。
      栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
8、请简单描述一下垃圾回收器的基本原理是什么?还有垃圾回收器可以马上回收内存吗?并且有什么办法可以主动通知虚拟机进行垃圾回收呢?
 当对象创建时,GC就开始采用有向图的方式记录和管理堆(heap)中的所有对象监控对象的地址、
 大小以及使用情况。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一
 些对象为”不可达”时,GC就有责任回收这些内存空间。程序员可以手动执行System.gc(),通知
 GC运行,并不保证GC一定会执行。
9、深拷贝和浅拷贝区别
 浅拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对
               象的指针,不复制堆内存中的对象。
 深拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对
               象的指针和堆内存中的对象。
10、什么是分布式垃圾回收(DGC)?它是如何工作的?
   DGC叫做分布式垃圾回收。RMI(远程方法调用)使用DGC来做自动垃圾回收。
   因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。
   DGC使用引用计数算法来给远程对象提供自动内存管理
11、什么是类加载器?类加载器有哪些?什么是双亲委派模型
   获取类的二进制字节流的代码块叫做类加载器。
   ①、启动类加载器(Bootstrap ClassLoader):
         这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或被-Xbootclasspath参数所指定的
         路径中的,并且是虚拟机识别的(例如rt.jar)类库加载到虚拟机内存中;
   ②、扩展类加载器(Extension ClassLoader):它负责加载<JAVA_HOME>\lib\ext目录中的,或
         者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器;
   ③、应用程序类加载器(Application ClassLoader):由于这个类加载器是ClassLoader中的
         getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器。它负责加载用户类
         路径(claspath)上所指定的类库,开发者可以直接使用这个类加载器。
   ④、线程上下文类加载器,它是为了解决基础类掉调用回用户代码而引入的设计,
        也就是兼容双亲委派模型的缺陷。
  双亲委派如何被打破:重写loadClass
  ①、双亲委派模型的第一次“被破坏”是发生在双亲委派模型出现之前——即JDK1.2发布之前;
  ②、双亲委派模型的第二次“被破坏”是由于模型自身的缺陷导致的;
  ③、双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的,即热部署
12、对象的创建方式
   1)new关键字	调用了构造函数
   2)使用Class的newInstance方法	调用了构造函数
   3)使用Constructor类的newInstance方法	调用了构造函数
   4)使用clone方法	没有调用构造函数
   5)使用反序列化	没有调用构造函数
13、Java 中都有哪些引用类型?
   1)强引用:发生 gc 的时候不会被回收。
   2)软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
   3)弱引用:有用但不是必须的对象,在下一次GC时会被回收。
   4)虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference 实现虚引
        用,虚引用的用途是在 gc 时返回一个通知。
14、JVM的内存模型
   JVM 内存共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分。
   .class文件通过类加载器进入类装载系统,类加载器有bootstrap加载器主要加载rt.jar中的class;
   Extension加载器主要加载剩余的jar中的class;Application加载器主要加载classpath下的class;
   自定义类加载器。
  java类的加载机制
   Java类加载分为5个过程,分别为:加载,链接(验证,准备,解析),初始化,使用,卸载。
   ①、加载将.class文件通过二进制字节流读入到JVM中。在加载阶段,JVM需要完成3件事:
      1)通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存。
      2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
      3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
   ②、验证主要确保加载进来的字节流符合JVM规范。验证阶段会完成以下4个阶段的检验动作:
      1)文件格式验证
      2)元数据验证(是否符合Java语言规范) 3)字节码验证(确定程序语义合法,符合逻辑)
      4)符号引用验证(确保下一步的解析能正常执行)
      准备是连接阶段的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。
      解析是连接阶段的第三步,是虚拟机将常量池内的符号引用替换为直接引用的过程。
   ③、初始化主要是根据程序中的赋值语句主动为类变量赋值。当有继承关系时,先初始化父类再
          初始化子类,所以创建一个子类时其实内存中存在两个对象实例。
   ④、使用:程序之间的相互调用。
   ⑤、卸载:销毁一个对象,由JVM垃圾回收器完成。代码层面的销毁只是将引用置为null。
  描述一下class初始化过程?
   类初始化详细过程如下(最重要的是类初始化是线程安全的):
  1)每个类都有一个初始化锁LC,进程获取LC(如果没有获取到,就一直等待)
  2)如果C正在被其他线程初始化,释放LC并等待C初始化完成
  3)如果C正在被本线程初始化,即递归初始化,释放LC
  4)如果C已经被初始化了,释放LC
  5)如果C处于erroneous状态,释放LC并抛出异常NoClassDefFoundError
  6)否则,将C标记为正在被本线程初始化,释放LC;然后, 初始化那些final且为基础类型的
       类成员变量
  7)初始化C的父类SC和各个接口SI_n(按照implements子句中的顺序来) ;
       如果SC或SIn初始化过程中抛出异常,则获取LC,将C标记为erroneous,并通知所有线程,
       然后释放LC,然后再抛出同样的异常。
  8)从classloader处获取assertion是否被打开
  9)接下来, 按照文本顺序执行类变量初始化和静态代码块,或接口的字段初始化,
       把它们当作是一个个单独的代码块。
 10)如果执行正常,获取LC,标记C为已初始化,并通知所有线程,然后释放LC
 11)否则,如果抛出了异常E。
        若E不是Error,则以E为参数创建新的异常ExceptionInInitializerError作为E。
        如果因为OutOfMemoryError导致无法创建ExceptionInInitializerError,
        则将OutOfMemoryError作为E。
  12)获取LC,将C标记为erroneous,通知所有等待的线程,释放LC,并抛出异常E。
15、调优实例
  实例1:发现部分开发测试机器出现异常:java.lang.OutOfMemoryError: GC overhead limit exceeded,这个异常代表:GC为了释放很小的空间却耗费了太多的时间,其原因一般有两个:1,堆太小,2,有死循环或大对象;
  使用ps -ef |grep "java"查看,

发现:该应用的堆区设置只有768m,而机器内存有2g,机器上只跑这一个java应用,没有其他需要占用内存的地方。另外,这个应用比较大,需要占用的内存也比较多;

   实例2:一个服务系统,经常出现卡顿,分析原因,发现Full GC时间太长:

jstat -gcutil:
S0 S1 E O P YGC YGCT FGC F GCT GCT
12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993
分析上面的数据,发现Young GC执行了54次,耗时2.047秒,每次Young GC耗时37ms,在正常范围,而Full GC执行了5次,耗时6.946秒,每次平均1.389s,数据显示出来的问题是:Full GC耗时较长,分析该系统的是指发现,NewRatio=9,也就是说,新生代和老生代大小之比为1:9,这就是问题的原因:
1,新生代太小,导致对象提前进入老年代,触发老年代发生Full GC;
2,老年代较大,进行Full GC时耗时较大;
优化的方法是调整NewRatio的值,调整到4,发现Full GC没有再发生,只有Young GC在执行。这就是把对象控制在新生代就清理掉,没有进入老年代(这种做法对一些应用是很有用的,但并不是对所有应用都要这么做)
实例3:
一应用在性能测试过程中,发现内存占用率很高,Full GC频繁,使用sudo -u admin -H jmap -dump:format=b,file=文件名.hprof pid 来dump内存,生成dump文件,并使用Eclipse下的mat差距进行分析,发现:

从图中可以看出,这个线程存在问题,队列LinkedBlockingQueue所引用的大量对象并未释放,导致整个线程占用内存高达378m,此时通知开发人员进行代码优化,将相关对象释放掉即可。

16、内存溢出和内存泄漏的区别:
    内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,
    出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
    内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露
    危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
17、java哪些情况会导致gc_哪些情况会导致Full GC?
  1)System.gc()方法的调用 2)老年代不足 3)永久代不足
  4)concurrent mode failure
       concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代
       空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触
       发Full GC)。
       相关参数:
       -XX:+UseCMSInitiatingOccupancyOnly ,如果没有设置此参数,虚拟机会根据收集的数据决
       定是否触发(建议线上环境带上这个参数,不然会加大问题排查的难度)。
       -XX:CMSInitiatingOccupancyFraction=80,即老年代满80%时触发CMS GC。设置太高,就
       容易产生concurrent mode failure,设置过低,CMS GC又太过频繁。
       -XX:UseCMSCompactAtFullCollection=true,由于CMS没有对内存进行压缩,所以会有内存
       碎片,设置此参数,默认每次执行Full GC的时候会进行整理压缩,目前默认是true。
       -XX:CMSFullGCsBeforeCompaction=n,指定多少次不压缩的CMS GC刚才之后,跟着来一
       次带压缩的CMS GC。默认是0,表示每次发生forground的cms gc 都会进行压缩,但压缩会
       影响暂停时间,因此可以适当调整次参数。
  5)promotion failed
       minor gc时年轻代的存活区空间不足而晋升老年代,老年代又空间不足而触发full gc。
       相关参数: 
      -XX:SurvivorRatio=8,设置eden和survivor的比例,默认8:1。
      -XX:MaxTenuringThreshold=15,minor gc后存活的年轻代对象会晋升老年代,默认15。
  6)统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
      当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old 
      gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除
      了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括
      young gen,所以不需要事先触发一次单独的young GC)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软软的铲屎官

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值