第一章 JAVA内部区域与内存溢出异常

简介: 一、前言 一直以来都要看看看这本书《深入理解JAVA虚拟机》,所以找机会记录下来,以备不时之需,供看客借鉴。

一、前言

一直以来都要看看看这本书《深入理解JAVA虚拟机》,所以找机会记录下来,以备不时之需,供看客借鉴。2017年10月16日10:16:02

二、虚拟机内存划分

科普一下,内存是计算机分配的一段空间(可以理解为一段用01表示的数据长度)。java 虚拟机运行时的内存如下。

         
        图示说明:白色模块为线程隔离、金色模块为所有线程共享

       2.1 程序计数器

              program Count Register 是一块 较小的内存空间,可以看作当前线程所执行的字节码的 行号指示器
             如果正在执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行一个Native方法,这个计数器值为空(Undefined)。
    计数器区域没有OutOfMemory。   

 2.2 Java虚拟机栈

Java Virtual Machine Stacks 生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个帧栈(Stack Frame),用于存储
局部变量表(基本类型、对象引用、returnAddress)、操作数栈、动态链接、方法出口 等信息。每个方法从调用到执行完成的过程,对应一个帧栈在虚拟机栈中的入栈到出栈的过程。
            StackOverflowError情况:线程请求的栈深度大于虚拟机允许的深度。
   OutOfMemory情况:虚拟机栈无法申请足够内存。

  2.3 本地方法栈

        Native Method Stack 为虚拟机中使用到的Native方法服务。在HotSpot中虚拟机栈与本地方法栈已经合二为一。
         StackOverflowError情况:线程请求的栈深度大于虚拟机允许的深度。
OutOfMemory情况:虚拟机栈无法申请足够内存。

 2.4 Java堆

         Java Heap是Java 虚拟机管理的内存中 最大的一块,在虚拟机启动时创建。Java堆的目的就是 存放对象实例,此区域是垃圾回收的主要区域。

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1

         Java Heap 的可扩展实现(通过-Xmx和 -Xms控制)
          OutOfMemory情况:在Java 堆中没有内存完成实例分配。

2.5 方法区

Method Area用于存储已被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。作为Java堆的逻辑部分,称之为Non-Heap(非堆),“永久代”。
此区域还包括:运行时常量池(运行期间产生的常量)。��栗子:String类的intern()方法;
OutOfMemory情况:在Java 堆中没有内存完成实例分配。

2.6 其他区域

2.5.1 直接内存:通过DirectByteBuffer对象作为这块内存的的引用进行操作。NIO直接操作Native分配堆外内存。
     OutOfMemory情况:物理内存总和大于系统限制等(32位系统最大为4G)。

三、Hotspot虚拟机

3.1 对象创建

3.2 对象内存分布

对象在内存中分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头:自身运行是的数据+类型指针。
存储内容                     标志位 状态    
对象哈希码、对象分带年龄 01 未锁定

3.3 对象的访问定位

对象访问定位:句柄访问(稳定)、指针访问(快)。可以理解为句柄访问的引用,指针访问的是直接地址。Hotspot采用指针访问对象。

四、OutOfMemoryError实例

在Debug/Run中设置虚拟机参数:参考Hotspot
-verbose:gc  -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8	

4.1 Java堆溢出

Java堆是保持对象实例,不断的创建对象,保证Gc Roots有可达路径来避免清除对象。
代码示例:
/**
 * VM args: -Xms20m -Xmx20m -XX:HeadDumpOnOutofMemoryError
 * @author ycy
 *
 */
public class HeapOOM {
	public static void main(String[] args) {
		List<String> testList=new ArrayList<>();
	while(true) {
		testList.add("ddddd");
	}
	}
}
出错信息:


内存泄露总体分析预览:

堆异常优化: 借鉴内存优化
实际错误例子

 
       

public static List<Object> list;

在项目中不断在list增加数据,进行操作,


4.2 虚拟机栈和本地方法栈溢出

栈存储局部变量表、操作数等,那么只要不断增加数据一直到内存溢出就可以实现。

示例代码:
package com.ycy.java.outofmermory;
/**
 * vm args: -Xss 128k
 * @author ycy
 *产生原因:堆栈空间太小、内存不足
 */
public class JavaStackSOF {
	//初始化堆栈长度为1
	private int stackLength=1;
	//堆栈溢出方法
	public void stackLeak() {
		stackLength++;
		stackLeak();
	}
	public static void main(String[] args) {
		JavaStackSOF oom=new JavaStackSOF();
		try {
			oom.stackLeak();
		}catch(Throwable e){
			System.out.println("stackLength:"+oom.stackLength);
			throw e;
		}
		
	}
}
出错信息:
实验结果:无论是堆帧太大还是虚拟机栈容量小,都会报Stack OverflowError异常。
Jvm默认大多数情况下帧的大小在1000到2000完全没得问题,正常的递归完全没得问题。---可以通过减少最大的堆和栈容量来解决部门“内存溢出”。

4.3 方法区和运行时常量池溢出

运行时常量池是方法区的一部分。
通过-XX:PermSzie和-XX:MaxPermSzie限制方法区的大小。是我们的永久代大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package  com.paddx.test.memory;
 
import  java.util.ArrayList;
import  java.util.List;
 
public  class  StringOomMock {
     static  String  base =  "string" ;
     public  static  void  main(String[] args) {
         List<String> list =  new  ArrayList<String>();
         for  ( int  i= 0 ;i< Integer.MAX_VALUE;i++){
             String str = base + base;
             base = str;
             list.add(str.intern());
         }
     }
}

这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:

JDK 1.6 的运行结果:

JDK 1.7的运行结果:

JDK 1.8的运行结果:

  



4.4 JDK 1.8元空间Metaspace

元空间与方法区相似,但是存在与直接内存中.
为什么出现Metaspace:
       1、字符串存在永久代中,容易出现性能问题和内存溢出。
  2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
  4、Oracle 可能会将HotSpot 与 JRockit 合二为一
结果:

1、符号引用(Symbols)转移到了native heap; 例子:com.ycy.test

2、字面量(interned strings)转移到了java heap; 例子: string.intern()

3、类的静态变量(class statics)转移到了java heap 例子:Class元


-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
实例代码:
package com.ycy.java.outofmermory;

import java.util.ArrayList;
import java.util.List;

/**
 * vm args: 
 * 	-XX:MetaspaceSize=10M 
	-XX:MaxMetaspaceSize=10M
	-XX:PermSize=10M -XX:MaxPermSize=10M 
 * tips:在jdk8中已经不会报permGen space 错误,因为1.8的jdk中已经没有永久代,而是报java heap space
 * @author ycy
 *
 */
public class RuntimeContantPoolOOM {
	static String  base = "string";
	public static void main(String[] args) {
		
		//使用List 保持着常量池的引用,避免Full Gc 回收常量池行为
		List<String> list=new ArrayList<>();
		int i=0;
		while(true) {
			String str = base + base;
            base = str;
            list.add(str.intern());
		}
	}
}
输出结果:


实际例子:
package com.ycy.java.outofmermory;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
/**
 * -XX:MetaspaceSize=2M 
-XX:MaxMetaspaceSize=2M
-XX:PermSize=10M -XX:MaxPermSize=10M
 * @author ycy
 *
 */
public class JavaMethodAreaOOM {
	  public static void main(final String[] args) {
		
		  while(true) {
			  Enhancer enhancer=new Enhancer();
			  //设置需要创建子类的类
			  enhancer.setSuperclass(OOMObject.class);
			  enhancer.setUseCache(false);
			  //通过字节码技术动态创建子类实例
			  enhancer.setCallback(new MethodInterceptor() {
				  
				  @Override
				  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
					  return methodProxy.invokeSuper(objects,args);
				  }
			  });
			  
		  }
	    }
	    static class OOMObject{}
}

执行结果:

4.5 本机直接内存溢出

默认与Jave heap一样的最大值。
示例代码
/**
 * VM Args:-Xms20m -Xmx20m -XX:MaxDirectMemorySize=10m
 * @author ycy
 *
 */
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class DirectMemoryOOM {
	private static final int _1MB=1024*1024;

    public static void main(String[] args) throws Exception {
        Field unfield= Unsafe.class.getDeclaredFields()[0];
        unfield.setAccessible(true);
        Unsafe unsafe=(Unsafe)unfield.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

错误信息:

出现症状:一般OOM溢出但是,Dump文件很小,也使用了nio可以回出现这个问题。

4.6 出现内存异常一般情况

   出现内存溢出一般情况
1.内存中加载的数据量过大。
 比如一次性从数据库加载过多的数据。
2.并发数量太高。
 并发数量太高,导致在短时间内创建大量的对象,GC也不及回收。
3.集合类中有无用对象的引用,使用完后没有立即清除。
 集合类中的对象,如果不手动进行清除,GC不是不会对集合中无用的对象进行回收。
4.代码中存在死循环,递归,或者循环次数过多产生大量的对象。
5.方法区内存溢出。
 方法区存放的是Class类型信息,类名,常量池,修饰符,方法描述等信息。
 使用了过多的静态变量。常量池也被大量的占用。
 jvm在“运行期间” 产生了大量的类。导致填满了方法区。比如使用反射,动态代理,字节码生成技术会在运行期间产生大量的类和类型信息。如hibernate,spring第三方框架大量使用了cglib技术产生大量的动态类。
 大量的jsp在编译生成java类时也有可能产生方法区溢出,GC对方法区的回收非常苛刻的,因为对于一个类的回收条件就很严格。
6.启动时JVM内存参数设置过小。



目录
相关文章
|
16天前
|
算法 安全 Java
Java内存管理:深入理解垃圾收集器
在Java的世界里,内存管理是一块基石,它支撑着应用程序的稳定运行。本文将带你走进Java的垃圾收集器(GC),探索它是如何默默守护着我们的内存安全。我们将从垃圾收集的基本概念出发,逐步深入到不同垃圾收集器的工作机制,并通过实例分析它们在实际应用中的表现。文章不仅旨在提升你对Java内存管理的认识,更希望你能通过这些知识优化你的代码,让程序运行更加高效。
34 3
|
4天前
|
Java 编译器 数据库连接
Java——异常
在 Java 中,程序执行过程中的不正常行为被称为异常。异常分为 Error 和 Exception。Error 表示系统级错误,而 Exception 则封装程序可能出现的问题。异常包括编译时异常和运行时异常(如数组越界)。异常可用于查找 bug 信息和作为方法内部的特殊返回值。处理异常的方式有默认处理和捕获异常,后者通过 try-catch 结构实现。此外,还可以自定义异常类来更灵活地处理特定情况。
24 9
Java——异常
|
7天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
6天前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制的深度剖析
在Java编程语言中,内存管理是一个核心概念。本文将深入探讨Java的垃圾回收(GC)机制,解析其工作原理、重要性以及优化方法。通过本文,您不仅会了解到基础的GC知识,还将掌握如何在实际开发中高效利用这一机制。
|
6天前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理策略和垃圾回收机制。首先介绍了Java内存模型的基本概念,包括堆、栈以及方法区的划分和各自的功能。进一步详细阐述了垃圾回收的基本原理、常见算法(如标记-清除、复制、标记-整理等),以及如何通过JVM参数调优垃圾回收器的性能。此外,还讨论了Java 9引入的接口变化对垃圾回收的影响,以及如何通过Shenandoah等现代垃圾回收器提升应用性能。最后,提供了一些编写高效Java代码的实践建议,帮助开发者更好地理解和管理Java应用的内存使用。
|
14天前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
43 11
|
15天前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
39 11
|
12天前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
8天前
|
存储 缓存 算法
Java中的内存管理:理解垃圾回收机制
本文将深入探讨Java中的内存管理,特别是垃圾回收机制。我们将从基本的内存分配开始,逐步解析垃圾回收的原理和过程,以及它对Java应用程序性能的影响。通过实例演示,我们会展示如何在Java中有效地管理和优化内存使用。最后,我们将讨论一些常见的内存泄漏问题及其解决方案。
|
2天前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
11 0