JVM知识点汇总

java内存

1、java虚拟机内存包括哪些区域?

  虚拟机所管理的内存包含一下几个运行时数据区域

  1. 程序计数器
    1. 线程私有
    2. 如果正在执行Java方法,则记录正在执行的字节码指令的地址,如果正在执行的是Native方法,计数器的值为空。
    3. 此区域不会产生OutOfMemoryError异常。
  2. java虚拟机栈
    1. 线程私有
    2. 虚拟机栈描述的是java方法执行的内存模型:每个方法的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口信息。(局部变量表存放编译期间可知的8种基本数据类型、reference类型和returnAddress类型,局部变量表在编译期间完成分配,不会改变)
    3. 如果线程请求栈深度超过虚拟机允许的深度产生StackOverflowError异常,如果虚拟机展可以动态扩展,扩展是无法申请到足够的内存产生OutOfMemoryError异常
  3. 本地方法栈
    1. 线程私有
    2. 本地方法栈的作用和java虚拟机栈类似,区别在于本地方法栈为Native方法服务
    3. 和Java虚拟机栈一样,会产生StackOverflowError异常和OutOfMemoryError异常
  4. java堆
    1. 线程共享
    2. 几乎所有的对象实例和数组都在对上分配。
    3. 如果没有内存完成实例分配,并且堆无法再扩展时,产生OutOfMemoryError异常
  5. 方法区
    1. 线程共享
    2. 方法区存储已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码数据等
    3. 当方法去无法满足内存分配需求时,将抛出OutOfMemoryError异常
  6. 运行时常量池
    1. 线程共享
    2. 运行时常量池是方法区的一部分,存放编译器生成的各种字面量和符号引用。运行期间也可以将新的常量放入常量池
    3. 当常量池无法申请内存时会产生OutOfMemoryError异常
  7. 直接内存
    1. 线程共享
    2. 不属于虚拟机运行时数据区,是通过Native函数库直接分配的对外内存,并且可以通过java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
    3. 不受java堆大小限制,但是直接内存占用过多导致,java堆没有足够的空间扩展,也会产生OutOfMemoryError异常

Java对象的创建、布局和访问?

  1. 对象创建过程
    1. 当遇到new指令时,先检查new指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化()。如果没有,必须先执行类加载过程。
    2. 类加载检查通过后,虚拟机为对象分配内存。
      1. 不同的收集器会使用不同的分配方法。
      2. 通过CAS或者TLAB防止线程竞争
    3. 内存分配完成后,将内存空间都初始化为零值(不包括对象头)。
    4. 将类的元数据信息、对象的哈希码、对象的GC年龄、锁标志等信息添加到对象头中。
    5. 最后执行方法完成实例初始化。
  2. 对象布局
    1. 对象头
    2. 实例数据
    3. 对齐填充
  3. 对象访问定位
    1. 句柄访问
      1. 在Java堆中分配一部分内存作为句柄池,reference存放指向句柄池的地址,句柄存放对象实例的指针和对象类型数据的指针。
    2. 直接访问
      1. reference存放指向对象实例数据的指针,对象实例数据需要需要保存对象类型数据的指针。

内存溢出实践

  1. java堆溢出

      Java堆用于存储对象实例,所以不断创建对象,并且保证对象GC Roots可达。
public class HeepOOM {
	
	static class OOMObject {
	}
	
	public static void main(String[] args) {
		List list = new ArrayList<>();
		while (true) {
			list.add(new OOMObject());
		}
	}

}
  1. 虚拟机栈和本地方法栈溢出
      
public class JavaVMStackSOF {
	private int stackLength = 1;
	
	private void stackLeak() {
		stackLength++;
		stackLeak();
	}
	
	public static void main(String[] args) throws Throwable{
		JavaVMStackSOF oom = new JavaVMStackSOF();
		try {
			oom.stackLeak();
		} catch (Throwable e) {
			System.out.println("stack length : " + oom.stackLength);
			throw e;
		}
	}
}
  1. 方法区溢出
      方法区用于存储已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。由于在Java1.8中没有永久代,方法区中的各种数据都被分散到Java堆、Native堆和元空间,其中类信息被放到元空间。下面通过代码观察元空间溢出。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class JavaMethodAreaOOM {

	public static void main(String[] args) {
		while (true) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				@Override
				public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
					return proxy.invokeSuper(obj, args);
				}
			});
			enhancer.create();
		}
	}
	static class OOMObject {
	}
  1. 运行时常量池溢出

      运行时常量池用于存放一些字面量和符号引用。在Java1.8中字面量被存放到Java堆中,符号引用被放到Native堆中。另外字符串常量池也在Java堆中。

你可能感兴趣的:(java面试,jvm,java基础)