JVM相关内容

目录

JVM的内存结构

说一下JVM的垃圾回收

为什么永久代要被替换为元空间?

深拷贝和浅拷贝

说一下堆栈的区别?

简述对象创建的过程?

对象的访问定位怎么实现?

简述垃圾回收机制,为什么要进行垃圾回收?

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

有哪些引用类型?

怎么判断对象是否可以被回收?

哪些对象可以作为GC roots?

对象可以被回收,就代表一定会被回收吗?程序员手动调用system.gc()就会立即执行垃圾回收吗

如何判断一个常量是废弃常量,一个类是一个废弃的类?

JVM中有哪些垃圾回收算法?

有哪些垃圾回收器?

简述垃圾分代收集的过程

说一下类加载机制

类加载器(定义、加载规则、类别)

说一下双亲委派模型(定义;工作流程;优点)

如何打破双亲委派模型

JVM的内存结构

线程共享:

(字符串常量池):存放实例对象,包括新生代、老年代、永久代。

方法区(后被替换为元空间):运行时数据区域的一块逻辑区域,会存放被虚拟机加载的类信息、字段信息、方法信息等数据。是一个抽象概念,具体实现分为永久代和元空间。

说一下JVM的垃圾回收

垃圾回收是Java的一个自动内存管理机制,主要进行识别和回收程序中不使用的内存,能够预防内存泄漏,简化程序员对内存的管理。JVM的内存结构主要分为:堆区,方法区,程序计数器,虚拟机栈、本地方法栈。后三者是线程私有的,会随着线程的结束释放内存,方法区主要存放的是静态变量和类的元数据,堆区主要存放的是实例对象,因此,垃圾回收主要发生的地方是堆区。堆区主要可以分为新生代和老年代,新生代又可以分为Eden和两个survivor区,新生对象首先分配在Eden区,Eden区满了之后会触发minor GC,此时Eden区和一个S区中存活的对象会被复制到另一个S区,然后全部清除。大对象直接进入老年代,存活次数够久的也会进入老年代,老年代内存满了之后会触发full GC,老年代采用标记-清除或者标记-整理算法进行垃圾回收。

为什么永久代要被替换为元空间?

答:元空间只替代了永久代中的元数据部分,原永久代中的静态变量被存放在堆内存中了。永久代有JVM设置的内存空间上限,元空间是本地内存,内存溢出的概率比原来小。提高垃圾回收效率,永久代主要存储元数据、静态变量等,生命周期也更长,所以元空间的内存回收会更简洁。

直接内存

线程私有:

虚拟机栈:生命周期和线程一样,方法的调用都是通过栈来实现,通过栈进行传递,调用一个方法就会压入一个栈帧(局部变量、返回地址等),控制方法执行流程。

本地方法栈:虚拟机栈为虚拟机执行Java方法,本地方法栈为虚拟机执行用到的本地native方法。

程序计数器:线程切换之后需要恢复到正确的位置,因此需要有程序计数器。

深拷贝和浅拷贝

答:浅拷贝是只复制对象的顶层结构和直接引用,而不复制引用的对象,所以如果对拷贝对象做出修改,会影响原始对象的数据;而深拷贝是指复制了引用和引用的对象,如果对复制的内容作出修改,不会影响原始引用的对象数据。

说一下堆栈的区别?

物理地址;存放的内容;可见性。

答:堆区的内存是不连续的,因此性能会慢一些,也要考虑到GC时的不连贯性,所以存在一系列比如标记删除、复制、标记整理的算法。栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的,性能更快。

堆区存放的数据是实例对象和数组,且对于整个进程是共享的;栈存放的是程序方法的局部变量、方法调用信息(以控制方法调用流程)、返回结果等内容,且是线程私有的。

简述对象创建的过程?

类加载检查:常量池是否已加载相应的类,如果没有,必须先加载相应的类。

分配内存空间:根据内存的连续情况有指针碰撞和空闲列表两种不同的分配方式。内存连续的情况下,分为使用内存和空闲内存,这样中间的指针往空闲方向移动一点即可;如果内存不连续,需要维护一个内存的列表,找到大小合适的内存空间来分配。

初始化零值:所有属性首先被设置为相应的零值。

设置对象头:对应哪个类的实例;运行时的数据状态:哈希码,GC的分代年龄等。

init方法初始化:将属性初始化为自定义的初值。

对象的访问定位怎么实现?

答:句柄访问和直接指针。

句柄访问是指引用中存储的是句柄的地址,句柄中包含了实例对象和对象数据类型数据的存放地址,,这两个地址真正指向实例对象数据和对象的类型数据。实例对象移动灵活,但二次定位开销增大。

直接指针是指,引用中存放的是数据对象的地址,这样对象的实例数据中还要包含一个指向对象类型数据的指针。一次定位,访问开销小,速度快。

简述垃圾回收机制,为什么要进行垃圾回收?

答:Java中,程序员不需要显式地释放内存,而是由虚拟机自行执行。只有在虚拟机空闲或当前堆内存不足时才会回收执行垃圾。能够简化编程,有效防止内存溢出,可以有效地使用有效内存。

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

答:对于GC来说,创建了对象之后,GC就会关注对象地地址、大小及使用情况。只有在判定对象不可达,或者引用计数器为0时,才会被回收。可以手动System.gc()来通知虚拟机,但是不一定会立即执行垃圾回收。

有哪些引用类型?

强引用:gc时不会被回收,内存不够也不会被回收。

软引用:内存够不会被回收,不够则会被回收。

弱引用:只要gc就会被回收。

虚引用:无法通过虚引用获得对象,可以在gc时返回一个通知,从而跟踪对象。

怎么判断对象是否可以被回收?

引用计数法:为对象创建一个引用计数器,有对象引用则计数器加一,释放则减一,为0时,可以被回收。但是无法解决循环引用的问题。

可达性分析:以GC roots对象作为起点往下搜索,走过的路径称为引用链,当一个对象到GCroots没有任何引用链相连时,证明对象是可以回收的。

哪些对象可以作为GC roots?

虚拟机栈引用的对象;本地方法栈引用的对象;方法区中常量和静态属性引用的对象;JNI引用的对象;被同步锁持有的对象。

对象可以被回收,就代表一定会被回收吗?程序员手动调用system.gc()就会立即执行垃圾回收吗

不是,对象不可达之后,要经历两次标记才会被回收,第一次标记之后筛选,判断是否要执行finalize方法,如果需要执行则放入队列中准备第二次标记,这时候如果还不可达,就会被回收。不是,只是告诉虚拟机可以回收了,虚拟机空闲或者内存空间满了的时候才会回收。

如何判断一个常量是废弃常量,一个类是一个废弃的类?

运行时常量在方法区中,字符串常量池在堆中,可以被回收,如果字符串常量池中的一个字符串没有被任何String对象引用,就认为是废弃常量。

1、该类的实例对象都被回收;2、加载该类的classloader被回收;3、该类没有在任何地方被引用,无法通过反射等方法再访问。

JVM中有哪些垃圾回收算法?

标记清除:先标记出可以被回收的对象,然后进行清除;实现简单但容易产生内存碎片。

复制:分成两部分,当一部分用完的时候,把活着的对象复制到空的一边,然后把已使用的一次性清除。实现简单,不会有太多内存碎片,但是分成两半内存变小了。且老年代中存活对象较多,复制很麻烦,针对大对象也比较麻烦。

标记整理:标记无用对象,让存活的都往一端移动,直接清除掉界限以外的数据。解决了内存碎片的问题,且不会像复制一样内存减半,但要对内存进行整理,一定程度上降低了效率。

分代:不同的收集算法对不同存活周期的对象的效率不同。所以根据对象存活周期不同划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

有哪些垃圾回收器?

答:主要有serial、parnew、parallel、serial old、parallel old、CMS、G1和ZGC这几种垃圾回收算法。其中serial  parnew  parallel主要用于新生代的垃圾回收,使用复制算法。serial指串行回收,意味着一次只有一个线程执行垃圾回收,且垃圾回收的过程中用户线程需要暂停;parnew是并行垃圾回收器,可以有多个线程并行执行垃圾回收,但同样会导致用户线程的停顿;parallel垃圾回收器也是一样的,但是它更注重CPU的效率,追求较高的吞吐量;serial old和parallel old主要是针对老年代,采用标记整理算法进行垃圾回收。CMS是第一个可以实现并发处理的垃圾回收器,它主要采用标记清除算法,并行标记和清除,且大部分时间能够和用户线程并发执行(初始标记——并发标记——重新标记——并行清除)。G1垃圾回收器主要针对服务器端的垃圾回收且是默认的垃圾回收器,能够并发标记和清理,并提供可预测的停顿时间(初始标记——并发标记——最终标记——筛选回收)。ZGC垃圾回收器主要在算法上做出了改进,追求最小的停顿时间。

简述垃圾分代收集的过程

答:分为新生代和永久代,新生代采用复制算法,分为Eden s0  s1三个分区,对象优先分配在Eden区,然后触发minor gc之后,会复制到survivor区,s0和s1每次gc的时候会交替复制。在survivor区每熬过一轮gc年龄就增大一岁,达到阈值之后就会复制到老年代中。

大对象会直接进入老年代。(长期存活的对象也会进入老年代)

老年代内存满了之后,会触发full gc  一般采用标记整理算法。

说一下类加载机制

答:class文件需要加载到虚拟机中才能运行,类加载机制是指虚拟机把描述类的数据从class加载到内存,并对数据进行校验、解析和初始化,最终形成可以被虚拟机直接使用的类型。

类加载过程

加载:根据查找路径找到相应的class文件然后导入内存。

通过类全名获取定义该类的二进制字节流;将二进制字节流中的静态结构转为方法区的运行时存储(在方法区加载类的元数据信息和静态属性)结构;内存中生成一个代表该类的class对象,作为访问方法区的入口。

验证:验证加载的class文件的正确性(class文件格式、class文件的二进制字节码语义;验证字节码即程序的语义)

准备:为类中的静态变量分配内存空间并设置零值

解析:虚拟机将常量池中的符号引用替换成直接引用,指向内存中的地址

初始化:对静态变量和静态代码块执行初始化工作

类加载器(定义、加载规则、类别)

类加载器是负责加载类的对象,主要作用是将Java类的字节码文件加载到JVM中。

加载规则:JVM启动的时候,并不会一次性加载所有的类,而是根据需要动态的加载,对于已加载的类会被放在classloader中。加载一个类的时候,系统会首先判断是否被加载过,已经加载的类会被直接返回,否则才会尝试加载。也就是说,对于一个类来说,相同的二进制名称的类只会被加载一次。

类加载器类别总结

启动类加载器:最顶层的加载类,没有父级,主要用来加载JDK内部的核心类库;

扩展类加载器:主要用于加载JRE home/lib/ext下的jar包的类以及系统变量指定路径下的所有类;

应用程序类加载器:面向用户的加载器,加载当前类路径下的所有jar包和类。

说一下双亲委派模型(定义;工作流程;优点)

是一种组织类加载的方式。

工作流程:每个类有一个自己的类加载器,这个类加载器实例有一个相关的父类加载器,如果一个类加载器收到了类加载请求,它首先不会自己加载这个类,而是把这个请求委派给父类加载器去完成,寻找父类是否加载过这个类,递归查询,这样所有的加载请求都会被传送到顶层的启动类中被加载。只有当父类无法完成加载请求时,子类才会尝试去加载类。

优点实现了对Java核心类库的保护,由于双亲委派模型的存在,应用程序不能随意地替换Java核心类库,有效防止潜在安全风险;防止类被重复加载:多个加载器想加载同一个类时,可以确保这个类只被加载一次。

如何打破双亲委派模型?

答:通过创建自定义的类加载器实现,继承Classloader,重写loadClass()方法,更改类加载的默认行为,再尝试加载类之前不委托给父类加载器,而是首先尝试自己加载。因为

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值