java面试之JVM

1JDK与JRE的区别:JRE: Java Runtime EnvironmentJDK:Java Development Kit JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。如果你需要运行java程序,只需安装JRE就可以了。如果你需要编写java程序,需要安装JDK。
2JVM的运行机制:jvm是用于运行字节码文件的虚拟机,运行在操作系统之上,不与硬件设备直接交互。java源文件用过编译器被编译成响应的.class文件(字节码文件),字节码文件又被jvm中的解释器编译成机器码在不同的操作系统上执行,不同操作系统的解释器都是不同的,但是基于解释器的虚拟机都是相同的,这就是java能够跨平台的原因。
3JVM的组成:一个类加载器子系统(Class Loader SubSystem),运行时数据区(Runtime Data Area),执行引擎和本地接口库(Native Interface Library)。本地接口库通过调用本地方法库与操作系统交互。
4JVM后台运行的线程:1虚拟机线程:在虚拟机到达安全点(safe point)时出现。2周期性任务线程:用过定时器调度线程来实现周期性操作的执行。3GC线程:GC线程支持JVM中不同的垃圾回收活动。4编译器线程:编译器线程在运行时将字节码动态编译成本地平台机器码,是JVM跨平台的具体实现。5信号分发线程:接受发送到JVM的信号并调用JVM方法。
5JVM的最大内存限制:(1)堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小 于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、 -Xmx相等以避免在每次GC后调整堆的大小。
(2)非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由-XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
(3)VM最大内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系 统下为2G-3G),而64bit以上的处理器就不会有限制了。
6JVM非堆内存:非堆内存也称方法区。jvm内存分为堆内存和堆外内存。堆内存分为新生代和老年代。堆外内存通常是指永久代。永久代是指内存的永久保存区域,主要存放Class和元数据信息。用于存储常量,静态变量,类信息,即时编译后的机器码和运行时的常量池。类的信息主要包括类的版本,字段,方法,接口描述。
7JVM的内存设置
Xmn、Xms、Xmx、Xss都是JVM对内存的配置参数,我们可以根据不同需要区修改这些参数,以达到运行程序的最好效果。

-Xms 堆内存的最小大小,默认为物理内存的1/64

-Xmx 堆内存的最大大小,默认为物理内存的1/4

-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn

-Xss 设置每个线程可使用的内存大小,即栈的大小。在相同物理内存下,减小这个值能生成更多的线程,当然操作系统对一个进程内的线程数还是有限制的,不能无限生成。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRatio:设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurvivorRatio:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示 Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5
-XX:MaxTenuringThreshold:设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:分别设置永久代最小大小与最大大小(Java8以前)
-XX:MetaspaceSize、-XX:MaxMetaspaceSize:分别设置元空间最小大小与最大大小(Java8以后)
虚拟机栈
虚拟机栈时描述java方法的执行过程的内存方法,它在当前栈帧存储了局部变量表,操作数栈,动态链接,方法出口等信息,同时栈帧用来存储部分运行时数据及其数据结构,处理动态链接方法的返回值和异常分派。

jvm运行过程中创建的对象和产生的数据都被存储在堆中,堆是被线程共享的内存区域,也是垃圾回收区域进行垃圾回收的最主要的内存区域。可以分为:新生代,老年代,永久代。
JAVA中堆和栈为什么要进行分离
栈是运行时的单位,而堆是存储单位。
栈解决程序的运行问题,即程序如何执行,或者说如何存储数据,堆解决的是数据存储的问题,即数据怎么放,放在哪。
java中一个线程就会有一个线程栈与之对应
栈是运行单位,因此里面存储的信息都是跟当前线程相关的信息,包括局部变量,程序运行状态,方法返回值等,而堆只负责存储数据信息。
一,从软件设计的角度来看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。
二,堆和栈的分离,使得堆的内容可以被多个栈共享(也可以理解为多个线程访问一个对象)。这种共享的收益是很多的,一方面这种共享提供了一种有效的数据交互方式,另外堆中共享的常量和缓存可以被所有栈访问,节省了空间。
三,栈因为运行时的需要,比如保存系统运行时的上下文,需要进行地址段的划分,由于栈只能向上增长,因此就会限制住存储内容的能力。而堆不同,堆中的对象可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长称为可能,相应栈中只需要记录堆中的一个地址即可。
四, 面向对象就是堆和栈的完美结合。对象的属性其实就是数据,存放在堆中,而对象的方法,就是运行逻辑,存放在栈中。
JVM的类加载阶段
1 加载:JVM读取Class文件,并根据Class文件描述创建java.lang.Class对象
2验证:主要确保Class文件符合当前虚拟机要求,保证虚拟机自身的安全。
3准备:在方法区中为类变量分配内存空间并设置类中变量的初始值。初始值是指不同数据类型的默认值。
4 解析:会将常量池中的符号引用替换为直接引用。
5 初始化:主要通过执行类构造器的< Client >方法为类进行初始化。
JVM类加载器
1 启动类加载器:负责加载Java_Home/lib目录中的类库
2 扩展类加载器:负责加载Java_Home/lib/ext目录中的类库
3 应用程序类加载器:负责加载用户路径(classpath)上的类库
4可以实现自定义加载器
JVM内存模型
java内存模型本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规则定义了程序中各个变量的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时都会为其创建一个工作内存(栈空间),用于存储线程的私有数据,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域。所有线程都可以访问,但线程对变量的操作(读取和复制)都必须在工作内存中进行。首先要将变量从主内存中拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存的变量副本拷贝,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信必须通过主内存来完成。
对于一个实例对象中的成员方法而言,如果方法中的本地变量是基本数据类型,那么将直接存储在工作内存的栈帧中,如果本地变量是引用类型,那么对象的实例存储在主内存,该对象的引用存储在工作内存的栈帧中,对于实例对象的成员变量,无论它是基本数据类型或者是包装类型还是引用类型,都会被存储到主内存中,至于static变量和类本身相关的信息,也会储存在主内存中。
当前的处理器架构大多是:多核+多级缓存+主存的模式,这样在多线程场景下就存在数据竞争从而造成缓存不一致的问题。
JMM决定了一个线程对共享变量的写入何时对另一个线程可见。
JMM中的三大问题
原子性,可见性,有序性
原子性:指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程锁影响。
可见性:是指当一个线程修改了某个共享变量的值后,其他线程是否能够马上得知这个修改的值。
有序性:指对于单线程的执行代码,可以认为代码的执行是按顺序执行的,对于多线程的环境,操作是无序的
volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问的时候都强迫从主存中读取该变量的值,而当该变量发生变化时,会强迫将最新的值写入到主存中,任何时刻,不同线程都能看到该变量的最新值。
内存溢出与内存泄漏
内存溢出:指程序在申请内存时,没有足够的内存空间供其使用
内存泄漏:指程序在申请内存后,无法释放已经申请的内存空间,一次内存泄漏的危害可以忽略,但多次的话,内存迟早会被占光。
java中如何避免内存溢出
1合理的使用强引用,软引用,弱引用,虚引用
强引用:强引用的对象一定是可达性的对象,JVM不会回收这个对象,即使内存在不足的情况下,JVM宁愿抛处OutOfMemory错误也不会回收这种对象。
软引用:SoftReference,用来描述一些有用,但不是必需的对象,对于软引用的对象,只有在内存不足的时候,JVM才会回收该对象。
弱引用:WeakReference用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,弱引用关联的对象都会被回收。
2修改JVM启动参数,设置启动内存空间和最大内存空间
3避免使用String,尽量使用StringBuilder
4数据尽量分配处理,不要一次性加载到内存
java中直接内存
java的内存分为两个部分,一部分是不需要jvm管理的直接内存,被称为堆外内存,堆外内存就是把内存对象分配在jvm堆以外的内存区域,这部分内存不是虚拟机管理的,而是由操作系统来管理的,这样可以减少垃圾回收对程序的影响
优点:可以提升性能,常规情况下,把java堆内部的数据进行远程发送,需要先把堆内部数据拷贝到直接内存中,也就是拷贝到堆外内存中,然后发送。可以把对象之间分配到直接内存中,发送的时候就可以省去复制的操作
缺点:没有jvm帮助管理内存,需要我们自己来管理堆外内存,防止内存溢出,为了避免一直没有full gc,最终导致物理内存被耗完。我们可以指定直接内存的最大值,通过-xx:MaxDirectMemorySize来指定,当达到阈值时,调用system.gc来进行一次full gc,把那些没有被使用的直接内存回收掉。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值