JVM学完总结,这一篇对付面试足够了

6 篇文章 0 订阅

前言

通过这10多天对于JVM的初步学习,我从一个对于java底层知识认知的小白,渐渐地bo开了蒙住视野地面纱,下面首先是我对JVM知识点的一个汇总
在这里插入图片描述
1.Java中会存在内存泄漏吗?请简单描述
理论上,由于java当中有垃圾回收机制,只要是与主要对象无关的或者说没有被引用的对象,对他们进行回收,因此你以为可能不会有内存泄漏的问题;然而在实际开发过程当中可能会存在无用且可达的现象,换句话来说,就是这些对象不会被垃圾回收,因此也会导致内存泄漏的发生。例如Hibernate中的Session中的对象属于持久态,垃圾回收是不会回收这些对象的,然而这些对象可能会存在无用的垃圾对象,如果不及时关闭(Close)或者清空(Flush)一级缓存就可能导致内存泄漏
下面来举一个代码例子,不要说怀怀光说不练:

package JVM;
import java.util.Arrays;
import java.util.EmptyStackException;
    public class MyStack<T> {
        private T[] elements;//这是一个栈,并且当中对应着的元素,这个就是创建栈的一个实例
        private int size = 0;//这是栈的初始大小

        private static final int INIT_CAPACITY = 16;

        public MyStack() {

            elements = (T[]) new Object[INIT_CAPACITY];//这个带有一定的强制转化的过程在里面
        }

        public void push(T elem) {//这个是入栈
            ensureCapacity();//这个是创建栈的空间大小
            elements[size++] = elem;//第一个元素是elem
        }

        public T pop() {
            if(size == 0)//当这个栈的空间大小为0时,就不能进行弹出
                throw new EmptyStackException();
            return elements[--size];//否则的话就将最顶层的那个给弹出去,为什么时--size呢,就是因为最底层为elements[0]
        }

        private void ensureCapacity() {
            if(elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    }

代码的注解我也解释得比较清除了,如果有什么错误的地方,大家一定要帮我指出呀。
那我现在就来说说为什么可能会出现这样的一个内存泄漏的问题。首先呢,是这个弹栈方法pop()处出现的错误,在弹出了对象之后,由于栈内部维持着对这些对象的过期引用,因此垃圾回收不会处理这些被弹出的对象,那是因为这个对象或者这些对象被栈内部给保留下来了。这样的坏处就是导致很多的对象被排除在垃圾回收之外,对性能造成极大的影响,极端情况下会引发Disk Paging(物理内存与硬盘的虚拟内存i交换数据),甚至造成OutOfMemoryError的错误。
2.如何避免内存泄漏、溢出的几种常用方法
(1)尽早释放掉无用对象的引用

好的办法就是使用临时变量的时候,让引用变量在退出活动域后自动设置为Null,暗示垃圾收集来收集对象,防止内存的泄漏

程序进行字符串处理的时候,尽量避免使用String,而应使用StringBuffer

由于String有一个很大的缺点就是,每一次创建新的对象都会独立占用内存一块区域,这样会造成虚拟机大量内存的浪费,于是我们应当尽量改用StringBuffer,当一个StringBuffer被创建之后,通过其提供的append()、insert()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过其来生成了最终想要的字符串。

下面列出对应的代码块

package JVM;

public class Java2 {
    public static void main(String[] args) {


        StringBuffer a = new StringBuffer("123");
        a.append("w");
        a.replace(0,2,"我喜欢你");
        System.out.println(a);
    }
}

输出的结果便是(我喜欢你3w),在这个代码当中我用到的方法就是这个StringBuffer对应的方法,仔细一看是不是很方便啊。但是它也有唯一的缺点就是速度相比于StringBuilder较慢,但是它的好处还有一个就是线程是安全的。下面我来解释一下为什么说StringBuffer是线程安全的呢

在这里插入图片描述

上图就是因为StringBuffer类的方法都添加了synchronized关键字,也就是给这个方法添加了个锁,用来保证线程安全
那下面我来介绍一下StringBuilder类中实现的方法

这里是引用

这个实现就是没有设置锁来保证线程的安全
(2)尽量少用静态变量
因为静态变量是全局的,GC不会回收
(3)不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象
可以使用Vector,hashtable创建一组对象容器,或者使用迭代器来设置循环的内容,然后从容器中取走那些对象,而不用每次new之后又丢弃
(4)优化配置
设置-Xms、-Xmx相等
设置NewSize、MaxNewSize
设置HeapSize,
3.在64位JVM当中,int的长度是多少?
int的长度无论是在32位还是在64位当中,对应的长度都是32。
4.PartNew与Serial之间的不同之处
Serial在执行垃圾回收的时候只有一个线程进行Gc操作,但是PartNew则是采用的是多线程进行的回收机制,就是说Serial在执行收集的时候,仅仅是收集完了一个线程之后才能收集另外一个线程,这样将会大大降低收集的效率
5.WeakReference和SoftReference之间的区别是什么?
这里我们首先需要了解的就是弱引用、软引用、强引用和虚引用的一些知识点
强引用就是我们在日常生活中经常用到,例如我们创建对象的过程一般就是强引用
下面就是软引用

说到这个软引用,可能大家有点不好理解,它其实就是一个备用的引用,当JVM内存充足的时候就将数据缓存到内存当中,如果内存不足了就将其回收掉。

    // 强引用
    String strongReference = new String("123");
    // 软引用,"456"这个String对象包含一个强引用 str 和弱引用 softReference 
    String str = new String("456");
    SoftReference<String> softReference = new SoftReference<String>(str);

弱引用:这个就是说当有一个对象,你有时候想用,但是又不想耽搁它进行垃圾回收(哈哈),那么就可以对它设置成弱引用

下面来介绍最后的一种引用,那就是虚引用

如果一个对象只有虚引用,那么这个对象就跟没有被引用一样,在任何时候都有可能被垃圾回收

因此,二者的区别应该就很清晰明了了。
** 6.解释java堆空间及GC**
java堆就是用来存储对象的,而且,它与垃圾回收关联是最多的,因为垃圾回收,回收的更多的还是对象。GC用于回收无用的对象,也就是暂未被引用或者现在没有太多关联的对象,那么就将他们进行回收来用于其他地方。

#7.JVM内存区域
程序计数器、堆、本地方法栈、方法区、虚拟机栈
在这里插入图片描述

8.程序计数器(线程私有)
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有” 的内存。

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址) 。如果还是 Native 方法,则为空。

这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

9.虚拟机栈(线程私有)
是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派(Dispatch Exception)。 栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

10.本地方法区(线程私有)
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一 。

11.你能保证GC执行吗?
这是不能的,为输入System.gc()并不能保证GC的执行,它只是发送了一个指令而已,等待进行垃圾回收而已
12.怎么获取Java程序使用的内存?堆使用的百分比?
可以通过Runtime类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及内存的剩余空间。Runtime.freeMemory()方法返回剩余空间的字节数,Runtime.totalMemory()方法总内存的字节数,Runtime.maxMemory()返回最大内存的字节数。
13.描述一下JVM加载class文件的原理机制
JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中各类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。

加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。

从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。
14.JVM运行时内存

Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。
在这里插入图片描述
15.新生代
在这个过程当中,如上图所示,你可以发现新生代一共由这三部分组成
Eden区
java新对象的出生地,当Eden区域不够的时候就会进行一次垃圾回收
ServisorFrom
上一次GC的幸存者,作为这一次gc的被扫描在
ServivorTo
保留了一次MinorGC过程的幸存者
MinorGC过程(复制->清空->互换)
MinorGC采用复制算法
16.永久代
指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,class在被加载的时候被放入永久区域,它和存放实例的不同,GC不会对它进行清理
17.引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用, 即他们的引用计数都不为 0, 则说明对象不太可能再被用到,那么这个对象就是可回收对象。

18.可达性分析
为了解决引用计数法的循环引用问题, Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象, 不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
19.标记清除算法( Mark-Sweep)
最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图
在这里插入图片描述

20.复制算法(copying)
为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:
在这里插入图片描述
21.Minor Gc和Full Gc分别在什么时候发生
前者是在新生代的内存区域不够的时候才进行的清理,而后者是在JVM内存不够的时候发生FGC
22.你知道哪些JVM性能调优?
设定堆内存大小
-Xmx:堆内存最大限制
设定新生代的大小。新生代不宜太小,否则将会有大量对象涌入到老年代
-XX:NewSize:新生代大小
-XX:NewRatio:新生代和老生代占比
-XX:SurvivorRatio:Eden空间和幸存者空间的占比
设定垃圾回收器:年轻代用 -XX:+UseParNewGC 老年代用-XX:+UseConcMarkSweepGC
23.调优工具

我一般还是喜欢用jconsole来对JVM当中的内存消耗进行检查
24.调优命令有哪些?
(1)jps:显示所有正在进行的进程,并显示进程号和名字
(2)jstat:用于监视虚拟机运行状态的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
(3)jmap:用于生成head dump文件
(4)jhat:JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
(5)jstack:用于生成java虚拟机当前时刻的线程快照
(6)jinfo:这个命令是实时查看和调整虚拟机运行参数
25.双亲委派
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class), 子类加载器才会尝试自己去加载。

采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象

在这里插入图片描述

最后

本篇内容全是之间的一些拙劣的建议,还有些不懂的是通过参考其他博主的讲解进行的构思,还有一些知识点我并没有进行介绍,就是觉得可能相对来说没那么重要或者说更简单一些,大家在学习JVM之前一定要看书呀,看《深入理解java虚拟机》这本书,看完之后再进行总结或者登陆csdn进行知识点的查询
好了,这期的水文就到此结束了,我是明怀,一个互联网苟且偷生的程序猿,我们下期见

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值