JVM常见知识点汇总:

 

围绕JVM内存空间有三个重要的组成部分:

       类加载器子系统(Class类文件内部结构怎样?JVM何时加载类?如何加载类?)

      字节码执行子引擎 (程序运行时帧栈结构?方法调用时如何支持多态?执行引擎如何工作?)

       垃圾回收器GC(如何确定对象可回收?哪些垃圾回收方法?何时进行垃圾回收?)

 

1.JVM的内存结构:

        JVM内存结构主要有:堆内存、方法区、栈、程序计数器。方法区和堆是所有线程共享的内存区域;而java虚拟机栈、本地方法栈、程序计数器是运行时线程私有的内存区域。

       堆内存是JVM中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。可以细分为:新生代和老年代。默认大小比例为Eden:Survivor =8:1,因为年轻代中的对象基本都是朝生夕死的(80%以上) ,所以在年轻代的垃圾回收算法使用的是复制算法,将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

        一般情况下,新创建的对象都会被分配到Eden区,这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

       方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。为与Java堆区分,方法区还有一个别名Non-Heap(非堆);

       栈又分为java虚拟机栈和本地方法栈,主要用于方法的执行。

       程序计数器是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器。

 

2.java GC是在什么时候,对什么东西,做了什么事情?

       在什么时候:1.新生代有一个Eden区和两个survivor区,首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放,如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中,然后清空Eden和之前的那个survivor区的内存。在某次GC过程中,如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去。2.大对象以及长期存活的对象直接进入老年区。3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析,如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大小,那么执行一次Full GC以尽可能地获得老年区的空间。

       对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象。

       做什么: 分代收集算法,对于新生代:复制清理; 老年代:标记-清除和标记-压缩算法; 永久代:存放Java中的类和加载类的类加载器本身。

 

3.JVM如何判断一个对象是否该被GC,可以视为root的都有哪几种类型。

        引用计数法:每个对象都有一个计数器,当这个对象被引用时计数器+1,不被引用时-1,当这个计数器为0时,该对象为无效对象。

        可达性分析法:通过根对象来判断,如果从一个对象没有到达根对象的路径,或者说从根对象开始无法引用到该对象,该对象就是不可达的。

       -->可以视为root的类型:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象(final 的常量值)、本地方法栈引用的对象。

        如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?--->不会,在下一个垃圾回收周期中,这个对象将是可被回收的。

3 GC常见算法:

          GC的常见算法:引用计数法、复制算法、标记清除算法、标记整理算法。

         CMS是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。过程分为六步:①初始标记 :虚拟机停顿正在执行的任务,从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记;②并发标记 :在初始标记的基础上继续向下追溯标记。应用程序的线程和并发标记的线程并发执行,用户不会感受到停顿。③并发预清理 :虚拟机查找在执行并发标记阶段新进入老年代的对象。通过重新扫描,减少下一个阶段"重新标记"的工作。④重新标记 :暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。⑤并发清理 :清理垃圾对象,收集器线程和应用程序线程并发执行⑥并发重置 :重置CMS收集器的数据结构,等待下一次垃圾回收。

         CMS的重新标记和初始标记两个阶段是Stop the world的。

         优点:并发收集、低停顿

          缺点:1)对CPU资源非常敏感;2)无法处理浮动垃圾,可能出现“concurrent mode failure”失败而导致另一次full gc的产生;3)基于标记-清除算法实现,结束后会产生内存碎片。

         G1的垃圾回收过程:①初始标记:暂停阶段。扫描根集合,标记所有从根集合可直接到达的对象并将它们的字段压入扫描栈中等到后续扫描。②并发标记:并发阶段。不断从扫描栈取出引用递归扫描整个堆里的对象图。每扫描到一个对象就会对其标记,并将其字段压入扫描栈。重复扫描过程直到扫描栈清空。③最终标记:暂停阶段。在完成并发标记后,每个Java线程还会有一些剩下的SATB write barrier记录的引用尚未处理。这个阶段就负责把剩下的引用处理完。④清理:暂停阶段。清点和重置标记状态。

          G1的优势:①并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间;②分代收集:能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。③空间整合:G1从整体看来是基于“标记-整理”算法实现的收集器,从局部(两个Region之间)上看是基于“复制”算法实现,无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。④可预测的停顿: G1能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

 

4.标记清除、标记整理、复制算法:

      标记清除算法:分为标记阶段和清除阶段,找到所有可访问的对象,做个标记,然后遍历堆,把未被标记的对象回收。

           优点:可以解决循环引用的问题,必要时才进行回收;

           缺点:回收时应用需要挂起,效率比较低,会造成内存碎片(空间碎片太多可能导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作)。

 

        复制算法:将可用内存按照容量划分为大小相同的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。【一般使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用coping算法进行拷贝时效率比较高。】

        优点:实现简单,运行高效,不会产生内存碎片

        缺点:浪费一半内存,内存代价高

 

        标记整理算法:标记过程与"标记-清除"算法一样,但后续不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;

        优点:不会产生内存碎片,

       缺点:由于增加了整理过程,效率降低。

5.常用的JVM调优参数

        JVM调优主要是针对内存管理方面的调优,包括控制各个代的大小,GC策略。由于GC开始垃圾回收时会挂起应用线程,严重影响了性能,调优的目是为了尽量降低GC所导致的应用线程暂停时间、 减少Full GC次数。关键参数:

       -Xms -Xmx :通常设置为相同的值,避免运行时要不断扩展JVM内存,这个值决定了JVM heap所能使用的最大内存。

         首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制, 这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G,因为寻址方式不同),而64bit以上的处理器就不会有限制了。

      -Xmn:决定了新生代空间的大小,新生代Eden、S0、S1三个区域的比率可以通过-XX:SurvivorRatio来控制(假如值为 4  表示:Eden:S0:S1 = 4:3:3 )

     -XX:MaxTenuringThreshold:控制对象在经过多少次minor GC之后进入老年代,此参数只有在Serial 串行GC时有效;

     -XX:PermSize、-XX:MaxPermSize:控制方法区的大小,通常设置为相同的值;

6.内存溢出?如果溢出怎么排查?JDK中的一些排查命令?

        内存溢出是由于没被引用的对象(垃圾)过多造成JVM没有及时回收,造成的内存溢出。

        1).堆栈溢出:java.lang.OutOfMemoryError: Java heap space.....

        ---> 先通过内存映像分析工具对dump出来的堆转储快照进行分析,如果是内存泄漏,可进一步通过工具查看泄漏对象到GC roots的引用链,准确定位出泄漏代码的位置;如果是内存溢出,检查虚拟机的堆参数(最小值-Xms、最大值-Xmx),与机器物理内存对比看是否可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长等情况,尝试减少程序运行期的内存消耗。

      2)栈溢出:OutOfMemoryError。(StackOverflowError实例)

 

         ----> 1.首先检查是否创建过多的线程,减少线程数

                  2. 可以通过“减少最大堆容量”或“减少栈容量”来解决。

永久代的溢出:常量池溢出、方法区溢出

      方法区溢出:OutOfMemoryError:PermGen space

        ----> 通过-XX:PermSize=64M -XX:MaxPermSize=128M改大方法区大小

 

JDK命令行工具:

        jps:虚拟机进程状况工具。可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称及这些进程的本地虚拟机唯一ID。(-v 输出虚拟机进程启动时JVM参数)

        jstat:虚拟机统计信息监控工具。实时监视虚拟机运行时的类装载情况、各部分内存占用情况、GC情况、JIT编译情况等,用于性能分析。

         jinfo: 查看虚拟机配置参数信息

        jmap:虚拟机内存映像工具,可以帮助查看内存情况, jmap工具可以让运行中的JVM生成Dump文件,当JVM内存出现问题时可以通过jmap生成快照,分析整个堆,还可以查询finalize执行队列、java堆和永久代的详细信息,如空间使用率、当前用的是那种收集器等。

         jhat:虚拟机堆转储快照分析工具。与jmap工具搭配使用,来分析jmap生成的堆转储快照,一般不会直接使用。

        Jstack:java堆栈跟踪工具。jstack能得到运行java程序的java stack和native stack的信息。可以轻松得知当前线程的运行情况。(可以定位线程出现长时间停顿的原因,通过该工具查看各个线程的调用堆栈,可以知道响应的线程在后台做什么事情,或者等待什么资源)

1.jstat -gcutil pid 看gc频次是否正常,看gc是否能回收掉垃圾;

2.jmap -histo:live pid (这个会触发FGC)

3.jstack pid 看线程谁被阻塞

 

JDK可视化工具:

        JConsole:在JDK/bin目录下,启动JConsole后,将自动搜索本机运行的jvm进程,不需要jps命令来查询指定。双击其中一个jvm进程即可开始监控,也可使用“远程进程”来连接远程服务器。

         VisualVM:是一个集成多个JDK命令行工具的可视化工具,可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等。VisualVM在JDK/bin目录下。

 

7.内存泄漏:

          是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。

引发的原因:

       1.静态集合类引起的内存泄漏:像HashMap、Vector等的使用最容易出现内存泄露,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。例如

 Vector v = new Vector(10);
 for (int i = 1; i < 100; i++) {
          Object o = new Object();
          v.add(o);
          o = null; 
}

         在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。

         2、监听器:调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

          3、各种连接:比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

          4、内部类和外部模块的引用:内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:public void registerMsg(Object b);这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

          5.单例模式:单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏,

8.Java对象在Java虚拟机中的创建过程:

当虚拟机遇到new指令时:

1) 去常量池当中定位类的符号引用;

2) 检查这个类的符号引用是否被加载,解析和初始化过;

3) 如果没有,进行类加载的过程, 如果已经加载过,则为对象分配内存;

4) 虚拟机为分配到的内存空间都赋零值;

5)虚拟机对对象进行必要的设置,比如对象是哪个类的实例,对象的哈希码,对象的GC分代年龄等信息;

6) 执行<init>方法,按照程序意愿初始化对象,即执行类的初始化函数

7) 执行完成之后,就得到了一个新的对象。

jvm为实例对象分配空间主要有两种方法:

        一:指针碰撞:这种方法是在java堆中,将已用内存和未用的内存分开成两部分,两部分内存之间放这一个指针作为分界点,当有新的实例对象需要分配内存空间时,指针向未用内存一侧移动相应大小的距离,将新的实例对象存储在该内存空间上。这种方式需要内存是规整的。

        二:空闲列表:这种方法分配空间是随机,每次分配内存空间都是从空闲的内存中选取一块分配给实例对象。那么就需要一个列表来存放这些空闲的内存空间地址,每当有实例对象需要空间,就从这个列表中选取出一块内存分配给实例对象。这种情况下内存是不规则的。

        Serial、ParNew等带有compact过程的收集器,采用指针碰撞

        CMS这种基于Mark—Sweep算法的收集器,采用空闲列表

 

9.Java类加载的过程。

          类加载过程:1)加载:通过一个类的全限定名来获取定义此类的二进制字节流,将字节流所代表的静态存储结构转化为方法区的运行时的数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。2)验证:确保被加载的类的正确性,包括文件格式验证、元数据验证、字节码验证、符号引用验证等;3) 准备:为类变量分配内存,并设置类变量的初始值;4)解析:虚拟机将常量池内的符号引用转换为直接引用;5)初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。(执行类构造器clinit()方法的过程)

       类文件:https://blog.csdn.net/sinat_38259539/article/details/78248454

       有魔数字段,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。

 

10.双亲委派模型的过程以及优势。

       类加载器:实现通过一个类的全限定名来获取定义此类的二进制字节流。

       双亲委派模型是 java类加载器 所使用的模型.

       双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每个层次的类加载器都是如此。 因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

        用户自定义加载器->应用程序加载器->扩展类加载器->启动类加载器。

        双亲委派模型的优势:java类随着它的加载器一起具备了一种带有优先级的层次关系.【通过双亲委派机制,使得每个类都只会被一个类加载器加载,确立每个类在jvm中的唯一性,同时保证安全性】

 

11.强软弱虚引用

        强引用:使用最普遍的引用:Object o=new Object();如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,也不会回收。

         软引用:用来描述一些还有用但是并非必须的对象。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可以和一个引用队列联合使用。

        弱引用:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。弱引用可以和一个引用队列联合使用。

          虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用必须和引用队列联合使用。

 

12.java内存模型:

       java内存模型(JMM)的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

JVM将内存组织为主内存和工作内存两个部分。

       1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的。

       2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。

        内存之间的交互:java内存中定义了8种操作,作用于主内存变量的lock、unlock、read、write,和作用于工作内存变量的load、use、assign(赋值)、store。如果要把一个变量从主内存复制到工作内存,需要顺序执行read、load;如果要把变量从工作内存同步到主内存,需要顺序执行store、write;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值