JVM基础

Java虚拟机

深入理解JVM读书笔记,先简单整理下,日后有时间在详细整理

JVM内存模型

JVM内存主要分为以下几大块

  1. 程序计数器:用于指示代码运行到哪一行,线程私有,唯一个没有OOM的区域。
  2. Java虚拟机栈:线程私有,跟线程的生命周期一样。调用方法时会创建栈帧,用于保存局部变量表、操作数栈、动态链接、方法出口等信息。
  3. 本地方法栈:跟Java虚拟机栈相似,只不过这里存的是native方法的信息。
  4. Java堆:线程共享,存放对象和数组,细分可以分为新生代和老年代。
  5. 方法区:存放类信息,线程共享,类变量就是存在这里,还有静态变量、常量。

内存泄漏与内存溢出

Java堆溢出

-Xms为堆最小值,-Xmx为堆最大值,设置成一样的话,就会避免堆自动扩展 这里会发生内存溢出和内存泄漏情况。

栈内存溢出

栈容量只由-Xss参数设定

  1. 当申请的栈深度大于虚拟机允许的最大值时发生StackOverflowErroe异常
  2. 当无法申请到足够的内存空间时,会发生OOM异常

在单线程时,出现溢出大多是第一种情况,多线程时,出现第二种情况可以考虑是否是给线程分配的内存太大。这是因为操作系统给进程的内存有限制,假如给虚拟机2G内存,减去堆和方法区,剩下的内存就基本固定了,给线程的内存越大,所能建立的线程越少。

方法区内存溢出

可以通过设置-XX:PermSize和-XX:MaxPermSize限制大小。

类的回收是比较苛刻的,生成大量Class的应用里,这里比较容易发生溢出,其他情况还有有大量JSP或会动态产生JSP文件的应用。

GC回收

判断对象死亡

引用计数算法

当一个对象被引用时,计数器加一,引用失效时减一,为0时说明死亡。但是这种算法无法避免互相引用的状况。

可达性分析算法

和GC Roots对象存在引用链的话就没有死亡,这种方法可以解决相互引用的问题。 GC Roots对象包含下面几种:

  1. 虚拟机栈引用的对象。
  2. 方法区里静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. Natice方法引用的对象。
四种引用类型
  1. 强引用:如Object obj=new Object()这种引用,只要强引用存在,永不被回收。
  2. 软引用:有用非必须对象,内存足够就不回收,不足则回收。常用于缓存对象,由SoftReference类实现。
  3. 弱引用:非必须对象,内存充足也会在第一次GC时被回收。由WeakReference类实现
  4. 虚引用:最弱的引用,主要用于跟踪对象被回收的过程。
finzlize

这个方法主要用于设置对象被回收前进行一些操作,如果一个对象不成引用链了,如果他没有覆盖这个方法或者方法已被调用,则会被回收,如果有必要执行这个方法,那么对象可以在这个方法里重新形成引用链,这个方法的执行不保证能执行结束。

垃圾回收算法

标记-清除算法

分为标记-清除两个部分,标记就是前面说的标记,全部标记后,统一清除。缺点有两个,一是时间问题,标记和清除速率都不高;二是空间问题,产生大量碎片,可能会导致需要分配大对象时没有连续内存而不得不提前进行另一次GC

复制算法

把内存分成一块比较大的Eden和两块比较小的Survivor,每次使用大的Eden和一块小的Survivor,GC时把存活的对象放到没有使用的那块内存里,然后一次性清理另外两块内存。

标记-整理算法

把存活的对象移动到一端去,最后清理另一端。

分代收集算法

Java里分为新生代和老年代,新生代用复制算法,老年代用标记整理或标记清除算法。

内存分配与回收

对象优先在新生代Eden区分配,当没有足够空间时,进行MinorGC,MonorGC非常频繁,速度比较快。大对象直接进入老年代和长期存活的对象进入老年代。老年代的连续空间大于新生对象总大小或历次晋升的平均大小,就会进行MinorGC,否则进行FullGC

垃圾收集器

  1. Serial收集器 单线程收集器,GC时其他线程全部暂停,优势简单高效。
  2. ParNew收集器 Serial的多线程版本,在Server模式下首选收集器。除了Serial,他是目前唯一一个可以和CMS收集器搭配的垃圾收集器。
  3. Paraller Scavenge收集器 多线程新生代收集器。关注点是吞吐量。
  4. Serial Old收集器 标记整理算法,收集老年代。
  5. Parallel Old收集器 Paraller 老年代版本。
  6. CMS收集器

类记载机制

类加载分7个阶段:加载、验证、准备、解析、初始化、使用、卸载

加载

加载主要做三件事:

  1. 获取定义此类的二进制字节流。
  2. 把字节流中的静态存储结构转化为方法区的运行时数据结构
  3. 在内存里生成Java.lang.Class对象,作为方法区的各种数据的访问入口

数组类由虚拟机直接创建,其他类由类加载器创建。

验证

主要是验证确保Class文件的信息符合虚拟机要求。其中包含了文件格式验证、元数据验证、字节码验证、符号引用验证。

准备阶段

把Static变量放大方法区里,并设定初始值。

剖析

把符号引用变成直接引用的过程。

初始化

有且只有下面五种情况对类进行加载

  1. 遇到new、getstatic、putstatic、invokestatic四个字节码指令时。
  2. 对类进行反射调用时,没有初始化会先初始化。
  3. 父类没有初始化就会先初始化父类。
  4. 有main()方法的类,先执行这个类的初始化。
  5. 使用JDK1.7的动态语言支持时。 这5种情况叫主动引用,其他都是被动引用,不会引起类的初始化

下面是一些常见的被动引用情况:

  1. 子类引用父类的静态代码段,不会引起子类初始化。
  2. 通过数组定义类,不会初始化,如下,不会引起类Persion类的初始化。
Persion [] per=new Persion[10];

类加载器

同一个Class文件被不同类加载器加载,得到的结果不相同。

三种系统提供的类加载器

启动类加载器(Bootstrap ClassLoader)

加载<JAVA_HOME>\lib目录里或被-Xbootclasspath参数指定的类库

扩展类加载器(Extension ClassLoader)

加载<JAVA_HOME>\lib\ext目录里或被java.ext.dirs系统变量指定的类库,开发者可以直接使用这个加载器

应用程序加载器(Application ClassLoader)

加载用户类路径(ClassPath)上的类库。

双亲委派模型

除了启动类加载器外,其他类加载器都有自己的父加载器,这种关系是通过组合类复用父加载器的。

工作过程:一个类受到加载请求,会把请求发送到父加载器里去,所以最终所类的请求都会发送到启动类加载器,当父加载器不能完成这个请求时(就是类没有放在这个父加载器的搜索范围里)是,会由子加载器尝试完成。

双亲委派关系的优点是保证了 一种有优先级的层次关系,前面说了,不同的类加载一个Class文件,结果不一样,假如我们自己实现了一个String类与rt.jar里的String相同的名字的类,这时候我们放在ClassPath下,这时候这个Class文件会被应用程序加载器加载,系统里会出现两个不一样的String类,这时候会发生混乱,这个String类会被编译成功,但是永远不会被加载运行。

Tomcat:正统的类加载器架构

Web服务器一般要解决以下几个问题:

  1. 部署在一个服务器上的两个Web应用程序使用的Java类库实现相互隔离,比如两个Web应用使用了不一样的Mybatis版本,这时候就必须隔离
  2. 可以实现共享类库,比如他们都使用了同一个版本的Spring框架,如果隔离了,容易造成资源浪费,Java虚拟机的方法区容易出现过度膨胀的危险。
  3. 服务器本身的类库要与应用的类库独立。出于安全考虑。
  4. 由于JSP大概率被修改,而且需要热替换

Tomcat目录结构

Tomcat有三组目录:(“/common/*” 、(“/server/*”、“/shared/*”)加上Web应用程序自身目录“/WEB-INF/”四组可以存放Java类库。

  1. 在/common目录下,类库可以被Tomcat和所有Web应用程序共用
  2. 在/server目录下,类库可被Tomcat使用,其他不能使用
  3. 在/Shared目录下,类库可以被所有Web应用使用,Tomcat不能使用
  4. 在/WebApp/WEB-INF目录中,近被Web应用程序自己使用,其他不能使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值