内存区域-面试与分析
1.运行时数据区是什么?
虚拟机在执行Java程序的过程将它所管理的内存划分为若干不同的数据区,这些区域有各自的用途、创建和销毁时间。
线程私有:程序计数器、Java虚拟机栈、本地方法栈
线程共享:Java堆,方法区
2.程序计数器是什么?
程序计数器是一块较小的内存空间,可以看作当前线程所执行字节码的行号指示器。字节码解释器工作时通过改变计数器的值选取下一条执行指令。
分支、循环、跳转、线程恢复等功能都需要依赖计数器完成。
如果线程正在执行Java方法,计数器记录正在执行 的虚拟机字节码指令地址。如果时本地方法,计数器值为Undefined.
3.堆的作用是什么?
堆是虚拟机所管理的内存中最大的一块,被所有线程共享的,在虚拟机启动时创建。堆用来存放对象实例,Java里几乎所有对象实例都在堆分配内存。堆可以处于 物理上不连续的内存空间,逻辑上应该连续,但对于数组这样的大对象,多数虚拟机实现处于简单,存储高效的考虑会要求连续的内存空间。
堆既可以被实现成固定大小 ,也可以是可扩展的。
4.方法区的作用是什么?
方法区用于存储被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据。
JDK8之前使用永久实现方法区,容易内存溢出,因为永久带有MaxPermSize上限,即使不设置也有默认大小。
JDK7把永久代的字符串常量池、静态变量等移出,JDK8中永久代完全废弃,改用在本地内存中实现的元空间代替。
5.运行时常量池的作用是什么?
相对于Class文件常量池的一个重要特征是动态性,Java不希望常量只有编译期才能产生,运行期间也可以将新的常量放入池中,这种特性利用较多的是String的intern方法。
6.直接内存是什么?
频繁使用的内存,不属于运行时数据区,而且可能导致内存溢出
JDK1.4新加入了NIO这种基于通道与缓存区的IO,可以使用Native函数库直接范培对外内u才能,通过一个堆里的DirectByteBuffer对象作为内存的引用进行操作,避免了Java堆和Native堆来回复制数据。
直接内存的分配不受Java堆大小的限制,但还是会受到本机总内存及处理器寻址空间限制,一般配置虚拟机参数会根据实际内存设置参数信息
-》但是会忽略直接内存,会导致内存区域总和大于物理内存限制,导致 动态扩展出现OOM
7.内存溢出和内存泄漏的区别?
内存溢出OutOfMemory,指程序在申请内存时,没有足够的内存空间供其使用。
内存泄漏 Memory Leak,指程序在申请内存后,无法释放已申请的内存空间,内存泄漏最终i将导致内存溢出。
8.栈溢出的原因?
HotSpot不区分虚拟机和本地方法栈,设置本地方法栈大小的参数没有意义,栈容量只能由-Xss参数来设定,存在两种异常:
StackOverflowError:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError,例如一个递归方法不断调用自己。该异常有明确错误堆栈可供分析,容易定位到问题所在。
OutOfMemoryError:如果JVM栈可以动态扩展,当扩展无法申请到足够内存时会抛出OutOfMemoryError。HotSpot不支持虚拟机栈扩展,所以除非在创建线程申请内存时因为无法获得足够内存而出现OOM,否则在线程运行时是不会因为扩展而导致溢出的。
运行时常量池溢出的原因?
String.intern方法;如果常量池中有等于此String对象的字符串,返回池中这个字符串的String对象的引用,否则将此String对象包含的字符串添加到常量池并返回此String对象的引用。
在while死循环中调用intern方法导致运行时常量池溢出。
JDK7之后不会出现该问题,因为存放在永久代的字符串常量池已经被移至堆中。
方法区溢出原因?
方法区主要存放类型信息,如类名,访问修饰符,常量池,字段描述,方法描述等。只要不断在运行时产生大量类,方法区就会溢出。
eg:使用JDK反射或CGLib直接操作字节码在运行时产生大量的类。