У нас есть небольшое C-приложение, которое мы используем для запуска всех наших Java-приложений через JNI. Это позволяет нам:
- Имеет осмысленное имя процесса (особенно в Windows)
- Иметь собственные значки (опять же, важно для Windows)
- Динамически создавать пути к классам (мы разбираем содержимое файла / lib для автоматического включения всех jar-файлов)
Для наших приложений мы просто жестко прописываем ограничение кучи, но вы можете легко динамически настроить максимальный размер кучи на основе доступной памяти.
Такого рода небольшое приложение на самом деле довольно легко сделать (это одна из самых простых вещей, которые можно сделать с JNI). Хорошей отправной точкой будет исходный код для JDK (есть подпапка для самого java.exe, которую вы можете использовать - это то, что мы сделали). Большинство людей весьма удивлены, обнаружив, что java.exe - это маленькое крошечное приложение (<200 строк кода), которое просто вызывает JNI и передает аргументы командной строки в (черт возьми, даже использование метода с именем main () довольно необязательно когда-то вы начинаете запускать вещи самостоятельно). </p>
Вот код, который не только запускает JVM и т. Д. ..., но и определяет максимальный объем кучи на основе доступной оперативной памяти компьютера. Это большой код для поста SO, и он совсем не симпатичный - но это закаленный в битве код - он использовался в течение почти десятилетия на многих сотнях установок и т. Д ... Наслаждайтесь:
#include <windows.h>
#include <jni.h>
#include <string>
#include <sstream>
using namespace std;
#define STARTUP_CLASS "some/path/to/YourStartupClass"
void vShowError(string sErrorMessage);
void vShowJREError(string sErrorMessage);
void vShowLastError(string sErrorMessage);
void vDestroyVM(JNIEnv *env, JavaVM *jvm);
void vAddOption(string& sName);
string GetClassPath(string root);
string GetJREPath();
int getMaxHeapAvailable(int permGenMB, int maxHeapMB);
JavaVMOption* vm_options;
int mctOptions = 0;
int mctOptionCapacity = 0;
boolean GetApplicationHome(char *buf, jint sz);
typedef jint (CALLBACK *CreateJavaVM)(JavaVM
**pvm, JNIEnv **penv, void *args);
boolean PathExists(string &path)
{
DWORD dwAttr = GetFileAttributes(path.c_str());
if (dwAttr == 0xffffffff)
return FALSE;
else
return TRUE;
}
// returns TRUE is there was an exception, FALSE otherwise
BOOL GetExceptionString(JNIEnv* jenv, string &result)
{
jthrowable ex;
if (NULL != (ex = jenv->ExceptionOccurred())) {
// clear exception
jenv->ExceptionClear();
jmethodID gmID = jenv->GetMethodID(
jenv->FindClass("java/lang/Throwable"),
"getMessage",
"()Ljava/lang/String;");
jstring jerrStr = (jstring)jenv->CallObjectMethod(ex,gmID);
// now you can look at the error message string
if (jerrStr != NULL){ // make sure getMessage() didn't return null
const char *errStr = jenv->GetStringUTFChars(jerrStr,0);
result = errStr;
jenv->ReleaseStringUTFChars(jerrStr, errStr);
} else {
result = "null";
}
return TRUE;
} else {
return FALSE;
}
}
BOOL GetJRESystemProperty(JNIEnv *env, string propname, string &propval, string &errmessage)
{
// now check for minimum JRE version requirement
jclass cls = env->FindClass("java/lang/System");
if (cls == NULL){
errmessage = "Unable to interact with Java Virtual Machine - please visit www.java.com and confirm that your Java installation is valid.";
return FALSE;
}
jmethodID mid = env->GetStaticMethodID(cls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
if (mid == NULL){
errmessage = "Unable to obtain Java runtime system properties - please visit www.java.net and confirm that your Java installation is valid.";
return FALSE;
}
jstring propName = env->NewStringUTF( propname.c_str() );
jstring result = (jstring) env->CallStaticObjectMethod(cls, mid, propName);
const char* utfResult = env->GetStringUTFChars( result, NULL );
if (utfResult == NULL){
errmessage = "Unable to obtain Java runtime system property " + propname + " - please visit www.java.net and confirm that your Java installation is valid.";
return FALSE;
}
propval = utfResult;
env->ReleaseStringUTFChars( result, utfResult );
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
JNIEnv *env;
JavaVM *jvm;
jint jintVMStartupReturnValue;
jclass jclassStartup;
jmethodID midStartup;
// Path Determination
// --- application home
char home[2000];
if (!GetApplicationHome(home, sizeof(home))) {
vShowError("Unable to determine application home.");
return 0;
}
string sAppHome(home);
string sOption_AppHome = "-Dapplication.home=" + sAppHome;
string sJREPath = GetJREPath();
// --- VM Path
string sRuntimePath = sJREPath + "\\bin\\client\\"; // must contain jvm.dll
string sJVMpath = sRuntimePath + "jvm.dll";
// --- boot path
string sBootPath = sJREPath + "\\lib";
string sOption_BootPath = "-Dsun.boot.class.path=" + sBootPath;
// --- class path
//string sClassPath = sAppHome + "\\lib;" + sAppHome + "\\lib\\" + APP_JAR + ";" + sAppHome + "\\lib\\log4j-1.2.7.jar";
string cpRoot = sAppHome + "\\";
string sClassPath = GetClassPath(cpRoot);
string sOption_ClassPath = "-Djava.class.path=" + sClassPath;
string sOption_JavaLibraryPath = "-Djava.library.path=" + sAppHome + "\\lib";
int maxHeapBM = 768;
int argStart = 1; // the first argument passed in that should be passed along to the JVM
if(__argc > 1){
string maxheapstr = __argv[1];
if (maxheapstr.substr(0, 9).compare("/maxheap=") == 0){
maxheapstr = maxheapstr.substr(9);
maxHeapBM = atoi(maxheapstr.c_str());
argStart++;
}
}
// we now use adaptive max heap size determination - we try for 768MB of heap, but if we don't get it, we can back off and use less instead of failing the launch
// note: we had problems going for 1024 heap at TrueNorth - it would throttle back to 848 and fail with error -4 no matter what I did
int maxHeapMB = getMaxHeapAvailable(62, maxHeapBM);
stringstream ss;
ss << "-Xmx";
ss << maxHeapMB;
ss << "m";
string sOption_HeapSpace = ss.str();
string sOption_PermSize = "-XX:MaxPermSize=62m";
string sOption_HeapDump = "-XX:+HeapDumpOnOutOfMemoryError";
if (strstr(szCmdLine, "/launcher_verbose") != NULL){
string msg = "App Home = ";
msg += sAppHome;
msg += "\nJRE Path = ";
msg += sJREPath;
msg += "\nRuntime Path = ";
msg += sRuntimePath;
msg += "\nClass Path = ";
msg += sClassPath;
msg += "\nHeap argument = ";
msg += sOption_HeapSpace;
msg += "\nPermsize argument = ";
msg += sOption_PermSize;
msg += "\nHeap dump = ";
msg += sOption_HeapDump;
msg += "\njava.library.path = ";
msg += sOption_JavaLibraryPath;
msg += "\nCommand line = ";
msg += szCmdLine;
FILE *f = fopen("launcher.txt", "w");
fprintf(f, "%s", msg.c_str());
fclose(f);
MessageBox(0, msg.c_str(), "Launcher Verbose Info", MB_OK);
}
// setup VM options
// vAddOption(string("-verbose"));
vAddOption(sOption_ClassPath);
vAddOption(sOption_AppHome);
vAddOption(sOption_HeapSpace);
vAddOption(sOption_PermSize);
vAddOption(sOption_HeapDump);
vAddOption(sOption_JavaLibraryPath);
// initialize args
JavaVMInitArgs vm_args;
vm_args.version = 0x00010002;
vm_args.options = vm_options;
vm_args.nOptions = mctOptions;
vm_args.ignoreUnrecognized = JNI_TRUE;
// need to diddle with paths to ensure that jvm can find correct libraries - see http://www.duckware.com/tech/java6msvcr71.html
string sBinPath = sJREPath + "\\bin";
char originalCurrentDirectory[4096];
GetCurrentDirectory(4095, originalCurrentDirectory);
SetCurrentDirectory(sBinPath.c_str());
// Dynamic binding to SetDllDirectory()
typedef BOOL (WINAPI *LPFNSDD)(LPCTSTR lpPathname);
HINSTANCE hKernel32 = GetModuleHandle("kernel32");
LPFNSDD lpfnSetDllDirectory = (LPFNSDD)GetProcAddress(hKernel32, "SetDllDirectoryA");
if (lpfnSetDllDirectory){
lpfnSetDllDirectory(sBinPath.c_str());
}
// load jvm library
HINSTANCE hJVM = LoadLibrary(sJVMpath.c_str());
SetCurrentDirectory(originalCurrentDirectory);
if (lpfnSetDllDirectory){
lpfnSetDllDirectory(NULL);
}
if( hJVM == NULL ){
vShowJREError("Java does not appear to be installed on this machine. Click OK to go to www.java.com where you can download and install Java");
return 0;
}
// try to start 1.2/3/4 VM
// uses handle above to locate entry point
CreateJavaVM lpfnCreateJavaVM = (CreateJavaVM)
GetProcAddress(hJVM, "JNI_CreateJavaVM");
jintVMStartupReturnValue = (*lpfnCreateJavaVM)(&jvm, &env, &vm_args);
// test for success
if (jintVMStartupReturnValue < 0) {
stringstream ss;
ss << "There is a problem with the 32 bit Java installation on this computer (";
ss << jintVMStartupReturnValue;
ss << "). Click OK to go to www.java.com where you can download and re-install 32 bit Java";
vShowJREError(ss.str());
// I don't think we should destroy the VM - it never was created...
//vDestroyVM(env, jvm);
return 0;
}
//now check for minimum jvm version
string version = "";
string errormsg = "";
if (!GetJRESystemProperty(env, "java.specification.version", version, errormsg)){
vShowJREError(errormsg);
vDestroyVM(env, jvm);
return 0;
}
double verf = atof(version.c_str());
if (verf < 1.599f){
string sErrorMessage = "This application requires Java Runtime version 1.6 or above, but your runtime is version " + version + "\n\nClick OK to go to www.java.com and update to the latest Java Runtime Environment";
vShowJREError(sErrorMessage);
vDestroyVM(env, jvm);
return 0;
}
// find startup class
string sStartupClass = STARTUP_CLASS;
// notice dots are translated to slashes
jclassStartup = env->FindClass(sStartupClass.c_str());
if (jclassStartup == NULL) {
string sErrorMessage = "Unable to find startup class [" + sStartupClass + "]";
vShowError(sErrorMessage);
vDestroyVM(env, jvm);
return 0;
}
// find startup method
string sStartupMethod_Identifier = "main";
string sStartupMethod_TypeDescriptor =
"([Ljava/lang/String;)V";
midStartup =
env->GetStaticMethodID(jclassStartup,
sStartupMethod_Identifier.c_str(),
sStartupMethod_TypeDescriptor.c_str());
if (midStartup == NULL) {
string sErrorMessage =
"Unable to find startup method ["
+ sStartupClass + "."
+ sStartupMethod_Identifier
+ "] with type descriptor [" +
sStartupMethod_TypeDescriptor + "]";
vShowError(sErrorMessage);
vDestroyVM(env, jvm);
return 0;
}
// create array of args to startup method
jstring jstringExampleArg;
jclass jclassString;
jobjectArray jobjectArray_args;
jstringExampleArg = env->NewStringUTF("example string");
if (jstringExampleArg == NULL){
vDestroyVM(env, jvm);
return 0;
}
jclassString = env->FindClass("java/lang/String");
jobjectArray_args = env->NewObjectArray(__argc-argStart, jclassString, jstringExampleArg);
if (jobjectArray_args == NULL){
vDestroyVM(env, jvm);
return 0;
}
int count;
for (count = argStart; count < __argc; count++){
env->SetObjectArrayElement(jobjectArray_args, count-1, env->NewStringUTF(__argv[count]));
}
// call the startup method -
// this starts the Java program
env->CallStaticVoidMethod(jclassStartup, midStartup, jobjectArray_args);
string errstr;
if (GetExceptionString(env, errstr)){
vShowError(errstr);
}
// attempt to detach main thread before exiting
if (jvm->DetachCurrentThread() != 0) {
vShowError("Could not detach main thread.\n");
}
// this call will hang as long as there are
// non-daemon threads remaining
jvm->DestroyJavaVM();
return 0;
}
void vDestroyVM(JNIEnv *env, JavaVM *jvm)
{
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
jvm->DestroyJavaVM();
}
void vShowError(string sError) {
MessageBox(NULL, sError.c_str(), "Startup Error", MB_OK);
}
void vShowJREError(string sError) {
MessageBox(NULL, sError.c_str(), "Startup Error", MB_OK);
ShellExecute(NULL, "open", "http://www.java.com", NULL, NULL, SW_SHOWNORMAL);
}
/* Shows an error message in an OK box with the
system GetLastError appended in brackets */
void vShowLastError(string sLocalError) {
LPVOID lpSystemMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpSystemMsgBuf, 0, NULL );
string sSystemError = string((LPTSTR)lpSystemMsgBuf);
vShowError(sLocalError + " [" + sSystemError + "]");
}
void vAddOption(string& sValue) {
mctOptions++;
if (mctOptions >= mctOptionCapacity) {
if (mctOptionCapacity == 0) {
mctOptionCapacity = 3;
vm_options = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
} else {
JavaVMOption *tmp;
mctOptionCapacity *= 2;
tmp = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
memcpy(tmp, vm_options, (mctOptions-1) * sizeof(JavaVMOption));
free(vm_options);
vm_options = tmp;
}
}
vm_options[mctOptions-1].optionString = (char*)sValue.c_str();
}
/* If buffer is "c:\app\bin\java",
* then put "c:\app" into buf. */
jboolean GetApplicationHome(char *buf, jint sz) {
char *cp;
GetModuleFileName(0, buf, sz);
*strrchr(buf, '\\') = '\0';
if ((cp = strrchr(buf, '\\')) == 0) {
// This happens if the application is in a
// drive root, and there is no bin directory.
buf[0] = '\0';
return JNI_FALSE;
}
return JNI_TRUE;
}
string GetClassPath(string root){
string rootWithBackslash = root;
if (rootWithBackslash[rootWithBackslash.length()-1] != '\\')
rootWithBackslash += "\\";
string cp = rootWithBackslash + "classes\\"; //first entry in the cp
string libPathWithBackslash = rootWithBackslash + "lib\\";
// now find all jar files...
string searchSpec = libPathWithBackslash;
searchSpec = libPathWithBackslash + "*.jar";
WIN32_FIND_DATA fd;
HANDLE find = FindFirstFile(searchSpec.c_str(), &fd);
while (find != NULL){
cp += ";";
cp += libPathWithBackslash;
cp += fd.cFileName;
if (!FindNextFile(find, &fd)){
FindClose(find);
find = NULL;
}
}
return cp;
}
string GetJREPath(){
// first, check for JRE in application directory
char home[2000];
if (!GetApplicationHome(home, sizeof(home))) {
vShowError("Unable to determine application home.");
return 0;
}
string sJREPath(home);
sJREPath += "\\jre";
if (PathExists(sJREPath)){
return sJREPath;
}
/* - don't check JAVA_HOME - it may be incorrect...
// next, check the JAVA_HOME environment variable
GetEnvironmentVariable("JAVA_HOME", home, sizeof(home));
sJREPath = home;
if (PathExists(sJREPath)){
return sJREPath;
}
*/
// next, check registry
HKEY hKeyJRERoot;
HKEY hKeyJREInstance;
DWORD dwType;
DWORD dwSize;
BYTE *pData;
string valueName;
string value;
LONG regRslt;
sJREPath = "";
regRslt = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\JavaSoft\\Java Runtime Environment", 0, KEY_READ, &hKeyJRERoot);
if (regRslt == ERROR_SUCCESS){
valueName = "CurrentVersion";
regRslt = RegQueryValueEx(hKeyJRERoot, valueName.c_str(), NULL, &dwType, NULL, &dwSize);
if (regRslt == ERROR_SUCCESS){
pData = (BYTE *)malloc(dwSize);
value = "";
regRslt = RegQueryValueEx(hKeyJRERoot, valueName.c_str(), NULL, &dwType, pData, &dwSize);
if (regRslt == ERROR_SUCCESS){
value = (LPCSTR)pData;
}
free(pData);
if (value != ""){
regRslt = RegOpenKeyEx(hKeyJRERoot, value.c_str(), 0, KEY_READ, &hKeyJREInstance);
if (regRslt == ERROR_SUCCESS){
valueName = "JavaHome";
value = "";
regRslt = RegQueryValueEx(hKeyJREInstance, valueName.c_str(), NULL, &dwType, NULL, &dwSize);
if (regRslt == ERROR_SUCCESS){
pData = (BYTE *)malloc(dwSize);
regRslt = RegQueryValueEx(hKeyJREInstance, valueName.c_str(), NULL, &dwType, pData, &dwSize);
if (regRslt == ERROR_SUCCESS){
value = (LPCSTR)pData;
sJREPath = value;
}
free(pData);
}
RegCloseKey(hKeyJREInstance);
}
}
}
RegCloseKey(hKeyJRERoot);
}
return sJREPath;
}
static const DWORD NUM_BYTES_PER_MB = 1024 * 1024;
bool canAllocate(DWORD bytes)
{
LPVOID lpvBase;
lpvBase = VirtualAlloc(NULL, bytes, MEM_RESERVE, PAGE_READWRITE);
if (lpvBase == NULL) return false;
VirtualFree(lpvBase, 0, MEM_RELEASE);
return true;
}
int getMaxHeapAvailable(int permGenMB, int maxHeapMB)
{
DWORD originalMaxHeapBytes = 0;
DWORD maxHeapBytes = 0;
int numMemChunks = 0;
SYSTEM_INFO sSysInfo;
DWORD maxPermBytes = permGenMB * NUM_BYTES_PER_MB; // Perm space is in addition to the heap size
DWORD numBytesNeeded = 0;
GetSystemInfo(&sSysInfo);
// jvm aligns as follows:
// quoted from size_t GenCollectorPolicy::compute_max_alignment() of jdk 7 hotspot code:
// The card marking array and the offset arrays for old generations are
// committed in os pages as well. Make sure they are entirely full (to
// avoid partial page problems), e.g. if 512 bytes heap corresponds to 1
// byte entry and the os page size is 4096, the maximum heap size should
// be 512*4096 = 2MB aligned.
// card_size computation from CardTableModRefBS::SomePublicConstants of jdk 7 hotspot code
int card_shift = 9;
int card_size = 1 << card_shift;
DWORD alignmentBytes = sSysInfo.dwPageSize * card_size;
maxHeapBytes = maxHeapMB * NUM_BYTES_PER_MB + 50*NUM_BYTES_PER_MB; // 50 is an overhead fudge factory per https://forums.oracle.com/forums/thread.jspa?messageID=6463655 (they had 28, I'm bumping it 'just in case')
// make it fit in the alignment structure
maxHeapBytes = maxHeapBytes + (maxHeapBytes % alignmentBytes);
numMemChunks = maxHeapBytes / alignmentBytes;
originalMaxHeapBytes = maxHeapBytes;
// loop and decrement requested amount by one chunk
// until the available amount is found
numBytesNeeded = maxHeapBytes + maxPermBytes;
while (!canAllocate(numBytesNeeded) && numMemChunks > 0)
{
numMemChunks --;
maxHeapBytes = numMemChunks * alignmentBytes;
numBytesNeeded = maxHeapBytes + maxPermBytes;
}
if (numMemChunks == 0) return 0;
// we can allocate the requested size, return it now
if (maxHeapBytes == originalMaxHeapBytes) return maxHeapMB;
// calculate the new MaxHeapSize in megabytes
return maxHeapBytes / NUM_BYTES_PER_MB;
}