关于javaunsafe包的信息

博主:adminadmin 2023-03-19 11:35:11 293

本篇文章给大家谈谈javaunsafe包,以及对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

求教java中的unsafe.allocateMemory 会导致内存申请失败吗

一:Java内存区域与内存溢出异常

在运行Java程序时,Java虚拟机会把管理的内存划分为若干个不同的数据区域。

Java虚拟机运行时数据区

数据区域图中,除了方法区和堆区是线程共享区外,其他三个是线程隔离的数据区(private)

程序计数器(Program Counter Register):属于线程私有的,占用的内存空间较少,可以看成是当前线程所执行字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选择下一条,需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能需要依赖这个计数器来完成,这个区域是jvm规范中没有规定任何OutOfMemoryError情况区域。

虚拟机栈:和程序计数器一样,都属于线程私有,生命周期与线程相同,描述的是java方法执行的内存模型,每个方法执行都会创建一个栈帧,用于存储局部变量表,操作栈,动态链接,方法出口等信息,每一个方法被调用直至执行完成的过程,就对应一个栈帧在jvm stack 从入栈到出栈的过程.局部变量表存放了编译期可知的各种数据基本类型(Boolean,byte,char,short,int,float,long,double),以及对象的引用。这个区域中定义了2种异常情况,如果线程请求的栈深度大于jvm所允许的深度,将抛出StackOverflowError异常,如果jvm可以动态扩张,当扩张无法申请到足够的内存空间是会抛出OutOfMemoryError异常。(这些数据区域异常将在下面的例子都讲到)。

本地方法栈:与虚拟机栈比较相似。其区别:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用Native方法服务。

堆(Heap):jvm中内存占用最大的一块,是所有线程共享的一块内存区域.在jvm启动时创建,存放的是所有对象实例(或数组),所有的对象实例都在这里进行动态分配,当类空间无法再扩张会抛出OutOfMemoryError异常。Java堆是垃圾收集器管理的主要区域,而收集器采用分代收集算法。

方法区(Method Area):与堆类似,也是各个线程共享的内存区域,主要用来存储加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,当方法区无法满足内存分配时,也抛出OutOfMemoryError异常。运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用相对于Class文件常量池的重要特征是具备动态性(常量并非强制编译期产生,运行期间也可以新增,例如String类的intern()方法)。

直接内存(DirectMemort):并不属于数据区,也不属于java定义的内存区域。由于NIO(New Input/Output)类,引入了一种基于通道与缓冲区(Buffer)的I/O方式。

对象访问

Object object = new Object();

Object object 这部分存储在java栈的本地变量表中,作为一个引用(reference)类型存在。

new Object() 这部分存储在java堆中,形成了一块存储了Object类型所有的实例数据值的结构化内存,动态变化,长度不固定。

方法区:在java堆中,必须要找到此对象类型数据,比如,对象类型,基类,实现的接口,方法等都存放在方法区。

对象访问方式有两种:句柄和直接指针。

句柄:reference中存储是对象的句柄地址,而句柄包含了对象实例数据和类型数据各自具体地址信息。好处:在对象移动时只需改变句柄中的实例数据指针,reference本身不需要修改。

直接指针:reference中直接存储的就是对象地址。好处:速度快,它节省了一次指针定位的时间开销。

实战:OutOfMemoryError异常

1. Java堆溢出

调整虚拟机最小值(-Xms)和最大值(-Xmx),并通过参数-XX:+HeapDumpOnOutOfMemoryError生成快照。要解决这个区域的异常,通过内存映像分析工具对快照分析,确认内存中的对象是否是必要的,分清楚出现了内存泄露还是内存溢出。若是内存泄露,通过工具查看泄露对象到GCRoots引用链,找到泄露对象是通过怎样的路径与GCRoots相关联并导致垃圾收集器无法自动回收。若不存在泄露,则检查虚拟机堆参数与机器物理内存对比看是否还能调大或从代码上检查某些对象生命周期是否过长,尝试减少程序运行期的内存消耗。

2. 虚拟机栈和本地方法栈溢出

调节栈容量大小(-Xss)。如果线程请求的栈深度大于虚拟机所允许的最大深度,将会抛出StackOverflowError异常。使用-Xss参数减小栈内存容量或者增加此方法帧中本地变量表的程度都使栈深度缩小。

3. 运行时常量池溢出

调节参数-XX:PermSize和-XX:MaxPermSize限制方法区的大小,然后使用String.intern()这个Native方法向常量池中添加内容。运行时常量池溢出,在OutOfMemoryError后面跟随提示信息是“PermGen space”,说明运行时常量池属于方法区(HotSpot虚拟机的永久代)的一部分。

4. 方法区溢出

同样使用参数-XX:PermSize和-XX:MaxPermSize限制方法区的大小,然后不断产生大量的class来加载到内存,从而出现OutOfMemoryError。所以在经常动态生成大量Class的应用中,需要特别注意类的回收状况。

5. 本机直接内存溢出

通过参数-XX:MaxDirectMemorySize指定DirectMemory容量,若不指定则与Java堆最大值一样。可以直接通过反射获取Unsafe实例并进行内存分配,使用unsafe.allocateMemory()申请分配内存。不足时会出现OutOfMemoryError。

二.垃圾收集器与内存分配策略

概述

Java内存运行时区域的各个部分,其中程序计数器、VM栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的帧随着方法进入、退出而有条不紊的进行着出栈入栈操作。而Java堆和方法区(包括运行时常量池)则不一样,我们必须等到程序实际运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的。

判断对象已死

1)引用计数算法(对象中添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的),但引用计数算法无法解决对象循环引用的问题。

根搜索算法(通过一系列的称为“GCRoots”的点作为起始进行向下搜索,当一个对象到GCRoots没有任何引用链(ReferenceChain)相连,则证明此对象是不可用的),主流程序语言Java,c#都使用此算法。在Java语言中,GC Roots包括:

1.在VM栈(帧中的本地变量)中的引用。2.方法区中的静态引用和常量引用的对象。3.JNI(即一般说的Native方法)中的引用。

2)生存还是死亡?

判定一个对象死亡,至少经历两次标记过程:如果对象在进行根搜索后,发现没有与GC Roots相连接的引用链,那它将会被第一次标记,并在稍后执行他的finalize()方法(如果它有的话)。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这点是必须的,否则一个对象在finalize()方法执行缓慢,甚至有死循环什么的将会很容易导致整个系统崩溃。 finalize()方法是对象最后一次逃脱死亡命运的机会,稍后GC将进行第二次规模稍小的标记,如果在finalize()中对象成功拯救自己(只要重新建立到GC Roots的连接即可,譬如把自己赋值到某个引用上),那在第二次标记时它将被移除出“即将回收”的集合,如果对象这时候还没有逃脱,那基本上它就真的离死不远了。需要关闭外部资源之类的事情,基本上它能做的使用try-finally可以做的更好。

3)回收方法区

方法区即后文提到的永久代,很多人认为永久代是没有GC的,这区GC的“性价比”一般比较低:在堆中,尤其是在新生代,进行一次GC可以一般可以回收70%~95%的空间,而永久代的GC效率远小于此。但是目前方法区主要回收两部分内容:废弃常量与无用类。需要满足下面3个条件:1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。2.加载该类的ClassLoader已经被GC。3.该类对应的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。

垃圾收集算法

1.标记-清除算法(Mark-Sweep)

算法分成“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象。主要缺点有两个,一是效率问题,标记和清理两个过程效率都不高,二是空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作。

2.复制算法(Copying)

将内存分为一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor,当回收时将eden和 survivor还存活的对象一次过拷贝到另外一块survivor空间上,然后清理掉eden和用过的survivor。复制收集算法在对象存活率高的时候,效率有所下降。

3.标记-整理(Mark-Compact)算法

标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存。

4.分代收集(Generational Collection)算法

此算法只是根据对象不同的存活周期将内存划分为几块。一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

垃圾收集器

没有最好的收集器,也没有万能的收集器,只有最合适的收集器。

1.Serial收集器单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用复制收集算法,虚拟机运行在Client模式时的默认新生代收集器。2.ParNew收集器ParNew收集器就是Serial的多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一摸一样。对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。3.Parallel Scavenge收集器Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。4.Serial Old收集器Serial Old是单线程收集器,使用标记-整理算法,是老年代的收集器,上面三种都是使用在新生代收集器。5.Parallel Old收集器老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。6.CMS(Concurrent Mark Sweep)收集器CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。

java.security包的jar包叫什么

\jdk1.6.0 -- JDK的根目录,包含一些软件版权,声明,和自述文件,

同时包含归档了的Java平台源代码包src.zip

\jdk1.6.0\bin -- JDK包含的一些开发工具执行文件

\jdk1.6.0\jre\bin\client

包含 Java HotSpotTM Client Virtual Machine 要用的 DLL 文件

\jdk1.6.0\jre\bin\server

包含 Java HotSpotTM Server Virtual Machine 要用的 DLL 文件

\jdk1.6.0\lib -- Java开发工具要用的一些库文件,有包含了支持JDK工具的非核心类库tool.jar,

dt.jar 归档的 BeanInfo 文件

用于告诉IDE这样显示java组件怎样让开发者在自己的应用程序中用户化它们

\jdk1.6.0\jre -- JDK使用的Java运行环境(JRE)的根目录,这个运行环境实现了Java平台

\jdk1.6.0\jre\bin -- Java平台所要用的工具和库的可执行文件

这些可执行文件和 /jdk1.6.0/bin相同的。

//Java 启动器工具充当了应用程序启动器(覆盖了1.1版本的JDK推出的旧版本JRE工具)

这个路径不需要设置 PATH 环境变量

\jdk1.6.0\jre\bin\client -- 包含Java Hotspot(Java性能引擎) 客户虚拟机要用的DLL文件

\jdk1.6.0\jre\bin\server -- 包含Java Hotspot(Java性能引擎) 服务器虚拟机要用的DLL文件

\jdk1.6.0\jre\lib -- JRE要用的代码库,属性设置,资源文件。

例如rt.jar Java 引导类库(java 核心APIRunTime类)

charsets.jar 字符转换类库

\jdk1.6.0\jre\lib\ext -- 默认的Java平台扩展安装环境

包含localedata.jar 是 ava.text 和 java.util包要用到的地区数据

\jdk1.6.0\jre\lib\security -- 包含安全管理文件,有安全规则(java.policy)

和安全属性文件(java.security)

\jdk1.6.0\jre\lib\applet -- Java applets 要的Jar包,可以放到lib/applet/目录,

这样可以节省 applet 类装载器从本地文件系统装载 大的applets 所需的applet类时间

减少从网上下载具有相同的保护的时间。

\jdk1.6.0\jre\lib\fonts 包含平台所需的TrueType字体文件

如何使用Unsafe操作内存中的Java类和对象

如果你已经获得了unsafe初期化后得实例, 那么直接调用其中的方法就好了。 通过Class的方法获取对象的内容, 然后unsafe自己得方法来set和操作。

Java为什么会引入及如何使用Unsafe

sun.misc.Unsafe至少从2004年Java1.4开始就存在于Java中了。在Java9中,为了提高JVM的可维护性,Unsafe和许多其他的东西一起都被作为内部使用类隐藏起来了。但是究竟是什么取代Unsafe不得而知,个人推测会有不止一样来取代它,那么问题来了,到底为什么要使用Unsafe?

做一些Java语言不允许但是又十分有用的事情

很多低级语言中可用的技巧在Java中都是不被允许的。对大多数开发者而言这是件好事,既可以拯救你,也可以拯救你的同事们。同样也使得导入开源代码更容易了,因为你能掌握它们可以造成的最大的灾难上限。或者至少明确你可以不小心失误的界限。如果你尝试地足够努力,你也能造成损害。

那你可能会奇怪,为什么还要去尝试呢?当建立库时,Unsafe中很多(但不是所有)方法都很有用,且有些情况下,除了使用JNI,没有其他方法做同样的事情,即使它可能会更加危险同时也会失去Java的“一次编译,永久运行”的跨平台特性。

对象的反序列化

当使用框架反序列化或者构建对象时,会假设从已存在的对象中重建,你期望使用反射来调用类的设置函数,或者更准确一点是能直接设置内部字段甚至是final字段的函数。问题是你想创建一个对象的实例,但你实际上又不需要构造函数,因为它可能会使问题更加困难而且会有副作用。

public class A implements Serializable {

private final int num;

public A(int num) {

System.out.println("Hello Mum");

this.num = num;

}

public int getNum() {

return num;

}

}

在这个类中,应该能够重建和设置final字段,但如果你不得不调用构造函数时,它就可能做一些和反序列化无关的事情。有了这些原因,很多库使用Unsafe创建实例而不是调用构造函数。

Unsafe unsafe = getUnsafe();

Class aClass = A.class;

A a = (A) unsafe.allocateInstance(aClass);

调用allocateInstance函数避免了在我们不需要构造函数的时候却调用它。

线程安全的直接获取内存

Unsafe的另外一个用途是线程安全的获取非堆内存。ByteBuffer函数也能使你安全的获取非堆内存或是DirectMemory,但它不会提供任何线程安全的操作。你在进程间共享数据时使用Unsafe尤其有用。

import sun.misc.Unsafe;

import sun.nio.ch.DirectBuffer;

import java.io.File;

import java.io.IOException;

import java.io.RandomAccessFile;

import java.lang.reflect.Field;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;

public class PingPongMapMain {

public static void main(String... args) throws IOException {

boolean odd;

switch (args.length 1 ? "usage" : args[0].toLowerCase()) {

case "odd":

odd = true;

break;

case "even":

odd = false;

break;

default:

System.err.println("Usage: java PingPongMain [odd|even]");

return;

}

int runs = 10000000;

long start = 0;

System.out.println("Waiting for the other odd/even");

File counters = new File(System.getProperty("java.io.tmpdir"), "counters.deleteme");

counters.deleteOnExit();

try (FileChannel fc = new RandomAccessFile(counters, "rw").getChannel()) {

MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

long address = ((DirectBuffer) mbb).address();

for (int i = -1; i runs; i++) {

for (; ; ) {

long value = UNSAFE.getLongVolatile(null, address);

boolean isOdd = (value 1) != 0;

if (isOdd != odd)

// wait for the other side.

continue;

// make the change atomic, just in case there is more than one odd/even process

if (UNSAFE.compareAndSwapLong(null, address, value, value + 1))

break;

}

if (i == 0) {

System.out.println("Started");

start = System.nanoTime();

}

}

}

System.out.printf("... Finished, average ping/pong took %,d ns%n",

(System.nanoTime() - start) / runs);

}

static final Unsafe UNSAFE;

static {

try {

Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");

theUnsafe.setAccessible(true);

UNSAFE = (Unsafe) theUnsafe.get(null);

} catch (Exception e) {

throw new AssertionError(e);

}

}

}

当你分别在两个程序,一个输入odd一个输入even,中运行时,可以看到两个进程都是通过持久化共享内存交换数据的。

在每个程序中,将相同的磁盘缓存映射到进程中。内存中实际上只有一份文件的副本存在。这意味着内存可以共享,前提是你使用线程安全的操作,比如volatile变量和CAS操作。(译注:CAS Compare and Swap 无锁算法)

在两个进程之间有83ns的往返时间。当考虑到System V IPC(进程间通信)大约需要2500ns,而且用IPC volatile替代persisted内存,算是相当快的了。

Unsafe适合在工作中使用吗?

个人不建议直接使用Unsafe。它远比原生的Java开发所需要的测试多。基于这个原因建议还是使用经过测试的库。如果你只是想自己用Unsafe,建议你最好在一个独立的类库中进行全面的测试。这限制了Unsafe在你的应用程序中的使用方式,但会给你一个更安全的Unsafe。

总结

Unsafe在Java中是很有趣的一个存在,你可以一个人在家里随便玩玩。它也有一些工作的应用程序特别是在写底层库的时候,但总的来说,使用经过测试的Unsafe库比直接用要好。

UnSafe类中的CAS操作

CAS操作

是通过compareAndSwapXXX方法实现的

/**

* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。

*

* @param obj 需要更新的对象

* @param offset obj中整型field的偏移量

* @param expect 希望field中存在的值

* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值

* @return 如果field的值被更改返回true

*/

public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。

首先介绍一下什么是Compare And Swap(CAS)?简单的说就是比较并交换。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。

在看一下volatile, Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的值是相同的,更简单一点理解就是volatile修饰的变量值发生变化时对于另外的线程是可见的。

如何正确使用volatile可以参考下面这篇文章:

Java

理论与实践: 正确使用 Volatile 变量

下面来看看java中具体的CAS操作类sun.misc.Unsafe。Unsafe类提供了硬件级别的原子操作,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。

关于javaunsafe包和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。