Java字符串常量池在JDK各个版本中的位置变化

前言:

1.内存调整:

1.1.JDK1.7及JDK1.7以后字符串常量池存放在了堆中。

  • jdk1.6 静态变量和字符串常量池在方法区, 方法区的实现方式是永久代(采用jvm内存)
  • jdk1.7 静态变量和字符串常量池在堆中, 方法区的实现方式是永久代(采用jvm内存)
  • jdk1.8 静态变量和字符串常量池在堆中, 方法区的实现方式是元空间(采用本地内存)

1.2.其他:

   栈: 存储方法中的局部变量和方法参数(每个方法在栈中会对应一块区域叫栈帧,存储本方法内容)

   堆:存储new出来的对象。

2.笔试题:

new  String ("a" +"b");

创建两个对象:   
第一个: 字符串优化机制,编译期会将 "a" +"b"优化为  "ab"存储到字符串常量池。
第二个: new String对象

3.作用:

字符串常量池的作用: 节省内存空间,提高性能(不会频繁操作内存,减少内存回收)

多个引用如果指向相同对象,则让这几个引用都指向字符串常量池中的同一个字符串常量对象。

一、虚拟机内存模型

1.本地方法栈: native方法的栈帧(局部变量....)

2.虚拟机栈 : java方法的 栈帧(局部变量....)

3.堆 : 对象(成员变量)

4.方法区: 静态变量、字符串常量池、方法信息(方法的名称、返回值、参数数量及类型、方法的字节码信息)、从class字节码文件加载的类的相关信息<类的全限定名,类的直接父类名称,修饰符,直接接口的一个有序列表.
注意: jdk1.6 静态变量和字符串常量池在方法区, 方法区的实现方式是永久代(采用jvm内存)
注意: jdk1.7 静态变量和字符串常量池在堆中, 方法区的实现方式是永久代(采用jvm内存)
注意: jdk1.8 静态变量和字符串常量池在堆中, 方法区的实现方式是元空间(采用本地内存) 

5.程序计数器: 计数器记录的是正在执行的虚拟机字节码指令的地址。

二、字符串常量池的位置变化:

JDK1.6时字符串常量池在运行时常量池内,属于运行时常量池的一部分

JDK6到JDK7的变化是把静态常量和字符串常量从方法区中移到了堆中

JDK7到JDK8的变化是对方法区的实现由永久代变为了元空间

jdk6.0及以前的版本是,字符串常量池是放在堆的Perm区的。Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等,默认大小只有4m,一旦常量池中大量使用intern是会直接产生java.lang.OutOfMemoryError:Perm Gen Space错误。

jdk7.0版本中,字符串常量已经从Perm区转移到正常的Java Heap区域了。为什么移动,Perm区太小是主要原因。

jdk8.0已经直接取消了Perm区域,而新建立了一个元区域,应该是JDK开发者认为Perm区域已经不适合现在的Java发展了。

Java知识体系之内存模型 - 作业部落 Cmd Markdown 编辑阅读器

三、java内存模型:

1.类装载器(ClassLoader)(用来装载.class文件)

2.执行引擎(执行字节码,或者执行本地方法)

3.运行时数据区 (图片上的蓝色区域)(方法区、堆、java栈、PC寄存器、本地方法栈)

具体每一块区域的介绍:

  • 第一块:PC寄存器

PC寄存器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。

  • 第二块:JVM栈

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址

  • 第三块:堆(Heap)

它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
(1)堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
(2)Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
(3)TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。

  • 第四块:方法区域(Method Area)

(1)在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。
(2)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

  • 第五块:运行时常量池(Runtime Constant Pool)

存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

  • 第六块:本地方法堆栈(Native Method Stacks)

JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

四、JVM类加载器:

4.1.JVM类加载过程的步骤:

   装载->链接->初始化

1.装载:
装载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:
类名+包名+ClassLoader实例ID。

2.链接:
链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
完成校验后,JVM初始化类中的静态变量,并将其值赋为默认值。
最后对类中的所有属性、方法进行验证,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。

3.初始化:
初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:
调用了new;
反射调用了类中的方法;
子类调用了初始化;
JVM启动过程中指定的初始化类。
JVM****类加载顺序:
在加载一个类之前,会先把请求委派给父类加载器处理,因此所有的请求最终会传送到顶层的启动类加载器BootstrapClassLoader,当父类加载器无法处理时,才由类自己来加载。当父类加载器为null时,会使用启动类加载器BootstrapClassLoader作为父类加载器。

4.2.类加载的原理:

  双亲委派机制

优点:

1.保证类加载的安全性。通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。

2.避免重复加载。双亲委派机制可以避免同一个类被多次加载。

  • 启动类装载器是JVM实现的一部分;
  • 用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。

JVM装载类的顺序:

自下而上查找: 应用程序需要加载某个类时,由User-Defined(用户自定义类加载器)向Bootstrap(启动类加载器)方向查找要加载类,如果加载过就返回,没加载过就继续查找上一层加载器;

自上而下加载: 启动类加载器无法加载某个类时,会逐层将加载任务往用户自定义加载器的方向传递,看当前类加载器能否加载此类,不能加载就继续往下传递,直到最底层的用户自定义加载器能加载就加载,加载不了就报异常: 无法加载此类;

如com.my.A。假设com.my.A在启动类加载器的加载目录中,而应用程序类加载器接到了加载A类的任务。

详细加载过程:

1.用户自定义加载器首先判断自己加载过没有加载过就返回,没有加载过就继续判断父类加载器(应用程序类加载器)是否加载过。

2.应用程序类加载器判断自己加载过没有,加载过就返回,没有加载过就继续判断父类加载器(扩展类加载器)是否加载过。

3.扩展类加载器判断自己加载过没有,如果加载过就返回,没有加载过就继续判断父类加载器(启动类加载器)是否加载过。

4启动类加载器判断自己加载过没有,如果加载过就返回,没有加载过就尝试加载此类。加载不了就将加载任务传递到子类加载器(扩展类加载器)加载。

5.扩展类加载器尝试加载此类,能加载的话就成功加载并返回此类。否则将加载任务传递到子类加载器(应用程序类加载器)加载。

6.应用程序类加载器尝试加载此类,能加载的话就成功加载并返回此类。否则将抛出异常: 无法加载此类。

4.2.1.自下而上判断:

加载一个类首先由用户自定义加载器(就是应用程序类加载器)加载,用户自定义加载器接收到一个加载类的任务时,会询问上上一层加载器是否加载过, 一层一层询问直到启动类加载器(如果其中某个类加载器加载过就直接返回)。

4.2.2.自上而下加载:

如果都没加载过,则启动类加载器加载,启动类加载器如果加载不了,就会将加载任务传递到下一层,下一层如果能加载就加载,   不能就将加载任务再传递到下一层。。。。。。。

4.3.类加载器介绍:

1.Bootstrap ClassLoader:  
  这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。
2.Extension ClassLoader:  JVM用此classloader来加载扩展功能的一些jar包。
3.System ClassLoader: 
  JVM用此classloader来加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。
4.User-Defined ClassLoader:
User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。

4.4.加载器的关键方法:

(1)loadClass():
  此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法。
(2)findLoadedClass():此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。
(3)findClass:
此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
(4)findSystemClass():
此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然为找到,则返回null。
(5)defineClass():此方法负责将二进制的字节码转换为Class对象
(6)resolveClass():此方法负责完成Class对象的链接,如已链接过,则会直接返回。

五、JVM垃圾回收

GC****的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

(1)对新生代的对象的收集称为minor GC;

(2)对旧生代的对象的收集称为Full GC;

(3)程序中主动调用System.gc()强制执行的GC为Full GC。

5.1.对象的四种引用类型:

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:
(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
(3)弱引用:在GC时一定会被GC回收
(4)虚引用:由于虚引用只是用来得知对象是否被GC

1.强引用:当对象被一个或一个以上的引用类型变量引用时,此对象处于可达状态不会被回收。(当没有引用变量引用此对象时,对象就处于不可达状态。 就非常非常可能会被回收)

2.软引用:软引用需要用到SoftReference类,内存不够时,可能会被回收。

(通常用于内存敏感的程序中) 对于不是必须一直存在于内存中的对象可以使用软引用,比如处理一张图片的过程,如果更重要的程序需要在处理过程中途运行,可以将这张图片使用软引用。 (应用于缓存数据<硬盘中还有一份儿, 内存不够时可以清理这个缓存中的数据,因为硬盘中还有一份儿数据>)(中途的程序在内存中存不下时,就删除这个软引用代表的对象)

3.弱引用:弱引用需要用到WeakReference类,无论内存够不够,只要垃圾回收器启动,弱引用关联的对象肯定被回收。用来描述非必需对象。(和软引用很像,但比软引用级别更低)。

常见的应用是ThreadLocal保存数据时key采用弱引用。

再比如: java集合中有一个WeakHashMap专门用来存储弱引用的(map中存储弱引用,弱引用指向一个对象)

WeakHashMap<String, Object> whm=new WeakHashMap<String, Object>();

4.虚引用:PhantomReference: 虚引用需要用到PhantomReference类, 用来在对象被回收时接收一个系统通知。

如果一个对象具有虚引用,那么它和没有任何引用一样,

被虚引用关联的对象引用通过get方法获取到的永远为null,也就是说这种对象在任何时候都有可能被垃圾回收器回收,通过这种方式关联的对象也无法调用对象中的方法。

虚引用主要是用来管理堆外内存的,通过ReferenceQueue这个类实现,当一个对象被回收的时候,会向这个引用队列里面添加相关数据,给一个通知。

5.2.java堆内存结构:

Java堆内存区域中存储对象的区域,分为新生代和老年代。

年轻代和老年区的内存占用比例是1:2,

年轻代占年轻代和老年代总空间1/3,老年代占年轻代和老年代总空间的2/3

1.年轻代(Young Generation)

  • 1.1.所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速将生命周期短的并符合回收条件的对象回收掉。
  • 1.2.年轻代(也叫新生代)内存按照8:1:1的比例分为一个eden区(占年轻代内存空间的80%)和两个survivor(survivor0,survivor1)区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
  • 1.3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代(eden和两个survivor区)、老年代都进行回收
  • 4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

2.老年代(Old Generation)

  • 2.1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
  • 2.2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低(Full GC表示把Eden、两个Survivor和老年代都回收),老年代对象存活时间比较长。

3.持久代(Permanent Generation):

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

5.3.垃圾收集算法总结:

上图描述的是垃圾收集器分代回收算法机制:

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。

1..对象是否要回收:

  • 引用计数法(Reference Counting)
  • 可达性分析算法(Reachability Analysis)

2.标记-清理算法(Mark and Sweep)(适合用在老年代)、标记-整理算法(Compacting)(适合用在老年代)、复制-清除算法(Copying)(适合用在年轻代)

3. MinorGC用于回收新生代区域(采用复制-清除算法把新生代分为三块区域,<1.Eden , 2.survivor From,  3.survivor To> 将需要回收的对象复制到survivor To 再清理,再将survivor From和 survivor To交换)

4.Full GC用于回收老生代区域(FullGC同时会触发MinorGC的执行)

5.案例解析:

1. java垃圾回收机制-案例解析:
           抽烟后垃圾直接扔地上, 由清洁工(垃圾收集器)每隔一段时间来打扫

2.C语言垃圾回收机制-案例解析:
           抽烟后自己回收垃圾(垃圾由程序开发者自己管理并回收)

5.4.GC算法 垃圾收集器概述:

垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。

jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的.

5.5.对象存活判断:

判断对象是否存活一般有两种方式:

1.引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

2.可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。(GC Roots指的是引用对象的引用类型变量)

在Java语言中,GC Roots包括:虚拟机栈中引用的对象。方法区中类静态属性实体引用的对象。方法区中常量引用的对象。本地方法栈中JNI引用的对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值